diff --git a/Misc/dqn_cpp_file.h b/Misc/dqn_cpp_file.h index bfd0a88..328bc7e 100644 --- a/Misc/dqn_cpp_file.h +++ b/Misc/dqn_cpp_file.h @@ -2,7 +2,7 @@ #define DQN_CPP_FILE_H // NOTE: Dqn_CppFile: Helper functions to generate C++ files -// ============================================================================= +// ///////////////////////////////////////////////////////////////////////////// #include /// printf, fputc #include /// va_list... #include /// assert @@ -51,21 +51,21 @@ void Dqn_CppPrint(Dqn_CppFile *cpp, char const *fmt, ...); #define Dqn_CppEnumBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__), true); \ + (Dqn_CppBeginEnumBlock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndEnumBlock(cpp), false)) #define Dqn_CppForBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, "for (" fmt ")", ##__VA_ARGS__), true); \ + (Dqn_CppBeginForBLock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndForBlock(cpp), false)) #define Dqn_CppWhileBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, "while (" fmt ")", ##__VA_ARGS__), true); \ + (Dqn_CppBeginWhileBlock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ - DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndForBlock(cpp), false)) + DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndWhileBlock(cpp), false)) #define Dqn_CppIfOrElseIfBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ @@ -81,22 +81,28 @@ void Dqn_CppPrint(Dqn_CppFile *cpp, char const *fmt, ...); #define Dqn_CppFuncBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__), true); \ + (Dqn_CppBeginFuncBlock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndFuncBlock(cpp), false)) #define Dqn_CppStructBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, "struct " fmt, ##__VA_ARGS__), true); \ + (Dqn_CppBeginStructBlock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndStructBlock(cpp), false)) #define Dqn_CppSwitchBlock(cpp, fmt, ...) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ - (Dqn_CppBeginBlock(cpp, false /*append*/, "switch (" fmt ")", ##__VA_ARGS__), true); \ + (Dqn_CppBeginSwitchBlock(cpp, fmt, ##__VA_ARGS__), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndSwitchBlock(cpp), false)) +#define Dqn_CppBlock(cpp, ending, fmt, ...) \ + for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = \ + (Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__), true); \ + DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ + DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppEndBlock(cpp, ending), false)) + #define Dqn_CppIfChain(cpp) \ for (bool DQN_CPP_TOKEN_PASTE_(once_, __LINE__) = (Dqn_CppBeginIfChain(cpp), true); \ DQN_CPP_TOKEN_PASTE_(once_, __LINE__); \ @@ -106,29 +112,32 @@ void Dqn_CppPrint(Dqn_CppFile *cpp, char const *fmt, ...); /// increasing the indent level after the brace. void Dqn_CppBeginBlock (Dqn_CppFile *cpp, bool append, char const *fmt, ...); void Dqn_CppBeginBlockV(Dqn_CppFile *cpp, bool append, char const *fmt, va_list args); -void Dqn_CppEndBlock (Dqn_CppFile *cpp); +void Dqn_CppEndBlock (Dqn_CppFile *cpp, char const *ending); /// Begin/End a block, specifically for the following language constructs. -#define Dqn_CppBeginEnumBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__) -#define Dqn_CppEndEnumBlock(cpp) Dqn_CppEndBlock(cpp), Dqn_CppAppend(cpp, ";\n") +#define Dqn_CppBeginEnumBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, "enum " fmt, ##__VA_ARGS__) +#define Dqn_CppEndEnumBlock(cpp) Dqn_CppEndBlock(cpp, ";\n") -#define Dqn_CppBeginForBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__) -#define Dqn_CppEndForBlock(cpp) Dqn_CppEndBlock(cpp), Dqn_CppAppend(cpp, "\n") +#define Dqn_CppBeginWhileBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, "while (" fmt ")", ##__VA_ARGS__) +#define Dqn_CppEndWhileBlock(cpp) Dqn_CppEndBlock(cpp, "\n") + +#define Dqn_CppBeginForBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, "for (" fmt ")", ##__VA_ARGS__) +#define Dqn_CppEndForBlock(cpp) Dqn_CppEndBlock(cpp, "\n") #define Dqn_CppBeginFuncBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__) -#define Dqn_CppEndFuncBlock(cpp) Dqn_CppEndBlock(cpp), Dqn_CppAppend(cpp, "\n") +#define Dqn_CppEndFuncBlock(cpp) Dqn_CppEndBlock(cpp, "\n") -#define Dqn_CppBeginStructBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__) -#define Dqn_CppEndStructBlock(cpp) Dqn_CppEndBlock(cpp), Dqn_CppAppend(cpp, ";\n") +#define Dqn_CppBeginStructBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, "struct " fmt, ##__VA_ARGS__) +#define Dqn_CppEndStructBlock(cpp) Dqn_CppEndBlock(cpp, ";\n") -#define Dqn_CppBeginSwitchBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, fmt, ##__VA_ARGS__) -#define Dqn_CppEndSwitchBlock(cpp) Dqn_CppEndBlock(cpp), Dqn_CppAppend(cpp, "\n") +#define Dqn_CppBeginSwitchBlock(cpp, fmt, ...) Dqn_CppBeginBlock(cpp, false /*append*/, "switch (" fmt ")", ##__VA_ARGS__) +#define Dqn_CppEndSwitchBlock(cpp) Dqn_CppEndBlock(cpp, "\n") void Dqn_CppBeginIfOrElseIfBlock (Dqn_CppFile *cpp, char const *fmt, ...); -#define Dqn_CppEndIfOrElseIfBlock(cpp) Dqn_CppEndBlock(cpp) +#define Dqn_CppEndIfOrElseIfBlock(cpp) Dqn_CppEndBlock(cpp, "") void Dqn_CppBeginElseBlock (Dqn_CppFile *cpp); -#define Dqn_CppEndElseBlock(cpp) Dqn_CppEndBlock(cpp) +void Dqn_CppEndElseBlock (Dqn_CppFile *cpp); #define DQN_CPP_TOKEN_PASTE2_(x, y) x ## y #define DQN_CPP_TOKEN_PASTE_(x, y) DQN_CPP_TOKEN_PASTE2_(x, y) @@ -179,20 +188,23 @@ void Dqn_CppBeginBlockV(Dqn_CppFile *cpp, bool append, char const *fmt, va_list Dqn_CppAppendV(cpp, fmt, args); else Dqn_CppPrintV(cpp, fmt, args); - Dqn_CppAppend(cpp, " {\n"); + + bool empty_fmt = fmt == nullptr || strlen(fmt) == 0; + Dqn_CppAppend(cpp, "%s{\n", empty_fmt ? "" : " "); Dqn_CppIndent(cpp); } -void Dqn_CppEndBlock(Dqn_CppFile *cpp) +void Dqn_CppEndBlock(Dqn_CppFile *cpp, char const *ending) { Dqn_CppUnindent(cpp); - Dqn_CppPrint(cpp, "}"); + Dqn_CppPrint(cpp, "}%s", ending); } void Dqn_CppBeginIfOrElseIfBlock(Dqn_CppFile *cpp, char const *fmt, ...) { va_list args; va_start(args, fmt); + assert(cpp->if_chain_size); if (cpp->if_chain[cpp->if_chain_size - 1] == 0) Dqn_CppPrint(cpp, "if"); else @@ -208,10 +220,17 @@ void Dqn_CppBeginIfOrElseIfBlock(Dqn_CppFile *cpp, char const *fmt, ...) void Dqn_CppBeginElseBlock(Dqn_CppFile *cpp) { + assert(cpp->if_chain_size); if (cpp->if_chain[cpp->if_chain_size - 1] >= 1) Dqn_CppBeginBlock(cpp, true /*append*/, " else"); } +void Dqn_CppEndElseBlock(Dqn_CppFile *cpp) +{ + if (cpp->if_chain[cpp->if_chain_size - 1] >= 1) + Dqn_CppEndBlock(cpp, ""); +} + void Dqn_CppBeginIfChain(Dqn_CppFile *cpp) { assert(cpp->if_chain_size < sizeof(cpp->if_chain)/sizeof(cpp->if_chain[0])); @@ -220,8 +239,12 @@ void Dqn_CppBeginIfChain(Dqn_CppFile *cpp) void Dqn_CppEndIfChain(Dqn_CppFile *cpp) { - if (cpp->if_chain[cpp->if_chain_size - 1] >= 1) + assert(cpp->if_chain_size); + if (cpp->if_chain[cpp->if_chain_size - 1] >= 1) { Dqn_CppNewLine(cpp); + } + cpp->if_chain[cpp->if_chain_size - 1] = 0; + cpp->if_chain_size--; } #endif // DQN_CPP_FILE_IMPLEMENTATION diff --git a/Misc/dqn_json.h b/Misc/dqn_json.h index 0318bf6..92bb856 100644 --- a/Misc/dqn_json.h +++ b/Misc/dqn_json.h @@ -4,103 +4,93 @@ #if !defined(DQN_JSON_H) #define DQN_JSON_H +// NOTE: Dqn_JSON ////////////////////////////////////////////////////////////////////////////////// -// NOTE: Dqn_JSON -// ----------------------------------------------------------------------------- -void *Dqn_JSON_ArenaAllocFunc(void *user_data, size_t count); +void *Dqn_JSON_ArenaAllocFunc (void *user_data, size_t count); char const *Dqn_JSON_TypeEnumCString(json_type_e type, size_t *size); -bool Dqn_JSON_String8Cmp(json_string_s const *lhs, Dqn_String8 rhs); +bool Dqn_JSON_String8Cmp (json_string_s const *lhs, Dqn_Str8 rhs); -// NOTE: Dqn_JSON_Iterator -// ----------------------------------------------------------------------------- -enum Dqn_JSONIteratorEntryType +// NOTE: Dqn_JSON_It ///////////////////////////////////////////////////////////////////////// +enum Dqn_JSONItEntryType { - Dqn_JSON_IteratorEntryTypeObjElement, - Dqn_JSON_IteratorEntryTypeObj, - Dqn_JSON_IteratorEntryTypeArrayElement, - Dqn_JSON_IteratorEntryTypeArray, - Dqn_JSON_IteratorEntryTypeString, - Dqn_JSON_IteratorEntryTypeNumber, + Dqn_JSON_ItEntryTypeObjElement, + Dqn_JSON_ItEntryTypeObj, + Dqn_JSON_ItEntryTypeArrayElement, + Dqn_JSON_ItEntryTypeArray, + Dqn_JSON_ItEntryTypeString, + Dqn_JSON_ItEntryTypeNumber, }; -struct Dqn_JSONIteratorEntry +struct Dqn_JSONItEntry { - Dqn_JSONIteratorEntryType type; - void *value; + Dqn_JSONItEntryType type; + void *value; }; -struct Dqn_JSONIterator +struct Dqn_JSONIt { - Dqn_JSONIteratorEntry stack[128]; - int stack_count; - size_t flags; + Dqn_JSONItEntry stack[128]; + int stack_count; + size_t flags; }; -// NOTE: Dqn_JSON_IteratorPush/Pop -// ----------------------------------------------------------------------------- -bool Dqn_JSON_IteratorPushObjElement (Dqn_JSONIterator *it, json_object_element_s *element); -bool Dqn_JSON_IteratorPushObj (Dqn_JSONIterator *it, json_object_s *obj); -bool Dqn_JSON_IteratorPushArrayElement(Dqn_JSONIterator *it, json_array_element_s *element); -bool Dqn_JSON_IteratorPushArray (Dqn_JSONIterator *it, json_value_s *value); -bool Dqn_JSON_IteratorPushValue (Dqn_JSONIterator *it, json_value_s *value); -void Dqn_JSON_IteratorPop (Dqn_JSONIterator *it); +Dqn_JSONIt Dqn_JSON_LoadFileToIt(Dqn_Arena *arena, Dqn_Str8 json); -// NOTE: Dqn_JSON_Iterator tree navigation -// ----------------------------------------------------------------------------- -json_value_s *Dqn_JSON_IteratorPushCurrValue(Dqn_JSONIterator *it); -bool Dqn_JSON_IteratorNext(Dqn_JSONIterator *it); +// NOTE: Dqn_JSON_ItPush/Pop ///////////////////////////////////////////////////////////////// +bool Dqn_JSON_ItPushObjElement (Dqn_JSONIt *it, json_object_element_s *element); +bool Dqn_JSON_ItPushObj (Dqn_JSONIt *it, json_object_s *obj); +bool Dqn_JSON_ItPushArrayElement(Dqn_JSONIt *it, json_array_element_s *element); +bool Dqn_JSON_ItPushArray (Dqn_JSONIt *it, json_value_s *value); +bool Dqn_JSON_ItPushValue (Dqn_JSONIt *it, json_value_s *value); +void Dqn_JSON_ItPop (Dqn_JSONIt *it); -#define Dqn_JSON_IteratorErrorUnrecognisedKey(it) Dqn_JSON_IteratorErrorUnrecognisedKey_(it, DQN_STRING8(__FILE__), DQN_STRING8(__func__), __LINE__) -void Dqn_JSON_IteratorErrorUnrecognisedKey_(Dqn_JSONIterator *it, Dqn_String8 file, Dqn_String8 func, Dqn_uint line); +// NOTE: Dqn_JSON_It tree navigation ///////////////////////////////////////////////////////// +json_value_s *Dqn_JSON_ItPushCurrValue(Dqn_JSONIt *it); +bool Dqn_JSON_ItNext(Dqn_JSONIt *it); -#define Dqn_JSON_IteratorPushCurrValueIterateThenPop(it) \ - for(void *DQN_UNIQUE_NAME(ptr) = Dqn_JSON_IteratorPushCurrValue(it); DQN_UNIQUE_NAME(ptr); Dqn_JSON_IteratorPop(it), DQN_UNIQUE_NAME(ptr) = nullptr) \ - while (Dqn_JSON_IteratorNext(it)) +#define Dqn_JSON_ItPushCurrValueIterateThenPop(it) \ + for(void *DQN_UNIQUE_NAME(ptr) = Dqn_JSON_ItPushCurrValue(it); DQN_UNIQUE_NAME(ptr); Dqn_JSON_ItPop(it), DQN_UNIQUE_NAME(ptr) = nullptr) \ + while (Dqn_JSON_ItNext(it)) -// NOTE: Dqn_JSON_IteratorCurr -// ----------------------------------------------------------------------------- -Dqn_JSONIteratorEntry *Dqn_JSON_IteratorCurr(Dqn_JSONIterator *it); -json_value_s *Dqn_JSON_IteratorCurrValue(Dqn_JSONIterator *it); -json_object_element_s *Dqn_JSON_IteratorCurrObjElement(Dqn_JSONIterator *it); +// NOTE: Dqn_JSON_ItCurr ///////////////////////////////////////////////////////////////////// +Dqn_JSONItEntry *Dqn_JSON_ItCurr(Dqn_JSONIt *it); +json_value_s *Dqn_JSON_ItCurrValue(Dqn_JSONIt *it); +json_object_element_s *Dqn_JSON_ItCurrObjElement(Dqn_JSONIt *it); -// NOTE: Dqn_JSON_IteratorValueIs -// ----------------------------------------------------------------------------- -json_value_s *Dqn_JSON_IteratorValueIs(Dqn_JSONIterator *it, json_type_e type); -json_object_s *Dqn_JSON_IteratorValueIsObj(Dqn_JSONIterator *it); -json_array_s *Dqn_JSON_IteratorValueIsArray(Dqn_JSONIterator *it); -json_string_s *Dqn_JSON_IteratorValueIsString(Dqn_JSONIterator *it); -json_number_s *Dqn_JSON_IteratorValueIsNumber(Dqn_JSONIterator *it); -json_value_s *Dqn_JSON_IteratorValueIsBool(Dqn_JSONIterator *it); +// NOTE: Dqn_JSON_ItValueIs ////////////////////////////////////////////////////////////////// +json_value_s *Dqn_JSON_ItValueIs(Dqn_JSONIt *it, json_type_e type); +json_object_s *Dqn_JSON_ItValueIsObj(Dqn_JSONIt *it); +json_array_s *Dqn_JSON_ItValueIsArray(Dqn_JSONIt *it); +json_string_s *Dqn_JSON_ItValueIsString(Dqn_JSONIt *it); +json_number_s *Dqn_JSON_ItValueIsNumber(Dqn_JSONIt *it); +json_value_s *Dqn_JSON_ItValueIsBool(Dqn_JSONIt *it); +json_value_s *Dqn_JSON_ItValueIsNull(Dqn_JSONIt *it); -size_t Dqn_JSON_IteratorValueArraySize(Dqn_JSONIterator *it); +size_t Dqn_JSON_ItValueArraySize(Dqn_JSONIt *it); -// NOTE: Dqn_JSON_IteratorKeyValueIs -// ----------------------------------------------------------------------------- -Dqn_String8 Dqn_JSON_IteratorKey(Dqn_JSONIterator *it); -bool Dqn_JSON_IteratorKeyIs(Dqn_JSONIterator *it, Dqn_String8 key); -json_object_s *Dqn_JSON_IteratorKeyValueIsObj(Dqn_JSONIterator *it, Dqn_String8 key); -json_array_s *Dqn_JSON_IteratorKeyValueIsArray(Dqn_JSONIterator *it, Dqn_String8 key); -json_string_s *Dqn_JSON_IteratorKeyValueIsString(Dqn_JSONIterator *it, Dqn_String8 key); -json_number_s *Dqn_JSON_IteratorKeyValueIsNumber(Dqn_JSONIterator *it, Dqn_String8 key); -json_value_s *Dqn_JSON_IteratorKeyValueIsBool(Dqn_JSONIterator *it, Dqn_String8 key); +// NOTE: Dqn_JSON_ItKeyValueIs /////////////////////////////////////////////////////////////// +Dqn_Str8 Dqn_JSON_ItKey(Dqn_JSONIt *it); +bool Dqn_JSON_ItKeyIs(Dqn_JSONIt *it, Dqn_Str8 key); +json_object_s *Dqn_JSON_ItKeyValueIsObj(Dqn_JSONIt *it, Dqn_Str8 key); +json_array_s *Dqn_JSON_ItKeyValueIsArray(Dqn_JSONIt *it, Dqn_Str8 key); +json_string_s *Dqn_JSON_ItKeyValueIsString(Dqn_JSONIt *it, Dqn_Str8 key); +json_number_s *Dqn_JSON_ItKeyValueIsNumber(Dqn_JSONIt *it, Dqn_Str8 key); +json_value_s *Dqn_JSON_ItKeyValueIsBool(Dqn_JSONIt *it, Dqn_Str8 key); +json_value_s *Dqn_JSON_ItKeyValueIsNull(Dqn_JSONIt *it, Dqn_Str8 key); -// NOTE: Dqn_JSON_IteratorValueTo -// ----------------------------------------------------------------------------- -Dqn_String8 Dqn_JSON_IteratorValueToString(Dqn_JSONIterator *it); -int64_t Dqn_JSON_IteratorValueToI64(Dqn_JSONIterator *it); -uint64_t Dqn_JSON_IteratorValueToU64(Dqn_JSONIterator *it); -bool Dqn_JSON_IteratorValueToBool(Dqn_JSONIterator *it); +// NOTE: Dqn_JSON_ItValueTo ////////////////////////////////////////////////////////////////// +Dqn_Str8 Dqn_JSON_ItValueToString(Dqn_JSONIt *it); +int64_t Dqn_JSON_ItValueToI64(Dqn_JSONIt *it); +uint64_t Dqn_JSON_ItValueToU64(Dqn_JSONIt *it); +bool Dqn_JSON_ItValueToBool(Dqn_JSONIt *it); -#define Dqn_JSON_IteratorErrorUnknownKeyValue(it) \ - Dqn_JSON_IteratorErrorUnknownKeyValue_(it, DQN_CALL_SITE) - -void Dqn_JSON_IteratorErrorUnknownKeyValue_(Dqn_JSONIterator *it, Dqn_String8 file, Dqn_String8 func, int line); +#define Dqn_JSON_ItErrorUnknownKeyValue(it) Dqn_JSON_ItErrorUnknownKeyValue_(it, DQN_CALL_SITE) +void Dqn_JSON_ItErrorUnknownKeyValue_(Dqn_JSONIt *it, Dqn_CallSite call_site); #endif // DQN_JSON_H #if defined(DQN_JSON_IMPLEMENTATION) -// NOTE: Dqn_JSON -// ----------------------------------------------------------------------------- +// NOTE: Dqn_JSON ////////////////////////////////////////////////////////////////////////////////// void *Dqn_JSON_ArenaAllocFunc(void *user_data, size_t count) { void *result = NULL; @@ -108,7 +98,7 @@ void *Dqn_JSON_ArenaAllocFunc(void *user_data, size_t count) return result; Dqn_Arena *arena = DQN_CAST(Dqn_Arena*)user_data; - result = Dqn_Arena_Allocate(arena, count, alignof(json_value_s), Dqn_ZeroMem_No); + result = Dqn_Arena_Alloc(arena, count, alignof(json_value_s), Dqn_ZeroMem_No); return result; } @@ -127,70 +117,86 @@ char const *Dqn_JSON_TypeEnumCString(json_type_e type, size_t *size) } } -bool Dqn_JSON_String8Cmp(json_string_s const *lhs, Dqn_String8 key) +bool Dqn_JSON_String8Cmp(json_string_s const *lhs, Dqn_Str8 key) { bool result = false; - if (lhs && Dqn_String8_IsValid(key)) { - Dqn_String8 lhs_string = Dqn_String8_Init(lhs->string, lhs->string_size); - result = Dqn_String8_Eq(lhs_string, key); + if (lhs && Dqn_Str8_HasData(key)) { + Dqn_Str8 lhs_string = Dqn_Str8_Init(lhs->string, lhs->string_size); + result = Dqn_Str8_Eq(lhs_string, key); } return result; } -// NOTE: Dqn_JSON_Iterator_push/pop -// ----------------------------------------------------------------------------- -bool Dqn_JSON_IteratorPushObjElement(Dqn_JSONIterator *it, json_object_element_s *element) +// NOTE: Dqn_JSON_It /////////////////////////////////////////////////////////////////////////////// +Dqn_JSONIt Dqn_JSON_LoadFileToIt(Dqn_Arena *arena, Dqn_Str8 json) +{ + json_parse_result_s parse_result = {}; + json_value_ex_s *ex_value = + DQN_CAST(json_value_ex_s *) json_parse_ex(json.data, + json.size, + json_parse_flags_allow_location_information, + Dqn_JSON_ArenaAllocFunc, + arena, + &parse_result); + + Dqn_JSONIt result = {}; + Dqn_JSON_ItPushValue(&result, &ex_value->value); + return result; +} + +// NOTE: Dqn_JSON_ItPush/Pop /////////////////////////////////////////////////////////////////////// +bool Dqn_JSON_ItPushObjElement(Dqn_JSONIt *it, json_object_element_s *element) { if (!it || !element) return false; DQN_ASSERT(it->stack_count < DQN_ARRAY_ICOUNT(it->stack)); - it->stack[it->stack_count++] = {Dqn_JSON_IteratorEntryTypeObjElement, element}; + it->stack[it->stack_count++] = {Dqn_JSON_ItEntryTypeObjElement, element}; return true; } -bool Dqn_JSON_IteratorPushObj(Dqn_JSONIterator *it, json_object_s *obj) +bool Dqn_JSON_ItPushObj(Dqn_JSONIt *it, json_object_s *obj) { if (!it || !obj) return false; DQN_ASSERT(it->stack_count < DQN_ARRAY_ICOUNT(it->stack)); - it->stack[it->stack_count++] = {Dqn_JSON_IteratorEntryTypeObj, obj}; + it->stack[it->stack_count++] = {Dqn_JSON_ItEntryTypeObj, obj}; return true; } -bool Dqn_JSON_IteratorPushArrayElement(Dqn_JSONIterator *it, json_array_element_s *element) +bool Dqn_JSON_ItPushArrayElement(Dqn_JSONIt *it, json_array_element_s *element) { if (!it || !element) return false; DQN_ASSERT(it->stack_count < DQN_ARRAY_ICOUNT(it->stack)); - it->stack[it->stack_count++] = {Dqn_JSON_IteratorEntryTypeArrayElement, element}; + it->stack[it->stack_count++] = {Dqn_JSON_ItEntryTypeArrayElement, element}; return true; } -bool Dqn_JSON_IteratorPushArray(Dqn_JSONIterator *it, json_value_s *value) +bool Dqn_JSON_ItPushArray(Dqn_JSONIt *it, json_value_s *value) { if (!it || !value || json_value_as_array(value) == nullptr) return false; DQN_ASSERT(it->stack_count < DQN_ARRAY_ICOUNT(it->stack)); - it->stack[it->stack_count++] = {Dqn_JSON_IteratorEntryTypeArray, value}; + it->stack[it->stack_count++] = {Dqn_JSON_ItEntryTypeArray, value}; return true; } -bool Dqn_JSON_IteratorPushValue(Dqn_JSONIterator *it, json_value_s *value) +bool Dqn_JSON_ItPushValue(Dqn_JSONIt *it, json_value_s *value) { bool result = false; if (!it || !value) return result; if (value->type == json_type_object) { - result = Dqn_JSON_IteratorPushObj(it, json_value_as_object(value)); + result = Dqn_JSON_ItPushObj(it, json_value_as_object(value)); } else if (value->type == json_type_array) { - result = Dqn_JSON_IteratorPushArray(it, value); + result = Dqn_JSON_ItPushArray(it, value); } return result; } -void Dqn_JSON_IteratorPop(Dqn_JSONIterator *it) +void Dqn_JSON_ItPop(Dqn_JSONIt *it) { if (!it) return; @@ -199,19 +205,18 @@ void Dqn_JSON_IteratorPop(Dqn_JSONIterator *it) it->stack_count--; } -// NOTE: Dqn_JSON_Iterator json tree navigation -// ----------------------------------------------------------------------------- -json_value_s *Dqn_JSON_IteratorPushCurrValue(Dqn_JSONIterator *it) +// NOTE: Dqn_JSON_It JSON tree navigation ////////////////////////////////////////////////////////// +json_value_s *Dqn_JSON_ItPushCurrValue(Dqn_JSONIt *it) { json_value_s *result = nullptr; - Dqn_JSONIteratorEntry *curr = Dqn_JSON_IteratorCurr(it); + Dqn_JSONItEntry *curr = Dqn_JSON_ItCurr(it); if (!curr) return result; - if (curr->type == Dqn_JSON_IteratorEntryTypeObjElement) { + if (curr->type == Dqn_JSON_ItEntryTypeObjElement) { json_object_element_s *element = DQN_CAST(json_object_element_s *) curr->value; result = element->value; - } else if (curr->type == Dqn_JSON_IteratorEntryTypeArrayElement) { + } else if (curr->type == Dqn_JSON_ItEntryTypeArrayElement) { json_array_element_s *element = DQN_CAST(json_array_element_s *) curr->value; result = element->value; } else { @@ -221,57 +226,56 @@ json_value_s *Dqn_JSON_IteratorPushCurrValue(Dqn_JSONIterator *it) if (result->type == json_type_array) { json_array_s *array = json_value_as_array(result); DQN_ASSERT(array); - Dqn_JSON_IteratorPushArray(it, result); + Dqn_JSON_ItPushArray(it, result); } else if (result->type == json_type_object) { json_object_s *obj = json_value_as_object(result); DQN_ASSERT(obj); - Dqn_JSON_IteratorPushObj(it, obj); + Dqn_JSON_ItPushObj(it, obj); } return result; } -bool Dqn_JSON_IteratorNext(Dqn_JSONIterator *it) +bool Dqn_JSON_ItNext(Dqn_JSONIt *it) { - Dqn_JSONIteratorEntry *curr = Dqn_JSON_IteratorCurr(it); + Dqn_JSONItEntry *curr = Dqn_JSON_ItCurr(it); if (!curr) return false; json_object_element_s *obj_element = nullptr; json_array_element_s *array_element = nullptr; - if (curr->type == Dqn_JSON_IteratorEntryTypeObj) { + if (curr->type == Dqn_JSON_ItEntryTypeObj) { auto *obj = DQN_CAST(json_object_s *) curr->value; obj_element = obj->start; - } else if (curr->type == Dqn_JSON_IteratorEntryTypeObjElement) { + } else if (curr->type == Dqn_JSON_ItEntryTypeObjElement) { auto *element = DQN_CAST(json_object_element_s *) curr->value; obj_element = element->next; - Dqn_JSON_IteratorPop(it); - } else if (curr->type == Dqn_JSON_IteratorEntryTypeArray) { + Dqn_JSON_ItPop(it); + } else if (curr->type == Dqn_JSON_ItEntryTypeArray) { auto *value = DQN_CAST(json_value_s *) curr->value; auto *array = json_value_as_array(value); array_element = array->start; - } else if (curr->type == Dqn_JSON_IteratorEntryTypeArrayElement) { + } else if (curr->type == Dqn_JSON_ItEntryTypeArrayElement) { auto *element = DQN_CAST(json_array_element_s *) curr->value; array_element = element->next; - Dqn_JSON_IteratorPop(it); + Dqn_JSON_ItPop(it); } else { - Dqn_JSON_IteratorPop(it); + Dqn_JSON_ItPop(it); } if (obj_element) - Dqn_JSON_IteratorPushObjElement(it, obj_element); + Dqn_JSON_ItPushObjElement(it, obj_element); else if (array_element) - Dqn_JSON_IteratorPushArrayElement(it, array_element); + Dqn_JSON_ItPushArrayElement(it, array_element); bool result = obj_element || array_element; return result; } -// NOTE: Dqn_JSON_IteratorCurr -// ----------------------------------------------------------------------------- -Dqn_JSONIteratorEntry *Dqn_JSON_IteratorCurr(Dqn_JSONIterator *it) +// NOTE: Dqn_JSON_ItCurr /////////////////////////////////////////////////////////////////////////// +Dqn_JSONItEntry *Dqn_JSON_ItCurr(Dqn_JSONIt *it) { - Dqn_JSONIteratorEntry *result = nullptr; + Dqn_JSONItEntry *result = nullptr; if (!it || it->stack_count <= 0) return result; @@ -279,23 +283,23 @@ Dqn_JSONIteratorEntry *Dqn_JSON_IteratorCurr(Dqn_JSONIterator *it) return result; } -json_value_s *Dqn_JSON_IteratorCurrValue(Dqn_JSONIterator *it) +json_value_s *Dqn_JSON_ItCurrValue(Dqn_JSONIt *it) { json_value_s *result = nullptr; - Dqn_JSONIteratorEntry *curr = Dqn_JSON_IteratorCurr(it); + Dqn_JSONItEntry *curr = Dqn_JSON_ItCurr(it); if (!curr) return result; - if (curr->type == Dqn_JSON_IteratorEntryTypeObjElement) { + if (curr->type == Dqn_JSON_ItEntryTypeObjElement) { auto *element = DQN_CAST(json_object_element_s *)curr->value; result = element->value; - } else if (curr->type == Dqn_JSON_IteratorEntryTypeArrayElement) { + } else if (curr->type == Dqn_JSON_ItEntryTypeArrayElement) { auto *element = DQN_CAST(json_array_element_s *)curr->value; result = element->value; - } else if (curr->type == Dqn_JSON_IteratorEntryTypeString || - curr->type == Dqn_JSON_IteratorEntryTypeNumber || - curr->type == Dqn_JSON_IteratorEntryTypeObj || - curr->type == Dqn_JSON_IteratorEntryTypeArray) + } else if (curr->type == Dqn_JSON_ItEntryTypeString || + curr->type == Dqn_JSON_ItEntryTypeNumber || + curr->type == Dqn_JSON_ItEntryTypeObj || + curr->type == Dqn_JSON_ItEntryTypeArray) { result = DQN_CAST(json_value_s *)curr->value; } @@ -303,74 +307,79 @@ json_value_s *Dqn_JSON_IteratorCurrValue(Dqn_JSONIterator *it) return result; } -json_object_element_s *Dqn_JSON_IteratorCurrObjElement(Dqn_JSONIterator *it) +json_object_element_s *Dqn_JSON_ItCurrObjElement(Dqn_JSONIt *it) { - Dqn_JSONIteratorEntry *curr = Dqn_JSON_IteratorCurr(it); - auto *result = (curr && curr->type == Dqn_JSON_IteratorEntryTypeObjElement) + Dqn_JSONItEntry *curr = Dqn_JSON_ItCurr(it); + auto *result = (curr && curr->type == Dqn_JSON_ItEntryTypeObjElement) ? DQN_CAST(json_object_element_s *) curr->value : nullptr; return result; } -// NOTE: Dqn_JSON_IteratorValueIs -// ----------------------------------------------------------------------------- -json_value_s *Dqn_JSON_IteratorValueIs(Dqn_JSONIterator *it, json_type_e type) +// NOTE: Dqn_JSON_ItValueIs //////////////////////////////////////////////////////////////////////// +json_value_s *Dqn_JSON_ItValueIs(Dqn_JSONIt *it, json_type_e type) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_value_s *result = (curr && type == curr->type) ? curr : nullptr; return result; } -json_object_s *Dqn_JSON_IteratorValueIsObj(Dqn_JSONIterator *it) +json_object_s *Dqn_JSON_ItValueIsObj(Dqn_JSONIt *it) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_object_s *result = curr ? json_value_as_object(curr) : nullptr; return result; } -json_array_s *Dqn_JSON_IteratorValueIsArray(Dqn_JSONIterator *it) +json_array_s *Dqn_JSON_ItValueIsArray(Dqn_JSONIt *it) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_array_s *result = curr ? json_value_as_array(curr) : nullptr; return result; } -json_string_s *Dqn_JSON_IteratorValueIsString(Dqn_JSONIterator *it) +json_string_s *Dqn_JSON_ItValueIsString(Dqn_JSONIt *it) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_string_s *result = curr ? json_value_as_string(curr) : nullptr; return result; } -json_number_s *Dqn_JSON_IteratorValueIsNumber(Dqn_JSONIterator *it) +json_number_s *Dqn_JSON_ItValueIsNumber(Dqn_JSONIt *it) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_number_s *result = curr ? json_value_as_number(curr) : nullptr; return result; } -json_value_s *Dqn_JSON_IteratorValueIsBool(Dqn_JSONIterator *it) +json_value_s *Dqn_JSON_ItValueIsBool(Dqn_JSONIt *it) { - json_value_s *curr = Dqn_JSON_IteratorCurrValue(it); + json_value_s *curr = Dqn_JSON_ItCurrValue(it); json_value_s *result = (curr && (curr->type == json_type_true || curr->type == json_type_false)) ? curr : nullptr; return result; } -size_t Dqn_JSON_IteratorValueArraySize(Dqn_JSONIterator *it) +json_value_s *Dqn_JSON_ItValueIsNull(Dqn_JSONIt *it) +{ + json_value_s *curr = Dqn_JSON_ItCurrValue(it); + json_value_s *result = (curr && (curr->type == json_type_null)) ? curr : nullptr; + return result; +} + +size_t Dqn_JSON_ItValueArraySize(Dqn_JSONIt *it) { size_t result = 0; - if (json_array_s *curr = Dqn_JSON_IteratorValueIsArray(it)) + if (json_array_s *curr = Dqn_JSON_ItValueIsArray(it)) result = curr->length; return result; } -// NOTE: Dqn_JSON_IteratorKeyValueIs -// ----------------------------------------------------------------------------- -Dqn_String8 Dqn_JSON_IteratorKey(Dqn_JSONIterator *it) +// NOTE: Dqn_JSON_ItKeyValueIs ///////////////////////////////////////////////////////////////////// +Dqn_Str8 Dqn_JSON_ItKey(Dqn_JSONIt *it) { - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); - Dqn_String8 result = {}; + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); + Dqn_Str8 result = {}; if (curr) { result.data = DQN_CAST(char *)curr->name->string; result.size = curr->name->string_size; @@ -378,98 +387,107 @@ Dqn_String8 Dqn_JSON_IteratorKey(Dqn_JSONIterator *it) return result; } -bool Dqn_JSON_IteratorKeyIs(Dqn_JSONIterator *it, Dqn_String8 key) +bool Dqn_JSON_ItKeyIs(Dqn_JSONIt *it, Dqn_Str8 key) { - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); bool result = Dqn_JSON_String8Cmp(curr->name, key); return result; } -json_object_s *Dqn_JSON_IteratorKeyValueIsObj(Dqn_JSONIterator *it, Dqn_String8 key) +json_object_s *Dqn_JSON_ItKeyValueIsObj(Dqn_JSONIt *it, Dqn_Str8 key) { json_object_s *result = nullptr; - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); if (curr && Dqn_JSON_String8Cmp(curr->name, key)) result = json_value_as_object(curr->value); return result; } -json_array_s *Dqn_JSON_IteratorKeyValueIsArray(Dqn_JSONIterator *it, Dqn_String8 key) +json_array_s *Dqn_JSON_ItKeyValueIsArray(Dqn_JSONIt *it, Dqn_Str8 key) { json_array_s *result = nullptr; - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); if (curr && Dqn_JSON_String8Cmp(curr->name, key)) result = json_value_as_array(curr->value); return result; } -json_string_s *Dqn_JSON_IteratorKeyValueIsString(Dqn_JSONIterator *it, Dqn_String8 key) +json_string_s *Dqn_JSON_ItKeyValueIsString(Dqn_JSONIt *it, Dqn_Str8 key) { - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); json_string_s *result = nullptr; if (curr && Dqn_JSON_String8Cmp(curr->name, key)) result = json_value_as_string(curr->value); return result; } -json_number_s *Dqn_JSON_IteratorKeyValueIsNumber(Dqn_JSONIterator *it, Dqn_String8 key) +json_number_s *Dqn_JSON_ItKeyValueIsNumber(Dqn_JSONIt *it, Dqn_Str8 key) { - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); json_number_s *result = nullptr; if (curr && Dqn_JSON_String8Cmp(curr->name, key)) result = json_value_as_number(curr->value); return result; } -json_value_s *Dqn_JSON_IteratorKeyValueIsBool(Dqn_JSONIterator *it, Dqn_String8 key) +json_value_s *Dqn_JSON_ItKeyValueIsBool(Dqn_JSONIt *it, Dqn_Str8 key) { - json_object_element_s *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); json_value_s *result = nullptr; if (curr && Dqn_JSON_String8Cmp(curr->name, key)) result = curr->value->type == json_type_true || curr->value->type == json_type_false ? curr->value : nullptr; return result; } -// NOTE: Dqn_JSON_IteratorValueTo -// ----------------------------------------------------------------------------- -Dqn_String8 Dqn_JSON_IteratorValueToString(Dqn_JSONIterator *it) +json_value_s *Dqn_JSON_ItKeyValueIsNull(Dqn_JSONIt *it, Dqn_Str8 key) { - Dqn_String8 result = {}; - if (json_string_s *curr = Dqn_JSON_IteratorValueIsString(it)) - result = Dqn_String8_Init(curr->string, curr->string_size); + json_object_element_s *curr = Dqn_JSON_ItCurrObjElement(it); + json_value_s *result = nullptr; + if (curr && Dqn_JSON_String8Cmp(curr->name, key)) + result = curr->value->type == json_type_null ? curr->value : nullptr; return result; } -int64_t Dqn_JSON_IteratorValueToI64(Dqn_JSONIterator *it) + +// NOTE: Dqn_JSON_ItValueTo //////////////////////////////////////////////////////////////////////// +Dqn_Str8 Dqn_JSON_ItValueToString(Dqn_JSONIt *it) +{ + Dqn_Str8 result = {}; + if (json_string_s *curr = Dqn_JSON_ItValueIsString(it)) + result = Dqn_Str8_Init(curr->string, curr->string_size); + return result; +} + +int64_t Dqn_JSON_ItValueToI64(Dqn_JSONIt *it) { int64_t result = {}; - if (json_number_s *curr = Dqn_JSON_IteratorValueIsNumber(it)) - result = Dqn_String8_ToI64(Dqn_String8_Init(curr->number, curr->number_size), 0 /*separator*/); + if (json_number_s *curr = Dqn_JSON_ItValueIsNumber(it)) + result = Dqn_Str8_ToI64(Dqn_Str8_Init(curr->number, curr->number_size), 0 /*separator*/).value; return result; } -uint64_t Dqn_JSON_IteratorValueToU64(Dqn_JSONIterator *it) +uint64_t Dqn_JSON_ItValueToU64(Dqn_JSONIt *it) { uint64_t result = {}; - if (json_number_s *curr = Dqn_JSON_IteratorValueIsNumber(it)) - result = Dqn_String8_ToU64(Dqn_String8_Init(curr->number, curr->number_size), 0 /*separator*/); + if (json_number_s *curr = Dqn_JSON_ItValueIsNumber(it)) + result = Dqn_Str8_ToU64(Dqn_Str8_Init(curr->number, curr->number_size), 0 /*separator*/).value; return result; } -bool Dqn_JSON_IteratorValueToBool(Dqn_JSONIterator *it) +bool Dqn_JSON_ItValueToBool(Dqn_JSONIt *it) { bool result = {}; - if (json_value_s *curr = Dqn_JSON_IteratorValueIsBool(it)) + if (json_value_s *curr = Dqn_JSON_ItValueIsBool(it)) result = curr->type == json_type_true; return result; } -void Dqn_JSON_IteratorErrorUnknownKeyValue_(Dqn_JSONIterator *it, Dqn_CallSite call_site) +void Dqn_JSON_ItErrorUnknownKeyValue_(Dqn_JSONIt *it, Dqn_CallSite call_site) { if (!it) return; - json_object_element_s const *curr = Dqn_JSON_IteratorCurrObjElement(it); + json_object_element_s const *curr = Dqn_JSON_ItCurrObjElement(it); if (!curr) return; @@ -484,19 +502,18 @@ void Dqn_JSON_IteratorErrorUnknownKeyValue_(Dqn_JSONIterator *it, Dqn_CallSite c "Unknown key-value pair in object [loc=%zu:%zu, key=%.*s, value=%.*s]", info->line_no, info->row_no, - key->string_size, + DQN_CAST(int)key->string_size, key->string, - value_type_size, + DQN_CAST(int)value_type_size, value_type); } else { Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, call_site, "Unknown key-value pair in object [key=%.*s, value=%.*s]", - key->string_size, + DQN_CAST(int)key->string_size, key->string, - value_type_size, + DQN_CAST(int)value_type_size, value_type); } } - #endif // defined(DQN_JSON_IMPLEMENTATION) diff --git a/Misc/dqn_tests_helpers.cpp b/Misc/dqn_tests_helpers.cpp deleted file mode 100644 index 93f94f9..0000000 --- a/Misc/dqn_tests_helpers.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#if defined(DQN_KECCAK_H) -// ----------------------------------------------------------------------------- -// Dqn_Keccak Reference Implementation -// ----------------------------------------------------------------------------- -// A very compact Keccak implementation taken from the reference implementation -// repository -// -// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c -// - -#define FOR(i,n) for(i=0; i>1; } -#define ROL(a,o) ((((u64)a)<>(64-o))) -static u64 load64(const u8 *x) { ui i; u64 u=0; FOR(i,8) { u<<=8; u|=x[7-i]; } return u; } -static void store64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]=u; u>>=8; } } -static void xor64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]^=u; u>>=8; } } -#define rL(x,y) load64((u8*)s+8*(x+5*y)) -#define wL(x,y,l) store64((u8*)s+8*(x+5*y),l) -#define XL(x,y,l) xor64((u8*)s+8*(x+5*y),l) -void KeccakF1600(void *s) -{ - ui r,x,y,i,j,Y; u8 R=0x01; u64 C[5],D; - for(i=0; i<24; i++) { - /*θ*/ FOR(x,5) C[x]=rL(x,0)^rL(x,1)^rL(x,2)^rL(x,3)^rL(x,4); FOR(x,5) { D=C[(x+4)%5]^ROL(C[(x+1)%5],1); FOR(y,5) XL(x,y,D); } - /*ρπ*/ x=1; y=r=0; D=rL(x,y); FOR(j,24) { r+=j+1; Y=(2*x+3*y)%5; x=y; y=Y; C[0]=rL(x,y); wL(x,y,ROL(D,r%64)); D=C[0]; } - /*χ*/ FOR(y,5) { FOR(x,5) C[x]=rL(x,y); FOR(x,5) wL(x,y,C[x]^((~C[(x+1)%5])&C[(x+2)%5])); } - /*ι*/ FOR(j,7) if (LFSR86540(&R)) XL(0,0,(u64)1<<((1<0) { b=(inLen0) { b=(outLen0) KeccakF1600(s); } -} - - -// PCG32 Random Number Generator -// ----------------------------------------------------------------------------- -// NOTE: https://github.com/imneme/pcg-c-basic - -struct pcg_state_setseq_64 -{ // Internals are *Private*. - uint64_t state; // RNG state. All values are possible. - uint64_t inc; // Controls which RNG sequence (stream) is - // selected. Must *always* be odd. -}; -typedef struct pcg_state_setseq_64 pcg32_random_t; - -// pcg32_random_r(rng) -// Generate a uniformly distributed 32-bit random number - -uint32_t pcg32_random_r(pcg32_random_t* rng) -{ - uint64_t oldstate = rng->state; - rng->state = oldstate * 6364136223846793005ULL + rng->inc; - uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; - uint32_t rot = oldstate >> 59u; - return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); -} - -// pcg32_srandom_r(rng, initstate, initseq): -// Seed the rng. Specified in two parts, state initializer and a -// sequence selection constant (a.k.a. stream id) - -void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) -{ - rng->state = 0U; - rng->inc = (initseq << 1u) | 1u; - pcg32_random_r(rng); - rng->state += initstate; - pcg32_random_r(rng); -} - -// pcg32_boundedrand_r(rng, bound): -// Generate a uniformly distributed number, r, where 0 <= r < bound - -uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound) -{ - uint32_t threshold = -bound % bound; - for (;;) { - uint32_t r = pcg32_random_r(rng); - if (r >= threshold) - return r % bound; - } -} -#endif // DQN_KECCAK_H diff --git a/build.bat b/build.bat index 8f323ef..49253eb 100644 --- a/build.bat +++ b/build.bat @@ -13,7 +13,7 @@ pushd Build REM O2 Optimisation Level 2 REM Oi Use CPU Intrinsics REM Z7 Combine multi-debug files to one debug file - set common_flags=-D DQN_TEST_WITH_MAIN -I %script_dir% %script_dir%\Misc\dqn_unit_tests.cpp + set common_flags=-D DQN_TEST_WITH_MAIN -D DQN_IMPLEMENTATION -D DQN_USE_STD_PRINTF /Tp %script_dir%\dqn.h set msvc_driver_flags=%common_flags% -MT -EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo diff --git a/dqn.h b/dqn.h index deb26cd..57f6fd0 100644 --- a/dqn.h +++ b/dqn.h @@ -1,11 +1,28 @@ -// dqn.h "Personal standard library" | MIT licensed | git.doylet.dev/dqn +#if !defined(DQN_H) +#define DQN_H + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$\ $$\ $$\ +// $$ __$$\ $$ __$$\ $$$\ $$ | +// $$ | $$ |$$ / $$ |$$$$\ $$ | +// $$ | $$ |$$ | $$ |$$ $$\$$ | +// $$ | $$ |$$ | $$ |$$ \$$$$ | +// $$ | $$ |$$ $$\$$ |$$ |\$$$ | +// $$$$$$$ |\$$$$$$ / $$ | \$$ | +// \_______/ \___$$$\ \__| \__| +// \___| +// +// dqn.h -- Personal standard library -- MIT License -- git.doylet.dev/dqn +// +//////////////////////////////////////////////////////////////////////////////////////////////////// // // This library is a single-header file-esque library with inspiration taken // from STB libraries for ease of integration and use. It defines a bunch of // primitives and standard library functions that are missing and or more -// appropriate for development in modern day computing (e.g. cache friendly -// memory management, 64bit MMU, non-pessimized APIs that aren't constrained by -// the language specification and operate closer to the OS). +// appropriate for development in modern day computing (e.g. allocator +// first-class APIs, a 64bit MMU and in general non-pessimized APIs that aren't +// constrained by the language specification and operate closer to the OS). // // Define DQN_IMPLEMENTATION macro in one and only one translation unit to // enable the implementation of this library, for example: @@ -15,7 +32,7 @@ // // Additionally, this library supports including/excluding specific sections // of the library by using #define on the name of the section. These names are -// documented in the section table of contents at the #define column, for +// documented in the section table of contents at the #define column, for // example: // // #define DQN_ONLY_VARRAY @@ -33,18 +50,128 @@ // Below is a table of contents that describes what you can find in each header // of this library and additional macros that can be defined to customise the // behaviour of this library. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\ $$ __$$\\__$$ __|\_$$ _|$$ __$$\ $$$\ $$ |$$ __$$\ +// $$ / $$ |$$ | $$ | $$ | $$ | $$ / $$ |$$$$\ $$ |$$ / \__| +// $$ | $$ |$$$$$$$ | $$ | $$ | $$ | $$ |$$ $$\$$ |\$$$$$$\ +// $$ | $$ |$$ ____/ $$ | $$ | $$ | $$ |$$ \$$$$ | \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ |$$ |\$$$ |$$\ $$ | +// $$$$$$ |$$ | $$ | $$$$$$\ $$$$$$ |$$ | \$$ |\$$$$$$ | +// \______/ \__| \__| \______| \______/ \__| \__| \______/ +// +// Options -- Compile time build customisation +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// - Override these routines from the CRT by redefining them. By default we wrap +// the CRT functions from and , e.g: +// +// #define DQN_MEMCPY(dest, src, count) memcpy(dest, src, value) +// #define DQN_MEMSET(dest, value, count) memset(dest, value, count) +// #define DQN_MEMCMP(lhs, rhs, count) memcpy(lhs, rhs, count) +// #define DQN_MEMMOVE(dest, src, count) memmove(dest, src, count) +// #define DQN_SQRTF(val) sqrtf(val) +// #define DQN_SINF(val) sinf(val) +// #define DQN_COSF(val) cosf(val) +// #define DQN_TANF(val) tanf(val) +// +// - Redefine 'DQN_API' to change the prefix of all declared functions in the library +// +// #define DQN_API +// +// - Define 'DQN_STATIC_API' to apply 'static' to all function definitions and +// disable external linkage to other translation units by redefining 'DQN_API' to +// 'static'. +// +// #define DQN_STATIC_API +// +// - Turn all assertion macros to no-ops except for hard asserts (which are +// always enabled and represent unrecoverable errors in the library). +// +// #define DQN_NO_ASSERT +// +// - Augment DQN_CHECK(expr) macro's behaviour. By default when the check fails a +// debug break is emitted. If this macro is defined, the check will not trigger +// a debug break. +// +// #define DQN_NO_CHECK_BREAK +// +// - Define this macro to enable memory leak tracking on arena's that are +// configured to track allocations. +// +// Allocations are stored in a global hash-table and their respective stack +// traces for the allocation location. Memory leaks can be dumped at the end +// of the program or some epoch by calling Dqn_Library_DumpLeaks() +// +// #define DQN_LEAK_TRACKING +// +// - Define this to revert to the family of printf functions from +// instead of using stb_sprintf in this library. stb_sprintf is 5-6x faster +// than printf with a smaller binary footprint and has deterministic behaviour +// across all supported platforms. +// +// #define DQN_USE_STD_PRINTF +// +// However, if you are compiling with ASAN on MSVC, MSVC's implementation of +// __declspec(no_sanitize_address) is unable to suppress warnings in some +// individual functions in stb's implementation causing ASAN to trigger. This +// library will error on compilation if it detects this is the case and is +// being compiled with STB sprintf. +// +// - Define this to stop this library from defining a minimal subset of Win32 +// prototypes and definitions in this file. You should use this macro if you +// intend to #include yourself to avoid symbol conflicts with +// the redefined declarations in this library. +// +// #define DQN_NO_WIN32_MIN_HEADER +// +// - Define this to stop this library from defining STB_SPRINTF_IMPLEMENTATION. +// Useful if another library uses and includes "stb_sprintf.h" +// +// #define DQN_STB_SPRINTF_HEADER_ONLY +// +// - Override the default break into the active debugger function. By default +// we use __debugbreak() on Windows and raise(SIGTRAP) on other platforms. +// +// #define DQN_DEBUG_BREAK +// +// - Define this macro to 1 to enable poisoning of memory from arenas when ASAN +// `-fsanitize=address` is enabled. Enabling this will detect memory overwrite +// by padding allocated before and after with poisoned memory which will raise +// a use-after-poison in ASAN on read/write. This is a no-op if the library is +// not compiled with ASAN. +// +// #define DQN_ASAN_POISON 1 +// +// - Define this macro 1 to enable sanity checks for manually poisoned memory in +// this library when ASAN `-fsanitize=address` is enabled. These sanity checks +// ensure that memory from arenas are correctly un/poisoned when pointers are +// allocated and returned to the memory arena's. This is a no-op if we are not +// compiled with ASAN or `DQN_ASAN_POISON` is not set to `1`. +// +// #define DQN_ASAN_VET_POISON 1 +// +// - Define this macro to the size of the guard memory reserved before and after +// allocations made that are poisoned to protect against out-of-bounds memory +// accesses. By default the library sets the guard to 128 bytes. +// +// #define DQN_ASAN_POISON_GUARD_SIZE 128 +// -#if !defined(DQN_H) #if defined(DQN_ONLY_VARRAY) || \ defined(DQN_ONLY_SARRAY) || \ defined(DQN_ONLY_FARRAY) || \ - defined(DQN_ONLY_SLICE) || \ defined(DQN_ONLY_DSMAP) || \ defined(DQN_ONLY_LIST) || \ defined(DQN_ONLY_FSTR8) || \ defined(DQN_ONLY_FS) || \ defined(DQN_ONLY_WINNET) || \ defined(DQN_ONLY_WIN) || \ + defined(DQN_ONLY_SEMAPHORE) || \ + defined(DQN_ONLY_THREAD) || \ defined(DQN_ONLY_V2) || \ defined(DQN_ONLY_V3) || \ defined(DQN_ONLY_V4) || \ @@ -63,9 +190,6 @@ #if !defined(DQN_ONLY_SARRAY) #define DQN_NO_SARRAY #endif - #if !defined(DQN_ONLY_SLICE) - #define DQN_NO_SLICE - #endif #if !defined(DQN_ONLY_DSMAP) #define DQN_NO_DSMAP #endif @@ -84,6 +208,12 @@ #if !defined(DQN_ONLY_WIN) #define DQN_NO_WIN #endif + #if !defined(DQN_ONLY_SEMAPHORE) + #define DQN_NO_SEMAPHORE + #endif + #if !defined(DQN_ONLY_THREAD) + #define DQN_NO_THREAD + #endif #if !defined(DQN_ONLY_V2) #define DQN_NO_V2 #endif @@ -110,222 +240,65 @@ #endif #endif -// NOTE: Table of Contents ========================================================================= -// Index | #define Label | Description -// NOTE: C Headers ================================================================================= -#include // | | va_list -#include // | | fprintf, FILE, stdout, stderr -#include // | | [u]int_*, ... -#include // | | [U]INT_MAX, ... - -// NOTE: Dqn_Base ================================================================================== -// [$CMAC] Compiler macros | | Macros for the compiler -// [$MACR] Macros | | Define macros used in the library -// [$TYPE] Types | | Basic types and typedefs -// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex -// [$CALL] Dqn_CallSite | | Source code location/tracing -// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics -// [$ALLO] Dqn_Allocator | | Generic allocator interface -// [$PRIN] Dqn_Print | | Console printing -// [$LLOG] Dqn_Log | | Console logging macros - -// NOTE: Additional Configuration -// - Override the default heap-allocation routine that is called when the -// default Dqn_Allocator is used by #define-ing. By default we use the OS's -// virtual memory allocators (e.g. VirtualAlloc on Windows and mmap on Linux). -// -// DQN_ALLOC(size) -// DQN_DEALLOC(ptr, size) -// -// - Override the byte-manipulation routines by #define-ing. By default we use -// -// -// DQN_MEMCPY(dest, src, count) -// DQN_MEMSET(dest, value, count) -// DQN_MEMCMP(lhs, rhs, count) -// DQN_MEMMOVE(dest, src, count) -// -// - Override these math functions. By default we use -// -// DQN_SQRTF(val) -// DQN_SINF(val) -// DQN_COSF(val) -// DQN_TANF(val) -// -// - Change the prefix to all declared functions in the library by #define-ing. -// -// DQN_API -// -// - Apply static to all function definitions and disable external linkage to -// other translation units by #define-ing. This macro is only used if DQN_API -// is not overriden. -// -// DQN_STATIC_API -// -// - Turn all assertion macros to no-ops except for hard asserts (which are -// always enabled and represent unrecoverable errors in the library). -// -// DQN_NO_ASSERT -// -// - Augment DQN_CHECK(expr) macro's behaviour. By default it will trigger a -// debugger break when when the expression evalutes false otherwise by -// #define-ing this macro it will evaluate to false and DQN_CHECK is usually -// used in a if branch to recover gracefully from the failed condition. -// -// DQN_NO_CHECK_BREAK - #include "dqn_base.h" - -// NOTE: Dqn_External ============================================================================== -// [$OS_H] OS Headers | | Headers from the operating system -// [$STBS] stb_sprintf | | Portable sprintf #include "dqn_external.h" +#if defined(DQN_PLATFORM_WIN32) #include "dqn_win32.h" - -// NOTE: Additional Configuration -// - Define this to stop this library from defining a minimal subset of Win32 -// prototypes and definitions in this file. You should use this macro if you -// intend to #include yourself to avoid symbol conflicts with -// the redefined declarations in this library. -// -// DQN_NO_WIN32_MIN_HEADER -// -// - Define this to stop this library from defining STB_SPRINTF_IMPLEMENTATION. -// Useful if another library uses and includes "stb_sprintf.h" -// -// DQN_STB_SPRINTF_HEADER_ONLY - -// NOTE: Dqn_Memory ================================================================================ -// [$VMEM] Dqn_VMem | | Virtual memory allocation -// [$MEMB] Dqn_MemBlock | | Virtual memory blocks -// [$AREN] Dqn_Arena | | Growing bump allocator -// [$ACAT] Dqn_ArenaCatalog | | Collate, create & manage arenas in a catalog -#include "dqn_memory.h" - -// NOTE: Dqn_Debug ================================================================================= -// [$DEBM] Debug Macros | | -// [$ASAN] Dqn_Asan | | Helpers to manually poison memory using ASAN -// [$STKT] Dqn_StackTrace | | Create stack traces -// [$DEBG] Dqn_Debug | | Debugging tools/helpers +#endif +#include "dqn_allocator.h" +#include "dqn_thread_context.h" #include "dqn_debug.h" - -// NOTE: Dqn_Strings =============================================================================== -// [$CSTR] Dqn_CStr8 | | C-string helpers -// [$STR8] Dqn_Str8 | | Pointer and length strings -// [$FSTR] Dqn_FStr8 | DQN_FSTr8 | Fixed-size strings -// [$STRB] Dqn_Str8Builder | | -// [$CHAR] Dqn_Char | | Character ascii/digit.. helpers -// [$UTFX] Dqn_UTF | | Unicode helpers -#include "dqn_strings.h" - -// NOTE: Dqn_Containers ============================================================================ -// [$CARR] Dqn_CArray | | Basic operations on C arrays for VArray/SArray/FArray to reuse -// [$VARR] Dqn_VArray | DQN_VARRAY | Array backed by virtual memory arena -// [$SARR] Dqn_SArray | DQN_SARRAY | Array that are allocated but cannot resize -// [$FARR] Dqn_FArray | DQN_FARRAY | Fixed-size arrays -// [$SLIC] Dqn_Slice | DQN_SLICE | Pointe and length container of data -// [$DMAP] Dqn_DSMap | DQN_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair -// [$LIST] Dqn_List | DQN_LIST | Chunked linked lists, append only +#include "dqn_string.h" #include "dqn_containers.h" - -// NOTE: Additional Configuration -// - Override the default break into the active debugger function. By default -// we use __debugbreak() on Windows and raise(SIGTRAP) on other platforms. -// -// DQN_DEBUG_BREAK -// -// - Change the byte that DQN_MEMSET will clear memory with. By default this -// is set to 0. Some of the API's in this library accept a Dqn_ZeroMem enum -// which scrubs memory with this #define-d value. -// -// DQN_MEMSET_BYTE -// -// - Define this macro to enable emory leak tracking when requesting memory -// from the OS via this library. For example calls to Dqn_VMem_Reserve or -// DQN_ALLOC are recorded to the leak table. -// -// Allocations are stored in a global hash-table and their respective stack -// traces for the allocation location. Memory leaks can be dumped at the end -// of the program or some epoch by calling Dqn_Library_DumpLeaks() -// -// You may mark sections of your program as allowed to leak memory by setting -// the arena's or Dqn_Library's runtime struct `allocs_are_allowed_to_leak` -// flag. -// -// DQN_LEAK_TRACING -// -// - Define this macro to 1 to enable poisoning of memory from arenas when ASAN -// `-fsanitize=address` is enabled. Enabling this will detect memory overwrite -// by padding allocated before and after with poisoned memory which will raise -// a use-after-poison in ASAN on read/write. This is a no-op if the library is -// not compiled with ASAN. -// -// DQN_ASAN_POISON 1 -// -// - Define this macro 1 to enable sanity checks for manually poisoned memory in -// this library when ASAN `-fsanitize=address` is enabled. These sanity checks -// ensure that memory from arenas are correctly un/poisoned when pointers are -// allocated and returned to the memory arena's. This is a no-op if we are not -// compiled with ASAN or `DQN_ASAN_POISON` is not set to `1`. -// -// DQN_ASAN_VET_POISON 1 -// -// - Define this macro to the size of the guard memory reserved before and after -// allocations made that are poisoned to protect against out-of-bounds memory -// accesses. By default the library sets the guard to 128 bytes. -// -// DQN_ASAN_POISON_GUARD_SIZE 128 - -// NOTE: Dqn_Platform ============================================================================== -// [$FSYS] Dqn_Fs | DQN_FS | Filesystem helpers -// [$DATE] Dqn_Date | | Date-time helpers -// [$WIND] Dqn_Win | | Windows OS helpers -// [$WINN] Dqn_WinNet | DQN_WINNET | Windows internet download/query helpers -// [$OSYS] Dqn_OS | DQN_WIN | Operating-system APIs -// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas -#include "dqn_platform.h" - -// NOTE: Dqn_OS ==================================================================================== -// [$EXEC] Dqn_OSExec | | Execute programs programatically +#if defined(DQN_PLATFORM_EMSCRIPTEN) || defined(DQN_PLATFORM_POSIX) || defined(DQN_PLATFORM_ARM64) +#elif defined(DQN_PLATFORM_WIN32) + #include "dqn_os_win32.h" +#else + #error Please define a platform e.g. 'DQN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif #include "dqn_os.h" - -// NOTE: Dqn_Math ================================================================================== -// [$VEC2] Dqn_V2, V2i | DQN_V2 | -// [$VEC3] Dqn_V3, V3i | DQN_V3 | -// [$VEC4] Dqn_V4, V4i | DQN_V4 | -// [$MAT4] Dqn_M4 | DQN_M4 | -// [$RECT] Dqn_Rect | DQN_RECT | -// [$MATH] Other | | #include "dqn_math.h" - -// NOTE: Dqn_Hash ================================================================================== -// [$FNV1] Dqn_FNV1A | | Hash(x) -> 32/64bit via FNV1a -// [$MMUR] Dqn_MurmurHash3 | | Hash(x) -> 32/128bit via MurmurHash3 #include "dqn_hash.h" - -// NOTE: Dqn_Helpers =============================================================================== -// [$PCG3] Dqn_PCG32 | | RNG from the PCG family -// [$JSON] Dqn_JSONBuilder | DQN_JSON_BUILDER | Construct json output -// [$BHEX] Dqn_Bin | DQN_BIN | Binary <-> hex helpers -// [$BSEA] Dqn_BinarySearch | | Binary search -// [$BITS] Dqn_Bit | | Bitset manipulation -// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts -// [$MISC] Misc | | Uncategorised helper functions -// [$DLIB] Dqn_Library | | Globally shared runtime data for this library -// [$PROF] Dqn_Profiler | DQN_PROFILER | Profiler that measures using a timestamp counter #include "dqn_helpers.h" +#include "dqn_type_info.h" #endif // DQN_H #if defined(DQN_IMPLEMENTATION) +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// /$$$$$$\ $$\ $$\ $$$$$$$\ $$\ +// \_$$ _|$$$\ $$$ |$$ __$$\ $$ | +// $$ | $$$$\ $$$$ |$$ | $$ |$$ | +// $$ | $$\$$\$$ $$ |$$$$$$$ |$$ | +// $$ | $$ \$$$ $$ |$$ ____/ $$ | +// $$ | $$ |\$ /$$ |$$ | $$ | +// $$$$$$\ $$ | \_/ $$ |$$ | $$$$$$$$\ +// \______|\__| \__|\__| \________| +// +// Implementation +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + #include "dqn_base.cpp" +#include "dqn_thread_context.cpp" #include "dqn_external.cpp" -#include "dqn_memory.cpp" +#include "dqn_allocator.cpp" #include "dqn_debug.cpp" -#include "dqn_strings.cpp" +#include "dqn_string.cpp" #include "dqn_containers.cpp" -#include "dqn_platform.cpp" + +#if defined(DQN_PLATFORM_EMSCRIPTEN) || defined(DQN_PLATFORM_POSIX) || defined(DQN_PLATFORM_ARM64) + #include "dqn_os_posix.cpp" +#elif defined(DQN_PLATFORM_WIN32) + #include "dqn_os_win32.cpp" +#else + #error Please define a platform e.g. 'DQN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif + #include "dqn_os.cpp" #include "dqn_math.cpp" #include "dqn_hash.cpp" #include "dqn_helpers.cpp" +#include "dqn_unit_tests.cpp" +#include "dqn_docs.cpp" #endif // DQN_IMPLEMENTATION diff --git a/dqn.rdbg b/dqn.rdbg deleted file mode 100644 index 59baf9f..0000000 Binary files a/dqn.rdbg and /dev/null differ diff --git a/dqn_allocator.cpp b/dqn_allocator.cpp new file mode 100644 index 0000000..8733bdd --- /dev/null +++ b/dqn_allocator.cpp @@ -0,0 +1,497 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ +// $$ __$$\ $$ | $$ | $$ __$$\ $$ __$$\ $$ __$$\\__$$ __|$$ __$$\ $$ __$$\ +// $$ / $$ |$$ | $$ | $$ / $$ |$$ / \__|$$ / $$ | $$ | $$ / $$ |$$ | $$ | +// $$$$$$$$ |$$ | $$ | $$ | $$ |$$ | $$$$$$$$ | $$ | $$ | $$ |$$$$$$$ | +// $$ __$$ |$$ | $$ | $$ | $$ |$$ | $$ __$$ | $$ | $$ | $$ |$$ __$$< +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$\ $$ | $$ | $$ | $$ | $$ |$$ | $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$$$$$ |\$$$$$$ |$$ | $$ | $$ | $$$$$$ |$$ | $$ | +// \__| \__|\________|\________|\______/ \______/ \__| \__| \__| \______/ \__| \__| +// +// dqn_allocator.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$AREN] Dqn_Arena ///////////////////////////////////////////////////////////////////////// +DQN_API Dqn_ArenaBlock *Dqn_Arena_BlockInit(uint64_t reserve, uint64_t commit, bool track_alloc, bool alloc_can_leak) +{ + Dqn_usize const page_size = g_dqn_library->os_page_size; + uint64_t real_reserve = reserve ? reserve : DQN_ARENA_RESERVE_SIZE; + uint64_t real_commit = commit ? commit : DQN_ARENA_COMMIT_SIZE; + real_reserve = Dqn_AlignUpPowerOfTwo(real_reserve, page_size); + real_commit = DQN_MIN(Dqn_AlignUpPowerOfTwo(real_commit, page_size), real_reserve); + + DQN_ASSERTF(DQN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DQN_ARENA_HEADER_SIZE, real_commit, real_reserve); + DQN_ASSERTF(page_size, "Call Dqn_Library_Init() to initialise the known page size"); + + Dqn_OSMemCommit mem_commit = real_reserve == real_commit ? Dqn_OSMemCommit_Yes : Dqn_OSMemCommit_No; + Dqn_ArenaBlock *result = DQN_CAST(Dqn_ArenaBlock *)Dqn_OS_MemReserve(real_reserve, mem_commit, Dqn_OSMemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == Dqn_OSMemCommit_No && !Dqn_OS_MemCommit(result, real_commit, Dqn_OSMemPage_ReadWrite)) { + Dqn_OS_MemRelease(result, real_reserve); + return result; + } + + result->used = DQN_ARENA_HEADER_SIZE; + result->commit = real_commit; + result->reserve = real_reserve; + + if (track_alloc) + Dqn_Debug_TrackAlloc(result, result->reserve, alloc_can_leak); + return result; +} + +DQN_API Dqn_ArenaBlock *Dqn_Arena_BlockInitFlags(uint64_t reserve, uint64_t commit, uint8_t arena_flags) +{ + bool track_alloc = (arena_flags & Dqn_ArenaFlag_NoAllocTrack) == 0; + bool alloc_can_leak = arena_flags & Dqn_ArenaFlag_AllocCanLeak; + Dqn_ArenaBlock *result = Dqn_Arena_BlockInit(reserve, commit, track_alloc, alloc_can_leak); + if (result && ((arena_flags & Dqn_ArenaFlag_NoPoison) == 0)) + Dqn_ASAN_PoisonMemoryRegion(DQN_CAST(char *)result + DQN_ARENA_HEADER_SIZE, result->commit - DQN_ARENA_HEADER_SIZE); + return result; +} + +DQN_API Dqn_Arena Dqn_Arena_InitSize(uint64_t reserve, uint64_t commit, uint8_t flags) +{ + Dqn_Arena result = {}; + result.flags = flags; + result.curr = Dqn_Arena_BlockInitFlags(reserve, commit, flags); + return result; +} + +static void Dqn_Arena_BlockDeinit_(Dqn_Arena const *arena, Dqn_ArenaBlock *block) +{ + Dqn_usize release_size = block->reserve; + if (Dqn_Bit_IsNotSet(arena->flags, Dqn_ArenaFlag_NoAllocTrack)) + Dqn_Debug_TrackDealloc(block); + Dqn_ASAN_UnpoisonMemoryRegion(block, block->commit); + Dqn_OS_MemRelease(block, release_size); +} + +DQN_API void Dqn_Arena_Deinit(Dqn_Arena *arena) +{ + for (Dqn_ArenaBlock *block = arena ? arena->curr : nullptr; block; ) { + Dqn_ArenaBlock *block_to_free = block; + block = block->prev; + Dqn_Arena_BlockDeinit_(arena, block_to_free); + } +} + +DQN_API bool Dqn_Arena_CommitTo(Dqn_Arena *arena, uint64_t pos) +{ + if (!arena || !arena->curr) + return false; + + Dqn_ArenaBlock *curr = arena->curr; + if (pos <= curr->commit) + return true; + + uint64_t real_pos = pos; + if (!DQN_CHECK(pos <= curr->reserve)) + real_pos = curr->reserve; + + Dqn_usize end_commit = Dqn_AlignUpPowerOfTwo(real_pos, g_dqn_library->os_page_size); + Dqn_usize commit_size = end_commit - curr->commit; + char *commit_ptr = DQN_CAST(char *) curr + curr->commit; + if (!Dqn_OS_MemCommit(commit_ptr, commit_size, Dqn_OSMemPage_ReadWrite)) + return false; + + bool poison = DQN_ASAN_POISON && ((arena->flags & Dqn_ArenaFlag_NoPoison) == 0); + if (poison) + Dqn_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + + curr->commit = end_commit; + return true; +} + +DQN_API bool Dqn_Arena_Commit(Dqn_Arena *arena, uint64_t size) +{ + if (!arena || !arena->curr) + return false; + uint64_t pos = arena->curr->commit + size; + bool result = Dqn_Arena_CommitTo(arena, pos); + return result; +} + +DQN_API void *Dqn_Arena_Alloc(Dqn_Arena *arena, uint64_t size, uint8_t align, Dqn_ZeroMem zero_mem) +{ + if (!arena) + return nullptr; + + if (!arena->curr) + arena->curr = Dqn_Arena_BlockInitFlags(DQN_ARENA_RESERVE_SIZE, DQN_ARENA_COMMIT_SIZE, arena->flags); + + if (!arena->curr) + return nullptr; + + try_alloc_again: + Dqn_ArenaBlock *curr = arena->curr; + bool poison = DQN_ASAN_POISON && ((arena->flags & Dqn_ArenaFlag_NoPoison) == 0); + uint8_t real_align = poison ? DQN_MAX(align, DQN_ASAN_POISON_ALIGNMENT) : align; + uint64_t offset_pos = Dqn_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DQN_ASAN_POISON_GUARD_SIZE : 0); + uint64_t end_pos = offset_pos + size; + + if (end_pos > curr->reserve) { + if (arena->flags & Dqn_ArenaFlag_NoGrow) + return nullptr; + Dqn_usize new_reserve = DQN_MAX(DQN_ARENA_HEADER_SIZE + size, DQN_ARENA_RESERVE_SIZE); + Dqn_usize new_commit = DQN_MAX(DQN_ARENA_HEADER_SIZE + size, DQN_ARENA_COMMIT_SIZE); + Dqn_ArenaBlock *new_block = Dqn_Arena_BlockInitFlags(new_reserve, new_commit, arena->flags); + if (!new_block) + return nullptr; + new_block->prev = arena->curr; + arena->curr = new_block; + new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; + goto try_alloc_again; + } + + Dqn_usize prev_arena_commit = curr->commit; + if (end_pos > curr->commit) { + Dqn_usize end_commit = Dqn_AlignUpPowerOfTwo(end_pos, g_dqn_library->os_page_size); + Dqn_usize commit_size = end_commit - curr->commit; + char *commit_ptr = DQN_CAST(char *)curr + curr->commit; + if (!Dqn_OS_MemCommit(commit_ptr, commit_size, Dqn_OSMemPage_ReadWrite)) + return nullptr; + if (poison) + Dqn_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + curr->commit = end_commit; + } + + void *result = DQN_CAST(char *) curr + offset_pos; + curr->used = end_pos; + Dqn_ASAN_UnpoisonMemoryRegion(result, size); + + if (zero_mem == Dqn_ZeroMem_Yes) { + Dqn_usize reused_bytes = DQN_MIN(prev_arena_commit - offset_pos, size); + DQN_MEMSET(result, 0, reused_bytes); + } + + return result; +} + +DQN_API void *Dqn_Arena_AllocContiguous(Dqn_Arena *arena, uint64_t size, uint8_t align, Dqn_ZeroMem zero_mem) +{ + uint8_t prev_flags = arena->flags; + arena->flags |= (Dqn_ArenaFlag_NoGrow | Dqn_ArenaFlag_NoPoison); + void *memory = Dqn_Arena_Alloc(arena, size, align, zero_mem); + arena->flags = prev_flags; + return memory; +} + +DQN_API void *Dqn_Arena_Copy(Dqn_Arena *arena, void const *data, uint64_t size, uint8_t align) +{ + if (!arena || !data || size == 0) + return nullptr; + void *result = Dqn_Arena_Alloc(arena, size, align, Dqn_ZeroMem_No); + if (result) + DQN_MEMCPY(result, data, size); + return result; +} + +DQN_API void Dqn_Arena_PopTo(Dqn_Arena *arena, uint64_t init_used) +{ + if (!arena) + return; + uint64_t used = DQN_MAX(DQN_ARENA_HEADER_SIZE, init_used); + Dqn_ArenaBlock *curr = arena->curr; + while (curr->reserve_sum >= used) { + Dqn_ArenaBlock *block_to_free = curr; + curr = curr->prev; + Dqn_Arena_BlockDeinit_(arena, block_to_free); + } + + arena->curr = curr; + curr->used = used - curr->reserve_sum; + char *poison_ptr = (char *)curr + Dqn_AlignUpPowerOfTwo(curr->used, DQN_ASAN_POISON_ALIGNMENT); + Dqn_usize poison_size = ((char *)curr + curr->commit) - poison_ptr; + Dqn_ASAN_PoisonMemoryRegion(poison_ptr, poison_size); +} + +DQN_API void Dqn_Arena_Pop(Dqn_Arena *arena, uint64_t amount) +{ + Dqn_ArenaBlock *curr = arena->curr; + Dqn_usize used_sum = curr->reserve_sum + curr->used; + if (!DQN_CHECK(amount <= used_sum)) + amount = used_sum; + Dqn_usize pop_to = used_sum - amount; + Dqn_Arena_PopTo(arena, pop_to); +} + +DQN_API void Dqn_Arena_Clear(Dqn_Arena *arena) +{ + Dqn_Arena_PopTo(arena, 0); +} + +DQN_API Dqn_ArenaTempMem Dqn_Arena_TempMemBegin(Dqn_Arena *arena) +{ + Dqn_ArenaTempMem result = {}; + if (arena) { + Dqn_ArenaBlock *curr = arena->curr; + result = {arena, curr ? curr->reserve_sum + curr->used : 0}; + } + return result; +}; + +DQN_API void Dqn_Arena_TempMemEnd(Dqn_ArenaTempMem mem) +{ + Dqn_Arena_PopTo(mem.arena, mem.used_sum); +}; + +Dqn_ArenaTempMemScope::Dqn_ArenaTempMemScope(Dqn_Arena *arena) +{ + mem = Dqn_Arena_TempMemBegin(arena); +} + +Dqn_ArenaTempMemScope::~Dqn_ArenaTempMemScope() +{ + Dqn_Arena_TempMemEnd(mem); +} + +// NOTE: [$CHUN] Dqn_ChunkPool ///////////////////////////////////////////////////////////////////// +DQN_API Dqn_ChunkPool Dqn_ChunkPool_Init(Dqn_Arena *arena, uint8_t align) +{ + Dqn_ChunkPool result = {}; + if (arena) { + result.arena = arena; + result.align = align; + if (result.align == 0) + result.align = DQN_CHUNK_POOL_DEFAULT_ALIGN; + } + return result; +} + +DQN_API bool Dqn_ChunkPool_IsValid(Dqn_ChunkPool const *pool) +{ + bool result = pool && pool->arena && pool->align; + return result; +} + +DQN_API void *Dqn_ChunkPool_Alloc(Dqn_ChunkPool *pool, Dqn_usize size) +{ + void *result = nullptr; + if (!Dqn_ChunkPool_IsValid(pool)) + return result; + + Dqn_usize const required_size = sizeof(Dqn_ChunkPoolSlot) + pool->align + size; + Dqn_usize const size_to_slot_offset = 5; // __lzcnt64(32) e.g. Dqn_ChunkPoolSlotSize_32B + Dqn_usize slot_index = 0; + if (required_size > 32) { + #if defined(DQN_OS_WIN32) + Dqn_usize dist_to_next_msb = __lzcnt64(required_size) + 1; + #else + Dqn_usize dist_to_next_msb = __builtin_clz(required_size) + 1; + #endif + + // NOTE: Round up if not PoT as the low bits are set. + dist_to_next_msb -= DQN_CAST(Dqn_usize)(!Dqn_IsPowerOfTwo(required_size)); + + Dqn_usize const register_size = sizeof(Dqn_usize) * 8; + DQN_ASSERT(register_size >= dist_to_next_msb + size_to_slot_offset); + slot_index = register_size - dist_to_next_msb - size_to_slot_offset; + } + + if (!DQN_CHECKF(slot_index < Dqn_ChunkPoolSlotSize_Count, "Chunk pool does not support the requested allocation size")) + return result; + + Dqn_usize slot_size_in_bytes = 1ULL << (slot_index + size_to_slot_offset); + DQN_ASSERT(required_size <= (slot_size_in_bytes << 0)); + DQN_ASSERT(required_size >= (slot_size_in_bytes >> 1)); + + Dqn_ChunkPoolSlot *slot = nullptr; + if (pool->slots[slot_index]) { + slot = pool->slots[slot_index]; + pool->slots[slot_index] = slot->next; + DQN_MEMSET(slot->data, 0, size); + DQN_ASSERT(Dqn_IsPowerOfTwoAligned(slot->data, pool->align)); + } else { + void *bytes = Dqn_Arena_Alloc(pool->arena, slot_size_in_bytes, alignof(Dqn_ChunkPoolSlot), Dqn_ZeroMem_Yes); + slot = DQN_CAST(Dqn_ChunkPoolSlot *) bytes; + + // NOTE: The raw pointer is round up to the next 'pool->align'-ed + // address ensuring at least 1 byte of padding between the raw pointer + // and the pointer given to the user and that the user pointer is + // aligned to the pool's alignment. + // + // This allows us to smuggle 1 byte behind the user pointer that has + // the offset to the original pointer. + slot->data = DQN_CAST(void *)Dqn_AlignDownPowerOfTwo(DQN_CAST(uintptr_t)slot + sizeof(Dqn_ChunkPoolSlot) + pool->align, pool->align); + + uintptr_t offset_to_original_ptr = DQN_CAST(uintptr_t)slot->data - DQN_CAST(uintptr_t)bytes; + DQN_ASSERT(slot->data > bytes); + DQN_ASSERT(offset_to_original_ptr <= sizeof(Dqn_ChunkPoolSlot) + pool->align); + + // NOTE: Store the offset to the original pointer behind the user's + // pointer. + DQN_MEMCPY(&(DQN_CAST(char *)slot->data)[-1], &offset_to_original_ptr, 1); + } + + // NOTE: Smuggle the slot type in the next pointer so that we know, when the + // pointer gets returned which free list to return the pointer to. + result = slot->data; + slot->next = DQN_CAST(Dqn_ChunkPoolSlot *)slot_index; + return result; +} + +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8FV(Dqn_ChunkPool *pool, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Str8 result = {}; + if (!Dqn_ChunkPool_IsValid(pool)) + return result; + + Dqn_usize size_required = Dqn_CStr8_FVSize(fmt, args); + result.data = DQN_CAST(char *) Dqn_ChunkPool_Alloc(pool, size_required + 1); + if (result.data) { + result.size = size_required; + DQN_VSNPRINTF(result.data, DQN_CAST(int)(result.size + 1), fmt, args); + } + return result; +} + +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8F(Dqn_ChunkPool *pool, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Str8 result = Dqn_ChunkPool_AllocStr8FV(pool, fmt, args); + va_end(args); + return result; +} + +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8Copy(Dqn_ChunkPool *pool, Dqn_Str8 string) +{ + Dqn_Str8 result = {}; + if (!Dqn_ChunkPool_IsValid(pool)) + return result; + + if (!Dqn_Str8_HasData(string)) + return result; + + char *data = DQN_CAST(char *)Dqn_ChunkPool_Alloc(pool, string.size + 1); + if (!data) + return result; + + DQN_MEMCPY(data, string.data, string.size); + data[string.size] = 0; + result = Dqn_Str8_Init(data, string.size); + return result; +} + +DQN_API void Dqn_ChunkPool_Dealloc(Dqn_ChunkPool *pool, void *ptr) +{ + if (!Dqn_ChunkPool_IsValid(pool)) + return; + + Dqn_usize offset_to_original_ptr = 0; + DQN_MEMCPY(&offset_to_original_ptr, &(DQN_CAST(char *)ptr)[-1], 1); + DQN_ASSERT(offset_to_original_ptr <= sizeof(Dqn_ChunkPoolSlot) + pool->align); + + char *original_ptr = DQN_CAST(char *)ptr - offset_to_original_ptr; + Dqn_ChunkPoolSlot *slot = DQN_CAST(Dqn_ChunkPoolSlot *)original_ptr; + Dqn_ChunkPoolSlotSize slot_index = DQN_CAST(Dqn_ChunkPoolSlotSize)(DQN_CAST(uintptr_t)slot->next); + DQN_ASSERT(slot_index < Dqn_ChunkPoolSlotSize_Count); + + slot->next = pool->slots[slot_index]; + pool->slots[slot_index] = slot; +} + + +// NOTE: [$ACAT] Dqn_ArenaCatalog ////////////////////////////////////////////////////////////////// +DQN_API void Dqn_ArenaCatalog_Init(Dqn_ArenaCatalog *catalog, Dqn_ChunkPool *pool) +{ + catalog->pool = pool; + catalog->sentinel.next = &catalog->sentinel; + catalog->sentinel.prev = &catalog->sentinel; +} + +DQN_API Dqn_ArenaCatalogItem *Dqn_ArenaCatalog_Find(Dqn_ArenaCatalog *catalog, Dqn_Str8 label) +{ + Dqn_ArenaCatalogItem *result = &catalog->sentinel; + for (Dqn_ArenaCatalogItem *item = catalog->sentinel.next; item != &catalog->sentinel; item = item->next) { + if (item->label == label) { + result = item; + break; + } + } + return result; +} + +DQN_API void Dqn_ArenaCatalog_AddLabelRef(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, Dqn_Str8 label) +{ + // 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_ChunkPool_New(catalog->pool, Dqn_ArenaCatalogItem); + if (result) { + result->arena = arena; + result->label = label; + + // 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_Atomic_AddU32(&catalog->arena_count, 1); + } + Dqn_TicketMutex_End(&catalog->ticket_mutex); +} + +DQN_API void Dqn_ArenaCatalog_AddLabelCopy(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, Dqn_Str8 label) +{ + Dqn_Str8 label_copy = Dqn_ChunkPool_AllocStr8Copy(catalog->pool, label); + Dqn_ArenaCatalog_AddLabelRef(catalog, arena, label_copy); +} + +DQN_API void Dqn_ArenaCatalog_AddF(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Str8 label = Dqn_ChunkPool_AllocStr8FV(catalog->pool, fmt, args); + va_end(args); + Dqn_ArenaCatalog_AddLabelRef(catalog, arena, label); +} + +DQN_API void Dqn_ArenaCatalog_AddFV(Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Str8 label = Dqn_ChunkPool_AllocStr8FV(catalog->pool, fmt, args); + Dqn_ArenaCatalog_AddLabelRef(catalog, arena, label); +} + +DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocLabelRef(Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, Dqn_Str8 label) +{ + Dqn_TicketMutex_Begin(&catalog->ticket_mutex); + Dqn_Arena *result = Dqn_ChunkPool_New(catalog->pool, Dqn_Arena); + Dqn_TicketMutex_End(&catalog->ticket_mutex); + + *result = Dqn_Arena_InitSize(reserve, commit, arena_flags); + Dqn_ArenaCatalog_AddLabelRef(catalog, result, label); + return result; +} + +DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocLabelCopy(Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, Dqn_Str8 label) +{ + Dqn_Str8 label_copy = Dqn_ChunkPool_AllocStr8Copy(catalog->pool, label); + Dqn_Arena *result = Dqn_ArenaCatalog_AllocLabelRef(catalog, reserve, commit, arena_flags, label_copy); + return result; +} + +DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocFV(Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Str8 label = Dqn_ChunkPool_AllocStr8FV(catalog->pool, fmt, args); + Dqn_Arena *result = Dqn_ArenaCatalog_AllocLabelRef(catalog, reserve, commit, arena_flags, label); + return result; +} + +DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF(Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Str8 label = Dqn_ChunkPool_AllocStr8FV(catalog->pool, fmt, args); + Dqn_Arena *result = Dqn_ArenaCatalog_AllocLabelRef(catalog, reserve, commit, arena_flags, label); + va_end(args); + return result; +} diff --git a/dqn_allocator.h b/dqn_allocator.h new file mode 100644 index 0000000..4453186 --- /dev/null +++ b/dqn_allocator.h @@ -0,0 +1,176 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ +// $$ __$$\ $$ | $$ | $$ __$$\ $$ __$$\ $$ __$$\\__$$ __|$$ __$$\ $$ __$$\ +// $$ / $$ |$$ | $$ | $$ / $$ |$$ / \__|$$ / $$ | $$ | $$ / $$ |$$ | $$ | +// $$$$$$$$ |$$ | $$ | $$ | $$ |$$ | $$$$$$$$ | $$ | $$ | $$ |$$$$$$$ | +// $$ __$$ |$$ | $$ | $$ | $$ |$$ | $$ __$$ | $$ | $$ | $$ |$$ __$$< +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$\ $$ | $$ | $$ | $$ | $$ |$$ | $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$$$$$ |\$$$$$$ |$$ | $$ | $$ | $$$$$$ |$$ | $$ | +// \__| \__|\________|\________|\______/ \______/ \__| \__| \__| \______/ \__| \__| +// +// dqn_allocator.h -- Custom memory allocators +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$AREN] Dqn_Arena -- Growing bump allocator +// [$CHUN] Dqn_ChunkPool -- Allocates reusable, free-able memory in PoT chunks +// [$ACAT] Dqn_ArenaCatalog -- Collate, create & manage arenas in a catalog +// +// NOTE: [$AREN] Dqn_Arena ///////////////////////////////////////////////////////////////////////// +#if !defined(DQN_ARENA_RESERVE_SIZE) + #define DQN_ARENA_RESERVE_SIZE DQN_MEGABYTES(64) +#endif +#if !defined(DQN_ARENA_COMMIT_SIZE) + #define DQN_ARENA_COMMIT_SIZE DQN_KILOBYTES(64) +#endif + +struct Dqn_ArenaBlock +{ + Dqn_ArenaBlock *prev; + uint64_t used; + uint64_t commit; + uint64_t reserve; + uint64_t reserve_sum; +}; + +enum Dqn_ArenaFlag +{ + Dqn_ArenaFlag_Nil = 0, + Dqn_ArenaFlag_NoGrow = 1 << 0, + Dqn_ArenaFlag_NoPoison = 1 << 1, + Dqn_ArenaFlag_NoAllocTrack = 1 << 2, + Dqn_ArenaFlag_AllocCanLeak = 1 << 3, +}; + +struct Dqn_Arena +{ + Dqn_ArenaBlock *curr; + uint8_t flags; +}; + +struct Dqn_ArenaTempMem +{ + Dqn_Arena *arena; + uint64_t used_sum; +}; + +struct Dqn_ArenaTempMemScope +{ + Dqn_ArenaTempMemScope(Dqn_Arena *arena); + ~Dqn_ArenaTempMemScope(); + Dqn_ArenaTempMem mem; +}; + +Dqn_usize const DQN_ARENA_HEADER_SIZE = Dqn_AlignUpPowerOfTwo(sizeof(Dqn_Arena), 64); + +// NOTE: [$CHUN] Dqn_ChunkPool ///////////////////////////////////////////////////////////////////// +#if !defined(DQN_CHUNK_POOL_DEFAULT_ALIGN) + #define DQN_CHUNK_POOL_DEFAULT_ALIGN 16 +#endif + +struct Dqn_ChunkPoolSlot +{ + void *data; + Dqn_ChunkPoolSlot *next; +}; + +enum Dqn_ChunkPoolSlotSize +{ + Dqn_ChunkPoolSlotSize_32B, + Dqn_ChunkPoolSlotSize_64B, + Dqn_ChunkPoolSlotSize_128B, + Dqn_ChunkPoolSlotSize_256B, + Dqn_ChunkPoolSlotSize_512B, + Dqn_ChunkPoolSlotSize_1KiB, + Dqn_ChunkPoolSlotSize_2KiB, + Dqn_ChunkPoolSlotSize_4KiB, + Dqn_ChunkPoolSlotSize_8KiB, + Dqn_ChunkPoolSlotSize_16KiB, + Dqn_ChunkPoolSlotSize_32KiB, + Dqn_ChunkPoolSlotSize_64KiB, + Dqn_ChunkPoolSlotSize_128KiB, + Dqn_ChunkPoolSlotSize_256KiB, + Dqn_ChunkPoolSlotSize_512KiB, + Dqn_ChunkPoolSlotSize_1MiB, + Dqn_ChunkPoolSlotSize_2MiB, + Dqn_ChunkPoolSlotSize_4MiB, + Dqn_ChunkPoolSlotSize_8MiB, + Dqn_ChunkPoolSlotSize_16MiB, + Dqn_ChunkPoolSlotSize_32MiB, + Dqn_ChunkPoolSlotSize_64MiB, + Dqn_ChunkPoolSlotSize_128MiB, + Dqn_ChunkPoolSlotSize_256MiB, + Dqn_ChunkPoolSlotSize_512MiB, + Dqn_ChunkPoolSlotSize_1GiB, + Dqn_ChunkPoolSlotSize_2GiB, + Dqn_ChunkPoolSlotSize_4GiB, + Dqn_ChunkPoolSlotSize_8GiB, + Dqn_ChunkPoolSlotSize_16GiB, + Dqn_ChunkPoolSlotSize_32GiB, + Dqn_ChunkPoolSlotSize_Count, +}; + +struct Dqn_ChunkPool +{ + Dqn_Arena *arena; + Dqn_ChunkPoolSlot *slots[Dqn_ChunkPoolSlotSize_Count]; + uint8_t align; +}; + +// NOTE: [$ACAT] Dqn_ArenaCatalog ////////////////////////////////////////////////////////////////// +struct Dqn_ArenaCatalogItem +{ + Dqn_Arena *arena; + Dqn_Str8 label; + Dqn_ArenaCatalogItem *next; + Dqn_ArenaCatalogItem *prev; +}; + +struct Dqn_ArenaCatalog +{ + Dqn_TicketMutex ticket_mutex; // Mutex for adding to the linked list of arenas + struct Dqn_ChunkPool *pool; + Dqn_ArenaCatalogItem sentinel; + uint16_t arena_count; +}; + +// NOTE: [$AREN] Dqn_Arena ///////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Arena Dqn_Arena_InitSize (uint64_t reserve, uint64_t commit, uint8_t flags); +DQN_API void Dqn_Arena_Deinit (Dqn_Arena *arena); +DQN_API bool Dqn_Arena_Commit (Dqn_Arena *arena, uint64_t size); +DQN_API bool Dqn_Arena_CommitTo (Dqn_Arena *arena, uint64_t pos); +DQN_API void * Dqn_Arena_Alloc (Dqn_Arena *arena, uint64_t size, uint8_t align, Dqn_ZeroMem zero_mem); +DQN_API void * Dqn_Arena_AllocContiguous (Dqn_Arena *arena, uint64_t size, uint8_t align, Dqn_ZeroMem zero_mem); +DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void const *data, uint64_t size, uint8_t align); +DQN_API void Dqn_Arena_PopTo (Dqn_Arena *arena, uint64_t init_used); +DQN_API void Dqn_Arena_Pop (Dqn_Arena *arena, uint64_t amount); +DQN_API void Dqn_Arena_Clear (Dqn_Arena *arena); +DQN_API Dqn_ArenaTempMem Dqn_Arena_TempMemBegin (Dqn_Arena *arena); +DQN_API void Dqn_Arena_TempMemEnd (Dqn_ArenaTempMem mem); +#define Dqn_Arena_New(arena, T, zero_mem) (T *)Dqn_Arena_Alloc(arena, sizeof(T), alignof(T), zero_mem) +#define Dqn_Arena_NewArray(arena, T, count, zero_mem) (T *)Dqn_Arena_Alloc(arena, sizeof(T) * (count), alignof(T), zero_mem) +#define Dqn_Arena_NewCopy(arena, T, src) (T *)Dqn_Arena_Copy (arena, (src), sizeof(*src), alignof(T)) +#define Dqn_Arena_NewArrayCopy(arena, T, src, count) (T *)Dqn_Arena_Copy (arena, (src), sizeof(*src) * (count), alignof(T)) + +// NOTE: [$CHUN] Dqn_ChunkPool ///////////////////////////////////////////////////////////////////// +#define Dqn_ChunkPool_New(pool, T) (T *)Dqn_ChunkPool_Alloc(pool, sizeof(T)) +DQN_API Dqn_ChunkPool Dqn_ChunkPool_Init (Dqn_Arena *arena, uint8_t align); +DQN_API bool Dqn_ChunkPool_IsValid (Dqn_ChunkPool const *pool); +DQN_API void * Dqn_ChunkPool_Alloc (Dqn_ChunkPool *pool, Dqn_usize size); +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8FV (Dqn_ChunkPool *pool, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8F (Dqn_ChunkPool *pool, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_Str8 Dqn_ChunkPool_AllocStr8Copy (Dqn_ChunkPool *pool, Dqn_Str8 string); +DQN_API void Dqn_ChunkPool_Dealloc (Dqn_ChunkPool *pool, void *ptr); + +// NOTE: [$ACAT] Dqn_ArenaCatalog ////////////////////////////////////////////////////////////////// +DQN_API void Dqn_ArenaCatalog_Init (Dqn_ArenaCatalog *catalog, Dqn_ChunkPool *pool); +DQN_API Dqn_ArenaCatalogItem *Dqn_ArenaCatalog_Find (Dqn_ArenaCatalog *catalog, Dqn_Str8 label); +DQN_API void Dqn_ArenaCatalog_AddLabelRef (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, Dqn_Str8 label); +DQN_API void Dqn_ArenaCatalog_AddLabelCopy (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, Dqn_Str8 label); +DQN_API void Dqn_ArenaCatalog_AddF (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_ArenaCatalog_AddFV (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API Dqn_Arena * Dqn_ArenaCatalog_AllocLabelRef (Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, Dqn_Str8 label); +DQN_API Dqn_Arena * Dqn_ArenaCatalog_AllocLabelCopy(Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, Dqn_Str8 label); +DQN_API Dqn_Arena * Dqn_ArenaCatalog_AllocFV (Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API Dqn_Arena * Dqn_ArenaCatalog_AllocF (Dqn_ArenaCatalog *catalog, Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, DQN_FMT_ATTRIB char const *fmt, ...); diff --git a/dqn_base.cpp b/dqn_base.cpp index d57dd54..1d4cc6c 100644 --- a/dqn_base.cpp +++ b/dqn_base.cpp @@ -1,10 +1,25 @@ -// NOTE: [$INTR] Intrinsics ======================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ +// $$ __$$\ +// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ +// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ +// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | +// $$ | $$ |$$ __$$ | \____$$\ $$ ____| +// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ +// \_______/ \_______|\_______/ \_______| +// +// dqn_base.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// #if !defined(DQN_PLATFORM_ARM64) && !defined(DQN_PLATFORM_EMSCRIPTEN) #if defined(DQN_COMPILER_GCC) || defined(DQN_COMPILER_CLANG) #include #endif -Dqn_CPUIDRegisters Dqn_CPUID(int function_id) +DQN_API Dqn_CPUIDRegisters Dqn_CPUID(int function_id) { Dqn_CPUIDRegisters result = {}; #if defined(DQN_COMPILER_MSVC) @@ -18,28 +33,7 @@ Dqn_CPUIDRegisters Dqn_CPUID(int function_id) } #endif // !defined(DQN_PLATFORM_ARM64) && !defined(DQN_PLATFORM_EMSCRIPTEN) -// NOTE: [$ALLO] Dqn_Allocator ===================================================================== -DQN_API void *Dqn_Allocator_Alloc(Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem) -{ - void *result = NULL; - if (allocator.alloc) { - result = allocator.alloc(size, align, zero_mem, allocator.user_context); - } else { - result = DQN_ALLOC(size); - } - return result; -} - -DQN_API void Dqn_Allocator_Dealloc(Dqn_Allocator allocator, void *ptr, size_t size) -{ - if (allocator.dealloc) { - allocator.dealloc(ptr, size, allocator.user_context); - } else { - DQN_DEALLOC(ptr, size); - } -} - -// NOTE: [$TMUT] Dqn_TicketMutex =================================================================== +// NOTE: [$TMUT] Dqn_TicketMutex /////////////////////////////////////////////////////////////////// DQN_API void Dqn_TicketMutex_Begin(Dqn_TicketMutex *mutex) { unsigned int ticket = Dqn_Atomic_AddU32(&mutex->ticket, 1); @@ -82,7 +76,7 @@ DQN_API bool Dqn_TicketMutex_CanLock(Dqn_TicketMutex const *mutex, Dqn_uint tick #endif #endif -// NOTE: [$PRIN] Dqn_Print ========================================================================= +// NOTE: [$PRIN] Dqn_Print ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_PrintStyle Dqn_Print_StyleColour(uint8_t r, uint8_t g, uint8_t b, Dqn_PrintBold bold) { Dqn_PrintStyle result = {}; @@ -115,7 +109,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_Str8 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 ==================================================== + // NOTE: Get the output handles from kernel //////////////////////////////////////////////////// DQN_THREAD_LOCAL void *std_out_print_handle = nullptr; DQN_THREAD_LOCAL void *std_err_print_handle = nullptr; DQN_THREAD_LOCAL bool std_out_print_to_console = false; @@ -130,7 +124,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_Str8 string) std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; } - // NOTE: Select the output handle ============================================================== + // 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) { @@ -138,7 +132,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_Str8 string) print_to_console = std_err_print_to_console; } - // NOTE: Write the string ====================================================================== + // 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) { @@ -164,17 +158,6 @@ DQN_API void Dqn_Print_StdStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, D } } -DQN_FILE_SCOPE char *Dqn_Print_VSPrintfChunker_(const char *buf, void *user, int len) -{ - Dqn_Str8 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, DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; @@ -191,10 +174,27 @@ DQN_API void Dqn_Print_StdFStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, va_end(args); } +#if !defined(DQN_USE_STD_PRINTF) +DQN_FILE_SCOPE char *Dqn_Print_VSPrintfChunker_(const char *buf, void *user, int len) +{ + Dqn_Str8 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; +} +#endif + DQN_API void Dqn_Print_StdFV(Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, va_list args) { + #if defined(DQN_USE_STD_PRINTF) + vfprintf(std_handle == Dqn_PrintStd_Out ? stdout : stderr, fmt, args); + #else char buffer[STB_SPRINTF_MIN]; STB_SPRINTF_DECORATE(vsprintfcb)(Dqn_Print_VSPrintfChunker_, DQN_CAST(void *)DQN_CAST(uintptr_t)std_handle, buffer, fmt, args); + #endif } DQN_API void Dqn_Print_StdFVStyle(Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, va_list args) @@ -255,26 +255,26 @@ DQN_API Dqn_Str8 Dqn_Print_ESCColourStr8(Dqn_PrintESCColour colour, uint8_t r, u DQN_THREAD_LOCAL char buffer[32]; buffer[0] = 0; Dqn_Str8 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.size = DQN_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_Str8 Dqn_Print_ESCColourU32Str8(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); + 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_Str8 result = Dqn_Print_ESCColourStr8(colour, r, g, b); return result; } -// NOTE: [$LLOG] Dqn_Log ========================================================================== -DQN_API Dqn_Str8 Dqn_Log_MakeStr8(Dqn_Allocator allocator, +// NOTE: [$LLOG] Dqn_Log /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_Log_MakeStr8(Dqn_Arena *arena, bool colour, Dqn_Str8 type, int log_type, @@ -282,64 +282,58 @@ DQN_API Dqn_Str8 Dqn_Log_MakeStr8(Dqn_Allocator allocator, DQN_FMT_ATTRIB char const *fmt, va_list args) { - Dqn_usize header_size_no_ansi_codes = 0; - Dqn_Str8 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_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_Str8 colour_esc = {}; - Dqn_Str8 bold_esc = {}; - Dqn_Str8 reset_esc = {}; - if (colour) { - bold_esc = Dqn_Print_ESCBoldStr8; - reset_esc = Dqn_Print_ESCResetStr8; - switch (log_type) { - case Dqn_LogType_Debug: break; - case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Info); break; - case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Warning); break; - case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Error); break; - } + Dqn_Str8 colour_esc = {}; + Dqn_Str8 bold_esc = {}; + Dqn_Str8 reset_esc = {}; + if (colour) { + bold_esc = Dqn_Print_ESCBoldStr8; + reset_esc = Dqn_Print_ESCResetStr8; + switch (log_type) { + case Dqn_LogType_Debug: break; + case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Info); break; + case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Warning); break; + case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32Str8(Dqn_LogTypeColourU32_Error); break; } - - Dqn_Str8 file_name = Dqn_Str8_FileNameFromPath(call_site.file); - Dqn_DateHMSTimeStr8 const time = Dqn_Date_LocalTimeHMSStr8Now(); - header = Dqn_Str8_InitF(allocator, - "%.*s " // date - "%.*s " // hms - "%.*s" // colour - "%.*s" // bold - "%.*s" // type - "%*s" // type padding - "%.*s" // reset - " %.*s" // file name - ":%05u ", // line number - DQN_CAST(uint32_t)time.date_size - 2, time.date + 2, // date - DQN_CAST(uint32_t)time.hms_size, time.hms, // hms - DQN_CAST(uint32_t)colour_esc.size, colour_esc.data, // colour - DQN_CAST(uint32_t)bold_esc.size, bold_esc.data, // bold - DQN_CAST(uint32_t)type.size, type.data, // type - DQN_CAST(uint32_t)type_padding, "", // type padding - DQN_CAST(uint32_t)reset_esc.size, reset_esc.data, // reset - DQN_CAST(uint32_t)file_name.size, file_name.data, // file name - call_site.line); // line number - header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetStr8.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; - } + Dqn_Str8 file_name = Dqn_Str8_FileNameFromPath(call_site.file); + Dqn_OSDateTimeStr8 const time = Dqn_OS_DateLocalTimeStr8Now(); + Dqn_Str8 header = Dqn_Str8_InitF(arena, + "%.*s " // date + "%.*s " // hms + "%.*s" // colour + "%.*s" // bold + "%.*s" // type + "%*s" // type padding + "%.*s" // reset + " %.*s" // file name + ":%05I32u " // line number + , + DQN_CAST(int)time.date_size - 2, time.date + 2, // date + DQN_CAST(int)time.hms_size, time.hms, // hms + DQN_STR_FMT(colour_esc), // colour + DQN_STR_FMT(bold_esc), // bold + DQN_STR_FMT(type), // type + DQN_CAST(int)type_padding, "", // type padding + DQN_STR_FMT(reset_esc), // reset + DQN_STR_FMT(file_name), // file name + call_site.line); // line number + Dqn_usize header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetStr8.size; - // NOTE: Construct final log =================================================================== - Dqn_Str8 user_msg = Dqn_Str8_InitFV(allocator, fmt, args); - Dqn_Str8 result = Dqn_Str8_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); + // NOTE: Header padding //////////////////////////////////////////////////////////////////////// + DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0; + max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes); + Dqn_usize header_padding = max_header_length - header_size_no_ansi_codes; + + // NOTE: Construct final log /////////////////////////////////////////////////////////////////// + Dqn_Str8 user_msg = Dqn_Str8_InitFV(arena, fmt, args); + Dqn_Str8 result = Dqn_Str8_Alloc(arena, 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; } @@ -350,25 +344,25 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d (void)log_type; (void)user_data; - // NOTE: Open log file for appending if requested ========================== + // NOTE: Open log file for appending if requested ////////////////////////// Dqn_TicketMutex_Begin(&lib->log_file_mutex); if (lib->log_to_file && !lib->log_file.handle && lib->log_file.error_size == 0) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 log_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/dqn.log", DQN_STR_FMT(lib->exe_dir)); - lib->log_file = Dqn_Fs_OpenFile(log_path, Dqn_FsFileOpen_CreateAlways, Dqn_FsFileAccess_AppendOnly); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 log_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/dqn.log", DQN_STR_FMT(lib->exe_dir)); + lib->log_file = Dqn_OS_OpenFile(log_path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_AppendOnly); } Dqn_TicketMutex_End(&lib->log_file_mutex); - // NOTE: Generate the log header =========================================== - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 log_line = Dqn_Log_MakeStr8(scratch.allocator, !lib->log_no_colour, type, log_type, call_site, fmt, args); + // NOTE: Generate the log header /////////////////////////////////////////// + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 log_line = Dqn_Log_MakeStr8(scratch.arena, !lib->log_no_colour, type, log_type, call_site, fmt, args); - // NOTE: Print log ========================================================= + // NOTE: Print log ///////////////////////////////////////////////////////// Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line); Dqn_TicketMutex_Begin(&lib->log_file_mutex); - Dqn_Fs_WriteFile(&lib->log_file, log_line); - Dqn_Fs_WriteFile(&lib->log_file, DQN_STR8("\n")); + Dqn_OS_WriteFile(&lib->log_file, log_line); + Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n")); Dqn_TicketMutex_End(&lib->log_file_mutex); } diff --git a/dqn_base.h b/dqn_base.h index c866675..8aadf63 100644 --- a/dqn_base.h +++ b/dqn_base.h @@ -1,9 +1,31 @@ -// NOTE: Preprocessor Token Tricks ================================================================= +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ +// $$ __$$\ +// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ +// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ +// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | +// $$ | $$ |$$ __$$ | \____$$\ $$ ____| +// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ +// \_______/ \_______|\_______/ \_______| +// +// dqn_base.h -- Base primitives for the library +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$MACR] Macros -- General macros +// [$TYPE] Types -- Basic types and typedefs +// [$INTR] Intrinsics -- Platform agnostic functions for CPU instructions (e.g. atomics, cpuid, ...) +// [$CALL] Dqn_CallSite -- Source code location/tracing +// [$TMUT] Dqn_TicketMutex -- Userland mutex via spinlocking atomics +// [$PRIN] Dqn_Print -- Console printing +// [$LLOG] Dqn_Log -- Console logging macros +// +// NOTE: [$MACR] Macros //////////////////////////////////////////////////////////////////////////// #define DQN_STRINGIFY(x) #x #define DQN_TOKEN_COMBINE2(x, y) x ## y #define DQN_TOKEN_COMBINE(x, y) DQN_TOKEN_COMBINE2(x, y) -// NOTE: [$CMAC] Compiler macros =================================================================== // NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER #if defined(_MSC_VER) #if defined(__clang__) @@ -39,14 +61,31 @@ #if defined(_WIN32) #define DQN_OS_WIN32 -#elif defined(__linux__) +#elif defined(__gnu_linux__) || defined(__linux__) #define DQN_OS_UNIX #endif -#if defined(__aarch64__) || defined(_M_ARM64) - #define DQN_PLATFORM_ARM64 -#elif defined(__EMSCRIPTEN__) - #define DQN_PLATFORM_EMSCRIPTEN +#include +#include +#include +#include + +#if !defined(DQN_OS_WIN32) +#include // exit() +#endif + +#if !defined(DQN_PLATFORM_EMSCRIPTEN) && \ + !defined(DQN_PLATFORM_POSIX) && \ + !defined(DQN_PLATFORM_WIN32) + #if defined(__aarch64__) || defined(_M_ARM64) + #define DQN_PLATFORM_ARM64 + #elif defined(__EMSCRIPTEN__) + #define DQN_PLATFORM_EMSCRIPTEN + #elif defined(DQN_OS_WIN32) + #define DQN_PLATFORM_WIN32 + #else + #define DQN_PLATFORM_POSIX + #endif #endif #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) @@ -81,33 +120,23 @@ #define DQN_GCC_WARNING_POP #endif -// NOTE: [$MACR] Macros ============================================================================ +// NOTE: MSVC does not support the feature detection macro for instance so we +// compile it out +#if defined(__has_feature) + #define DQN_HAS_FEATURE(expr) __has_feature(expr) +#else + #define DQN_HAS_FEATURE(expr) 0 +#endif + #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_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) -#define Dqn_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ((uintptr_t)(pot) - 1)) +#define Dqn_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) #define Dqn_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) #define Dqn_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) -// NOTE: Alloc Macros ============================================================================== -#if !defined(DQN_ALLOC) - #if defined(DQN_PLATFORM_EMSCRIPTEN) - #define DQN_ALLOC(size) malloc(size) - #else - #define DQN_ALLOC(size) Dqn_VMem_Reserve(size, Dqn_VMemCommit_Yes, Dqn_VMemPage_ReadWrite) - #endif -#endif - -#if !defined(DQN_DEALLOC) - #if defined(DQN_PLATFORM_EMSCRIPTEN) - #define DQN_DEALLOC(ptr, size) free(ptr) - #else - #define DQN_DEALLOC(ptr, size) Dqn_VMem_Release(ptr, size) - #endif -#endif - -// NOTE: String.h Dependencies ===================================================================== +// NOTE: String.h Dependencies ///////////////////////////////////////////////////////////////////// #if !defined(DQN_MEMCPY) || !defined(DQN_MEMSET) || !defined(DQN_MEMCMP) || !defined(DQN_MEMMOVE) #include #if !defined(DQN_MEMCPY) @@ -124,10 +153,12 @@ #endif #endif -// NOTE: Math.h Dependencies ======================================================================= +// NOTE: Math.h Dependencies /////////////////////////////////////////////////////////////////////// #if !defined(DQN_SQRTF) || !defined(DQN_SINF) || !defined(DQN_COSF) || !defined(DQN_TANF) #include - #define DQN_SQRTF(val) sqrtf(val) + #if !defined(DQN_SQRTF) + #define DQN_SQRTF(val) sqrtf(val) + #endif #if !defined(DQN_SINF) #define DQN_SINF(val) sinf(val) #endif @@ -139,11 +170,7 @@ #endif #endif -#if !defined(DQN_OS_WIN32) -#include // exit() -#endif - -// NOTE: Math Macros =============================================================================== +// NOTE: Math ////////////////////////////////////////////////////////////////////////////////////// #define DQN_PI 3.14159265359f #define DQN_DEGREE_TO_RADIAN(degrees) ((degrees) * (DQN_PI / 180.0f)) @@ -163,7 +190,7 @@ b = temp; \ } while (0) -// NOTE: Function/Variable Annotations ============================================================= +// NOTE: Function/Variable Annotations ///////////////////////////////////////////////////////////// #if defined(DQN_STATIC_API) #define DQN_API static #else @@ -180,86 +207,88 @@ #define DQN_FORCE_INLINE inline __attribute__((always_inline)) #endif -// NOTE: Size Macros =============================================================================== +// NOTE: Size ////////////////////////////////////////////////////////////////////////////////////// #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) +// NOTE: SI Byte /////////////////////////////////////////////////////////////////////////////////// +#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 =============================================================================== +// NOTE: Time ////////////////////////////////////////////////////////////////////////////////////// #define DQN_SECONDS_TO_MS(val) ((val) * 1000) -#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) +#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_WEEKS_TO_S(val) (DQN_DAYS_TO_S(val) * 7ULL) +#define DQN_YEARS_TO_S(val) (DQN_WEEKS_TO_S(val) * 52ULL) -// NOTE: Debug Break =============================================================================== +#if defined(__has_builtin) + #define DQN_HAS_BUILTIN(expr) __has_builtin(expr) +#else + #define DQN_HAS_BUILTIN(expr) 0 +#endif + +// NOTE: Debug Break /////////////////////////////////////////////////////////////////////////////// #if !defined(DQN_DEBUG_BREAK) #if defined(NDEBUG) #define DQN_DEBUG_BREAK #else #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) #define DQN_DEBUG_BREAK __debugbreak() - #elif defined(DQN_COMPILER_CLANG) + #elif DQN_HAS_BUILTIN(__builtin_debugtrap) #define DQN_DEBUG_BREAK __builtin_debugtrap() - #elif defined(DQN_COMPILER_CLANG) || defined(DQN_COMPILER_GCC) + #elif DQN_HAS_BUILTIN(__builtin_trap) || defined(DQN_COMPILER_GCC) + #define DQN_DEBUG_BREAK __builtin_trap() + #else #include - #define DQN_DEBUG_BREAK raise(SIGTRAP) - #elif - #error "Unhandled compiler" + #if defined(SIGTRAP) + #define DQN_DEBUG_BREAK raise(SIGTRAP) + #else + #define DQN_DEBUG_BREAK raise(SIGABRT) + #endif #endif #endif #endif -#if !defined(DQN_MEMSET_BYTE) - #define DQN_MEMSET_BYTE 0 -#endif - -// NOTE: Assert Macros ============================================================================= +// 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_StackTrace_Print(128 /*limit*/); \ - DQN_DEBUG_BREAK; \ - } +#define DQN_HARD_ASSERTF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + Dqn_Str8 stack_trace_ = Dqn_StackTrace_WalkStr8CRT(128 /*limit*/, 2 /*skip*/); \ + Dqn_Log_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DQN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DQN_DEBUG_BREAK; \ + } \ + } while (0) #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_StackTrace_Print(128 /*limit*/); \ - DQN_DEBUG_BREAK; \ - } + #define DQN_ASSERT(expr) DQN_ASSERTF((expr), "") + #define DQN_ASSERTF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + Dqn_Str8 stack_trace_ = Dqn_StackTrace_WalkStr8CRT(128 /*limit*/, 2 /*skip*/); \ + Dqn_Log_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DQN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DQN_DEBUG_BREAK; \ + } \ + } while (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: Check macro =============================================================================== -// Check the expression trapping in debug, whilst in release- trapping is -// removed and the expression is evaluated as if it were a normal 'if' branch. -// -// This allows handling of the condition gracefully when compiled out but traps -// to notify the developer in builds when it's compiled in. -#if 0 - bool flag = true; - if (DQN_CHECKF(flag, "Flag was false!")) { - // This branch will execute! - } else { - // Prints "Flag was false!" - } -#endif +// NOTE: Check macro /////////////////////////////////////////////////////////////////////////////// #define DQN_CHECK(expr) DQN_CHECKF(expr, "") #if defined(DQN_NO_CHECK_BREAK) @@ -267,32 +296,18 @@ ((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)) + ((expr) ? true : (Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__), Dqn_StackTrace_Print(128 /*limit*/), DQN_DEBUG_BREAK, false)) #endif -// NOTE: Zero initialisation macro ================================================================= +// NOTE: Zero initialisation macro ///////////////////////////////////////////////////////////////// #if defined(__cplusplus) #define DQN_ZERO_INIT {} #else #define DQN_ZERO_INIT {0} #endif -// NOTE: Defer Macro =============================================================================== +// NOTE: Defer Macro /////////////////////////////////////////////////////////////////////////////// #if defined(__cplusplus) -#if 0 -#include -int main() -{ - DQN_DEFER { printf("Three ..\n"); }; - printf("One ..\n"); - printf("Two ..\n"); - // One .. - // Two .. - // Three .. - return 0; -} -#endif - template struct Dqn_Defer { @@ -316,7 +331,7 @@ struct Dqn_DeferHelper DQN_UNIQUE_NAME(once); \ end, DQN_UNIQUE_NAME(once) = false) -// NOTE: [$TYPE] Types ============================================================================= +// NOTE: [$TYPE] Types ///////////////////////////////////////////////////////////////////////////// typedef intptr_t Dqn_isize; typedef uintptr_t Dqn_usize; typedef intptr_t Dqn_isize; @@ -350,7 +365,6 @@ struct Dqn_Str8 char *end () { return data + size; } }; -#if !defined(DQN_NO_SLICE) template struct Dqn_Slice // A pointer and length container of data { T *data; @@ -361,32 +375,17 @@ template struct Dqn_Slice // A pointer and length container of data T const *begin() const { return data; } T const *end () const { return data + size; } }; -#endif -// NOTE: [$CALL] Dqn_CallSite ====================================================================== +// NOTE: [$CALL] Dqn_CallSite ////////////////////////////////////////////////////////////////////// struct Dqn_CallSite { - Dqn_Str8 file; - Dqn_Str8 function; - unsigned int line; + Dqn_Str8 file; + Dqn_Str8 function; + uint32_t line; }; #define DQN_CALL_SITE Dqn_CallSite{DQN_STR8(__FILE__), DQN_STR8(__func__), __LINE__} -// NOTE: [$INTR] Intrinsics ======================================================================== -// Platform agnostic functions for CPU level instructions like atomics, barriers -// and timestamp counters. -// -// NOTE: API -// @proc Dqn_Atomic_SetValue64, Dqn_Atomic_SetValue32 -// @desc 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 - -// @proc Dqn_CPUID -// Execute 'CPUID' instruction to query the capabilities of the current CPU. - +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// // NOTE: Dqn_Atomic_Add/Exchange return the previous value store in the target #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) #include @@ -422,6 +421,176 @@ struct Dqn_CallSite #error "Compiler not supported" #endif +#if !defined(DQN_PLATFORM_ARM64) +struct Dqn_CPUIDRegisters +{ + Dqn_uint array[4]; // Values from 'CPUID' instruction for each register (EAX, EBX, ECX, EDX) +}; +#endif // DQN_PLATFORM_ARM64 + +// NOTE: [$TMUT] Dqn_TicketMutex /////////////////////////////////////////////////////////////////// +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 +}; + +// NOTE: [$PRIN] Dqn_Print ///////////////////////////////////////////////////////////////////////// +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: [$LLOG] Dqn_Log /////////////////////////////////////////////////////////////////////////// +enum Dqn_LogType +{ + Dqn_LogType_Debug, + Dqn_LogType_Info, + Dqn_LogType_Warning, + Dqn_LogType_Error, + Dqn_LogType_Count, +}; + +typedef void Dqn_LogProc(Dqn_Str8 type, + int log_type, + void *user_data, + Dqn_CallSite call_site, + DQN_FMT_ATTRIB char const *fmt, + va_list va); + +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// +DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64 (uint64_t volatile *target, uint64_t value); +DQN_FORCE_INLINE long Dqn_Atomic_SetValue32 (long volatile *target, long value); +#if !defined(DQN_PLATFORM_ARM64) +DQN_API Dqn_CPUIDRegisters Dqn_CPUID (int function_id); +#endif + +// NOTE: [$TMUT] Dqn_TicketMutex /////////////////////////////////////////////////////////////////// +DQN_API void Dqn_TicketMutex_Begin (Dqn_TicketMutex *mutex); +DQN_API void Dqn_TicketMutex_End (Dqn_TicketMutex *mutex); +DQN_API Dqn_uint Dqn_TicketMutex_MakeTicket (Dqn_TicketMutex *mutex); +DQN_API void Dqn_TicketMutex_BeginTicket (Dqn_TicketMutex const *mutex, Dqn_uint ticket); +DQN_API bool Dqn_TicketMutex_CanLock (Dqn_TicketMutex const *mutex, Dqn_uint ticket); + +// NOTE: [$PRIN] Dqn_Print ///////////////////////////////////////////////////////////////////////// +// 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 Macros ////////////////////////////////////////////////////////////////////////////// +#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); + +#define Dqn_Print_Err(string) Dqn_Print_Std(Dqn_PrintStd_Err, string) +#define Dqn_Print_ErrF(fmt, ...) Dqn_Print_StdF(Dqn_PrintStd_Err, fmt, ## __VA_ARGS__) +#define Dqn_Print_ErrFV(fmt, args) Dqn_Print_StdFV(Dqn_PrintStd_Err, fmt, args) + +#define Dqn_Print_ErrStyle(style, string) Dqn_Print_StdStyle(Dqn_PrintStd_Err, style, string) +#define Dqn_Print_ErrFStyle(style, fmt, ...) Dqn_Print_StdFStyle(Dqn_PrintStd_Err, style, fmt, ## __VA_ARGS__) +#define Dqn_Print_ErrFVStyle(style, fmt, args, ...) Dqn_Print_StdFVStyle(Dqn_PrintStd_Err, style, fmt, args) + +#define Dqn_Print_ErrLn(string) Dqn_Print_StdLn(Dqn_PrintStd_Err, string) +#define Dqn_Print_ErrLnF(fmt, ...) Dqn_Print_StdLnF(Dqn_PrintStd_Err, fmt, ## __VA_ARGS__) +#define Dqn_Print_ErrLnFV(fmt, args) Dqn_Print_StdLnFV(Dqn_PrintStd_Err, fmt, args) + +#define Dqn_Print_ErrLnStyle(style, string) Dqn_Print_StdLnStyle(Dqn_PrintStd_Err, style, string); +#define Dqn_Print_ErrLnFStyle(style, fmt, ...) Dqn_Print_StdLnFStyle(Dqn_PrintStd_Err, style, fmt, ## __VA_ARGS__); +#define Dqn_Print_ErrLnFVStyle(style, fmt, args) Dqn_Print_StdLnFVStyle(Dqn_PrintStd_Err, style, fmt, args); +// NOTE: Print ///////////////////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_Print_Std (Dqn_PrintStd std_handle, Dqn_Str8 string); +DQN_API void Dqn_Print_StdF (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_Print_StdFV (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_Str8 string); +DQN_API void Dqn_Print_StdFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_Print_StdFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdLn (Dqn_PrintStd std_handle, Dqn_Str8 string); +DQN_API void Dqn_Print_StdLnF (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_Print_StdLnFV (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, va_list args); + +DQN_API void Dqn_Print_StdLnStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_Str8 string); +DQN_API void Dqn_Print_StdLnFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_Print_StdLnFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, va_list args); + +// NOTE: ANSI Formatting Codes ///////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_Print_ESCColourStr8 (Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b); +DQN_API Dqn_Str8 Dqn_Print_ESCColourU32Str8 (Dqn_PrintESCColour colour, uint32_t value); + +#define Dqn_Print_ESCColourFgStr8(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Fg, r, g, b) +#define Dqn_Print_ESCColourBgStr8(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Bg, r, g, b) +#define Dqn_Print_ESCColourFg(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Fg, r, g, b).data +#define Dqn_Print_ESCColourBg(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Bg, r, g, b).data + +#define Dqn_Print_ESCColourFgU32Str8(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Fg, value) +#define Dqn_Print_ESCColourBgU32Str8(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Bg, value) +#define Dqn_Print_ESCColourFgU32(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Fg, value).data +#define Dqn_Print_ESCColourBgU32(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Bg, value).data + +#define Dqn_Print_ESCReset "\x1b[0m" +#define Dqn_Print_ESCBold "\x1b[1m" +#define Dqn_Print_ESCResetStr8 DQN_STR8(Dqn_Print_ESCReset) +#define Dqn_Print_ESCBoldStr8 DQN_STR8(Dqn_Print_ESCBold) +// NOTE: [$LLOG] Dqn_Log /////////////////////////////////////////////////////////////////////////// +#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 + +#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_Str8 Dqn_Log_MakeStr8 (struct Dqn_Arena *arena, bool colour, Dqn_Str8 type, int log_type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API void Dqn_Log_TypeFVCallSite (Dqn_LogType type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va); +DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_Log_FVCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va); +DQN_API void Dqn_Log_FCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...); + +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint64_t value) { #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) @@ -453,233 +622,3 @@ DQN_FORCE_INLINE long Dqn_Atomic_SetValue32(long volatile *target, long value) #error Unsupported compiler #endif } - -#if !defined(DQN_PLATFORM_ARM64) -struct Dqn_CPUIDRegisters -{ - Dqn_uint array[4]; ///< Values from 'CPUID' instruction for each register (EAX, EBX, ECX, EDX) -}; - -Dqn_CPUIDRegisters Dqn_CPUID(int function_id); -#endif // DQN_PLATFORM_ARM64 - -// NOTE: [$TMUT] Dqn_TicketMutex =================================================================== -// -// 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 -#if 0 - 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); -#endif - -// @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); - -// NOTE: [$ALLO] Dqn_Allocator ===================================================================== -typedef void *Dqn_Allocator_AllocProc(size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context); -typedef void Dqn_Allocator_DeallocProc(void *ptr, size_t size, void *user_context); - -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 -}; - -// NOTE: Macros ==================================================================================== -#define Dqn_Allocator_NewArray(allocator, Type, count, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type) * count, alignof(Type), zero_mem) -#define Dqn_Allocator_New(allocator, Type, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type), alignof(Type), zero_mem) - -// NOTE: API ======================================================================================= -void *Dqn_Allocator_Alloc (Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem); -void Dqn_Allocator_Dealloc(Dqn_Allocator allocator, void *ptr, size_t size); - -// NOTE: [$PRIN] Dqn_Print ========================================================================= -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); - -#define Dqn_Print_Err(string) Dqn_Print_Std(Dqn_PrintStd_Err, string) -#define Dqn_Print_ErrF(fmt, ...) Dqn_Print_StdF(Dqn_PrintStd_Err, fmt, ## __VA_ARGS__) -#define Dqn_Print_ErrFV(fmt, args) Dqn_Print_StdFV(Dqn_PrintStd_Err, fmt, args) - -#define Dqn_Print_ErrStyle(style, string) Dqn_Print_StdStyle(Dqn_PrintStd_Err, style, string) -#define Dqn_Print_ErrFStyle(style, fmt, ...) Dqn_Print_StdFStyle(Dqn_PrintStd_Err, style, fmt, ## __VA_ARGS__) -#define Dqn_Print_ErrFVStyle(style, fmt, args, ...) Dqn_Print_StdFVStyle(Dqn_PrintStd_Err, style, fmt, args) - -#define Dqn_Print_ErrLn(string) Dqn_Print_StdLn(Dqn_PrintStd_Err, string) -#define Dqn_Print_ErrLnF(fmt, ...) Dqn_Print_StdLnF(Dqn_PrintStd_Err, fmt, ## __VA_ARGS__) -#define Dqn_Print_ErrLnFV(fmt, args) Dqn_Print_StdLnFV(Dqn_PrintStd_Err, fmt, args) - -#define Dqn_Print_ErrLnStyle(style, string) Dqn_Print_StdLnStyle(Dqn_PrintStd_Err, style, string); -#define Dqn_Print_ErrLnFStyle(style, fmt, ...) Dqn_Print_StdLnFStyle(Dqn_PrintStd_Err, style, fmt, ## __VA_ARGS__); -#define Dqn_Print_ErrLnFVStyle(style, fmt, args) Dqn_Print_StdLnFVStyle(Dqn_PrintStd_Err, style, fmt, args); - - -// NOTE: Print ===================================================================================== -DQN_API void Dqn_Print_Std (Dqn_PrintStd std_handle, Dqn_Str8 string); -DQN_API void Dqn_Print_StdF (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Print_StdFV (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_Str8 string); -DQN_API void Dqn_Print_StdFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Print_StdFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdLn (Dqn_PrintStd std_handle, Dqn_Str8 string); -DQN_API void Dqn_Print_StdLnF (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Print_StdLnFV (Dqn_PrintStd std_handle, DQN_FMT_ATTRIB char const *fmt, va_list args); - -DQN_API void Dqn_Print_StdLnStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, Dqn_Str8 string); -DQN_API void Dqn_Print_StdLnFStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Print_StdLnFVStyle (Dqn_PrintStd std_handle, Dqn_PrintStyle style, DQN_FMT_ATTRIB char const *fmt, va_list args); - -// NOTE: ANSI Formatting Codes ===================================================================== -Dqn_Str8 Dqn_Print_ESCColourStr8 (Dqn_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b); -Dqn_Str8 Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour colour, uint32_t value); - -#define Dqn_Print_ESCColourFgStr8(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Fg, r, g, b) -#define Dqn_Print_ESCColourBgStr8(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Bg, r, g, b) -#define Dqn_Print_ESCColourFg(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Fg, r, g, b).data -#define Dqn_Print_ESCColourBg(r, g, b) Dqn_Print_ESCColourStr8(Dqn_PrintESCColour_Bg, r, g, b).data - -#define Dqn_Print_ESCColourFgU32Str8(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Fg, value) -#define Dqn_Print_ESCColourBgU32Str8(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Bg, value) -#define Dqn_Print_ESCColourFgU32(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Fg, value).data -#define Dqn_Print_ESCColourBgU32(value) Dqn_Print_ESCColourU32Str8(Dqn_PrintESCColour_Bg, value).data - -#define Dqn_Print_ESCReset "\x1b[0m" -#define Dqn_Print_ESCBold "\x1b[1m" -#define Dqn_Print_ESCResetStr8 DQN_STR8(Dqn_Print_ESCReset) -#define Dqn_Print_ESCBoldStr8 DQN_STR8(Dqn_Print_ESCBold) - -// NOTE: [$LLOG] Dqn_Log ========================================================================== -// NOTE: API -// @proc Dqn_LogProc -// @desc 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. - -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 - -typedef void Dqn_LogProc(Dqn_Str8 type, int log_type, void *user_data, Dqn_CallSite call_site, DQN_FMT_ATTRIB 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_Str8 Dqn_Log_MakeStr8 (Dqn_Allocator allocator, bool colour, Dqn_Str8 type, int log_type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va); -DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Log_FVCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va); -DQN_API void Dqn_Log_FCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...); diff --git a/dqn_containers.cpp b/dqn_containers.cpp index 6d7d85f..b8c0f46 100644 --- a/dqn_containers.cpp +++ b/dqn_containers.cpp @@ -1,5 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | +// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ +// +// dqn_containers.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$SLIC] Dqn_Slice ///////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_Slice_Str8Render(Dqn_Arena *arena, Dqn_Slice array, Dqn_Str8 separator) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + Dqn_usize total_size = 0; + for (Dqn_usize index = 0; index < array.size; index++) { + if (index) + total_size += separator.size; + Dqn_Str8 item = array.data[index]; + total_size += item.size; + } + + result = Dqn_Str8_Alloc(arena, total_size, Dqn_ZeroMem_No); + if (result.data) { + Dqn_usize write_index = 0; + for (Dqn_usize index = 0; index < array.size; index++) { + if (index) { + DQN_MEMCPY(result.data + write_index, separator.data, separator.size); + write_index += separator.size; + } + Dqn_Str8 item = array.data[index]; + DQN_MEMCPY(result.data + write_index, item.data, item.size); + write_index += item.size; + } + } + + return result; +} + +DQN_API Dqn_Str8 Dqn_Slice_Str8RenderSpaceSeparated(Dqn_Arena *arena, Dqn_Slice array) +{ + Dqn_Str8 result = Dqn_Slice_Str8Render(arena, array, DQN_STR8(" ")); + return result; +} + #if !defined(DQN_NO_DSMAP) -// NOTE: [$DMAP] Dqn_DSMap ========================================================================= +// NOTE: [$DMAP] Dqn_DSMap ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash(uint64_t u64) { Dqn_DSMapKey result = {}; diff --git a/dqn_containers.h b/dqn_containers.h index 46bf74c..3bd506d 100644 --- a/dqn_containers.h +++ b/dqn_containers.h @@ -1,4 +1,29 @@ -// NOTE: [$CARR] Dqn_CArray ======================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | +// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ +// +// dqn_containers.h -- Data structures for storing data (e.g. arrays and hash tables) +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$CARR] Dqn_CArray -- -- Basic operations on C arrays for VArray/SArray/FArray to reuse +// [$VARR] Dqn_VArray -- DQN_VARRAY -- Array backed by virtual memory arena +// [$SARR] Dqn_SArray -- DQN_SARRAY -- Array that are allocated but cannot resize +// [$FARR] Dqn_FArray -- DQN_FARRAY -- Fixed-size arrays +// [$SLIC] Dqn_Slice -- -- Pointe and length container of data +// [$DMAP] Dqn_DSMap -- DQN_DSMAP -- Hashtable, 70% max load, PoT size, linear probe, chain repair +// [$LIST] Dqn_List -- DQN_LIST -- Chunked linked lists, append only +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$CARR] Dqn_CArray //////////////////////////////////////////////////////////////////////// enum Dqn_ArrayErase { Dqn_ArrayErase_Unstable, @@ -18,117 +43,32 @@ struct Dqn_ArrayEraseResult Dqn_usize items_erased; // The number of items erased }; -template -struct Dqn_ArrayFindResult +template struct Dqn_ArrayFindResult { T *data; // Pointer to the value if a match is found, null pointer otherwise Dqn_usize index; // Index to the value if a match is found, 0 otherwise }; -template Dqn_ArrayEraseResult Dqn_CArray_EraseRange (T *data, Dqn_usize *size, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); -template T * Dqn_CArray_MakeArray (T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize count, Dqn_ZeroMem zero_mem); -template T * Dqn_CArray_InsertArray(T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize index, T const *items, Dqn_usize count); -template T Dqn_CArray_PopFront (T *data, Dqn_usize *size, Dqn_usize count); -template T Dqn_CArray_PopBack (T *data, Dqn_usize *size, Dqn_usize count); -template Dqn_ArrayFindResult Dqn_CArray_Find (T *data, Dqn_usize size, T const &value); - #if !defined(DQN_NO_VARRAY) -// NOTE: [$VARR] Dqn_VArray ======================================================================== -// An array that is backed by virtual memory by reserving addressing space and -// comitting pages as items are allocated in the array. This array never -// reallocs, instead you should reserve the upper bound of the memory you will -// possibly ever need (e.g. 16GB) and let the array commit physical pages on -// demand. On 64 bit operating systems you are given 48 bits of addressable -// space giving you 256 TB of reservable memory. This gives you practically -// an unlimited array capacity that avoids reallocs and only consumes memory -// that is actually occupied by the array. -// -// Each page that is committed into the array will be at page/allocation -// granularity which are always cache aligned. This array essentially retains -// all the benefits of normal arrays, -// -// - contiguous memory -// - O(1) random access -// - O(N) iterate -// -// In addition to no realloc on expansion or shrinking. -// +// NOTE: [$VARR] Dqn_VArray //////////////////////////////////////////////////////////////////////// // TODO(doyle): Add an API for shrinking the array by decomitting pages back to // the OS. -// -// NOTE: API -// @proc Dqn_VArray_InitByteSize, Dqn_VArray_Init -// @desc Initialise an array with the requested byte size or item capacity -// respectively. The returned array may have a higher capacity than the -// requested amount since requested memory from the OS may have a certain -// alignment requirement (e.g. on Windows reserve/commit are 64k/4k aligned). - -// @proc Dqn_VArray_IsValid -// @desc Verify if the array has been initialised - -// @proc Dqn_VArray_Make, Dqn_VArray_Add -// @desc Allocate items into the array -// 'Make' creates the `count` number of requested items -// 'Add' adds the array of items into the array -// @return The array of items allocated. Null pointer if the array is invalid -// or the array has insufficient space for the requested items. - -// @proc Dqn_VArray_EraseRange -// @desc Erase the next `count` items at `begin_index` in the array. `count` -// can be positive or negative which dictates the if we erase forward from the -// `begin_index` or in reverse. -// -// This operation will invalidate all pointers to the array! -// -// @param erase The erase method, stable erase will shift all elements after -// the erase ranged into the range. Unstable erase will copy the tail elements -// into the range to delete. - -// @proc Dqn_VArray_Clear -// @desc Set the size of the array to 0 - -// @proc Dqn_VArray_Reserve -// @desc Ensure that the requested number of items are backed by physical -// pages from the OS. Calling this pre-emptively will minimise syscalls into -// the kernel to request memory. The requested items will be rounded up to the -// in bytes to the allocation granularity of OS allocation APIs hence the -// reserved space may be greater than the requested amount (e.g. this is 4k -// on Windows). - template struct Dqn_VArray { - Dqn_MemBlock *block; // Block of memory from the allocator for this array - T *data; // Pointer to the start of the array items in the block of memory - Dqn_usize size; // Number of items currently in the array - Dqn_usize max; // Maximum number of items this array can store + Dqn_Arena arena; // Allocator for the array + T *data; // Pointer to the start of the array items in the block of memory + Dqn_usize size; // Number of items currently in the array + Dqn_usize max; // Maximum number of items this array can store T *begin() { return data; } T *end () { return data + size; } T const *begin() const { return data; } T const *end () const { return data + size; } }; - -// NOTE: Setup ===================================================================================== -DQN_API template Dqn_VArray Dqn_VArray_InitByteSize(Dqn_Arena *arena, Dqn_usize byte_size); -DQN_API template Dqn_VArray Dqn_VArray_Init (Dqn_Arena *arena, Dqn_usize max); -DQN_API template bool Dqn_VArray_IsValid (Dqn_VArray const *array); -DQN_API template void Dqn_VArray_Reserve (Dqn_VArray *array, Dqn_usize count); - -// NOTE: Insert ==================================================================================== -DQN_API template T * Dqn_VArray_MakeArray (Dqn_VArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_VArray_Make (Dqn_VArray *array, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_VArray_AddArray (Dqn_VArray *array, T const *items, Dqn_usize count); -DQN_API template T * Dqn_VArray_Add (Dqn_VArray *array, T const &item); -DQN_API template T Dqn_VArray_PopFront (Dqn_VArray *array, Dqn_usize count); -DQN_API template T Dqn_VArray_PopBack (Dqn_VArray *array, Dqn_usize count); - -// NOTE: Modify ==================================================================================== -DQN_API template Dqn_ArrayEraseResult Dqn_VArray_EraseRange (Dqn_VArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); -DQN_API template void Dqn_VArray_Clear (Dqn_VArray *array, Dqn_ZeroMem zero_mem); #endif // !defined(DQN_NO_VARRAY) #if !defined(DQN_NO_SARRAY) -// NOTE: [$SARR] Dqn_SArray ======================================================================== +// NOTE: [$SARR] Dqn_SArray //////////////////////////////////////////////////////////////////////// template struct Dqn_SArray { T *data; // Pointer to the start of the array items in the block of memory @@ -140,27 +80,10 @@ template struct Dqn_SArray T const *begin() const { return data; } T const *end () const { return data + size; } }; - -// NOTE: Setup ===================================================================================== -DQN_API template Dqn_SArray Dqn_SArray_Init (Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem); -DQN_API template Dqn_SArray Dqn_SArray_InitCArrayCopy (Dqn_Arena *arena, T const (&array)[N]); -DQN_API template bool Dqn_SArray_IsValid (Dqn_SArray const *array); - -// NOTE: API ======================================================================================= -DQN_API template T * Dqn_SArray_MakeArray (Dqn_SArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_SArray_Make (Dqn_SArray *array, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_SArray_AddArray (Dqn_SArray *array, T const *items, Dqn_usize count); -DQN_API template T * Dqn_SArray_Add (Dqn_SArray *array, T const &item); -DQN_API template T * Dqn_SArray_InsertArray(Dqn_SArray *array, Dqn_usize index, T const *items, Dqn_usize count); -DQN_API template T * Dqn_SArray_Insert (Dqn_SArray *array, Dqn_usize index, T const &item); -DQN_API template T Dqn_SArray_PopFront (Dqn_SArray *array, Dqn_usize count); -DQN_API template T Dqn_SArray_PopBack (Dqn_SArray *array, Dqn_usize count); -DQN_API template Dqn_ArrayEraseResult Dqn_SArray_EraseRange (Dqn_SArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); -DQN_API template void Dqn_SArray_Clear (Dqn_SArray *array); #endif // !defined(DQN_NO_SARRAY) #if !defined(DQN_NO_FARRAY) -// NOTE: [$FARR] Dqn_FArray ======================================================================== +// NOTE: [$FARR] Dqn_FArray //////////////////////////////////////////////////////////////////////// template struct Dqn_FArray { T data[N]; // Pointer to the start of the array items in the block of memory @@ -172,160 +95,14 @@ template struct Dqn_FArray T const *end () const { return data + size; } }; -// NOTE: Setup ===================================================================================== -DQN_API template Dqn_FArray Dqn_FArray_Init (T const *array, Dqn_usize count); -DQN_API template bool Dqn_FArray_IsValid (Dqn_FArray const *array); -DQN_API template Dqn_usize Dqn_FArray_Max (Dqn_FArray const *) { return N; } - -// NOTE: API ======================================================================================= -DQN_API template T * Dqn_FArray_MakeArray (Dqn_FArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_FArray_Make (Dqn_FArray *array, Dqn_ZeroMem zero_mem); -DQN_API template T * Dqn_FArray_AddArray (Dqn_FArray *array, T const *items, Dqn_usize count); -DQN_API template T * Dqn_FArray_Add (Dqn_FArray *array, T const &item); -DQN_API template T * Dqn_FArray_InsertArray(Dqn_FArray *array, T const &item, Dqn_usize index); -DQN_API template T * Dqn_FArray_Insert (Dqn_FArray *array, Dqn_usize index, T const &item); -DQN_API template T Dqn_FArray_PopFront (Dqn_FArray *array, Dqn_usize count); -DQN_API template T Dqn_FArray_PopBack (Dqn_FArray *array, Dqn_usize count); -DQN_API template Dqn_ArrayFindResult Dqn_FArray_Find (Dqn_FArray *array, T const &find); -DQN_API template Dqn_ArrayEraseResult Dqn_FArray_EraseRange (Dqn_FArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); -DQN_API template void Dqn_FArray_Clear (Dqn_FArray *array); +template using Dqn_FArray8 = Dqn_FArray; +template using Dqn_FArray16 = Dqn_FArray; +template using Dqn_FArray32 = Dqn_FArray; +template using Dqn_FArray64 = Dqn_FArray; #endif // !defined(DQN_NO_FARRAY) -#if !defined(DQN_NO_SLICE) -// NOTE: [$SLIC] Dqn_Slice ========================================================================= -template Dqn_Slice Dqn_Slice_Init (T* const data, Dqn_usize size); -template Dqn_Slice Dqn_Slice_InitCArrayCopy(Dqn_Arena *arena, T const *(&array)[N]); -template Dqn_Slice Dqn_Slice_Alloc (Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem); - -#endif // !defined(DQN_NO_SLICE) - #if !defined(DQN_NO_DSMAP) -// NOTE: [$DMAP] Dqn_DSMap ========================================================================= -// A hash table configured using the presets recommended by Demitri Spanos -// from the Handmade Network (HMN), -// -// - power of two capacity -// - grow by 2x on load >= 75% -// - open-addressing with linear probing -// - separate large values (esp. variable length values) into a separate table -// - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) -// - chain-repair on delete (rehash items in the probe chain after delete) -// - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) -// -// Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 -// -// This hash-table stores slots (values) separate from the hash mapping. Hashes -// are mapped to slots using the hash-to-slot array which is an array of slot -// indexes. This array intentionally only stores indexes to maximise usage -// of the cache line. Linear probing on collision will only cost a couple of -// cycles to fetch from L1 cache the next slot index to attempt. -// -// The slots array stores values contiguously, non-sorted allowing iteration of -// the map. On element erase, the last element is swapped into the deleted -// element causing the non-sorted property of this table. -// -// The 0th slot (DQN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved for a -// sentinel value, e.g. all zeros value. After map initialisation the 'occupied' -// value of the array will be set to 1 to exclude the sentinel from the -// capacity of the table. Skip the first value if you are iterating the hash -// table! -// -// This hash-table accept either a U64 or a buffer (ptr + len) as the key. In -// practice this covers a majority of use cases (with string, buffer and number -// keys). It also allows us to minimise our C++ templates to only require 1 -// variable which is the Value part of the hash-table simplifying interface -// complexity and cruft brought by C++. -// -// Keys are value-copied into the hash-table. If the key uses a pointer to a -// buffer, this buffer must be valid throughout the lifetime of the hash table! -// -// NOTE: API -// -// - All maps must be created by calling `DSMap_Init()` with the desired size -// and the memory allocated for the table can be freed by called -// `DSMap_Deinit()`. -// -// - Functions that return a pointer or boolean will always return null or false -// if the passed in map is invalid e.g. `DSMap_IsValid()` returns false. - -// @proc Dqn_DSMap_Init -// @param size[in] The number of slots in the table. This size must be a -// power-of-two or otherwise an assert will be triggered. -// @return The hash table. On memory allocation failure, the table will be -// zero initialised whereby calling Dqn_DSMap_IsValid() will return false. - -// @proc Dqn_DSMap_Deinit -// @desc Free the memory allocated by the table - -// @proc Dqn_DSMap_IsValid -// @desc Verify that the table is in a valid state (e.g. initialised -// correctly). - -// @proc Dqn_DSMap_Hash -// @desc Hash the input key using the custom hash function if it's set on the -// map, otherwise uses the default hashing function (32bit Murmur3). -// -// @proc Dqn_DSMap_HashToSlotIndex -// @desc Calculate the index into the map's `slots` array from the given hash. - -// @proc Dqn_DSMap_Find -// @desc Find the slot in the map's `slots` array corresponding to the given -// key and hash. If the map does not contain the key `found` is set to false -// and `slot` and `value` are null. -// -// `Find` returns the value. -// `FindSlot` returns the map's hash table slot. - -// @proc Dqn_DSMap_Make, Dqn_DSMap_Set -// @desc Same as `DSMap_Find*` except if the key does not exist in the table, -// a hash-table slot will be made. -// -// `Make` assigns the key to the table and returns the hash table slot's value. -// `Set` assigns the key-value to the table and returns the hash table slot's value. -// -// If by adding the key-value pair to the table puts the table over 75% load, -// the table will be grown to 2x the current the size before insertion -// completes. -// -// `found` will be set to true if the item already existed in the map before, -// or false if the item was just created by this call. - -// @proc Dqn_DSMap_Resize -// @desc Resize the table and move all elements to the new map. -// the elements currently set in the -// @param size[in] New size of the table, must be a power of two. -// @return False if memory allocation fails, or the requested size is smaller -// than the current number of elements in the map to resize. True otherwise. - -// @proc Dqn_DSMap_Erase -// @desc Remove the key-value pair from the table. If by erasing the key-value -// pair from the table puts the table under 25% load, the table will be shrunk -// by 1/2 the current size after erasing. The table will not shrink below the -// initial size that the table was initialised as. - -// @proc Dqn_DSMap_KeyCStringLit, Dqn_DSMap_KeyU64, Dqn_DSMap_KeyBuffer, -// Dqn_DSMap_KeyStr8 Dqn_DSMap_KeyStr8Copy -// @desc Create a hash-table key given -// -// `KeyCStringLit` a cstring literal -// `KeyU64` a u64 -// `KeyBuffer` a (ptr+len) slice of bytes -// `KeyStr8` a Dqn_Str8 string -// `KeyStr8Copy` a Dqn_Str8 string that is copied first using the allocator -// -// If the key points to an array of bytes, the lifetime of those bytes *must* -// remain valid throughout the lifetime of the map as the pointers are value -// copied into the hash table! - -// @proc Dqn_DSMap_KeyU64NoHash -// @desc Create a hash-table key given the uint64. This u64 is *not* hashed to -// map values into the table. This is useful if you already have a source of -// data that is already sufficiently uniformly distributed already (e.g. -// using 8 bytes taken from a SHA256 hash as the key). -// -// This value will be used directly but truncated to 32 bits as the table uses -// 32 bit hashes for mapping keys to values. - +// NOTE: [$DMAP] Dqn_DSMap ///////////////////////////////////////////////////////////////////////// enum Dqn_DSMapKeyType { Dqn_DSMapKeyType_Invalid, @@ -347,7 +124,8 @@ struct Dqn_DSMapKey } payload; }; -template struct Dqn_DSMapSlot +template +struct Dqn_DSMapSlot { Dqn_DSMapKey key; ///< Hash table lookup key T value; ///< Hash table value @@ -360,81 +138,22 @@ template struct Dqn_DSMap Dqn_DSMapSlot *slots; // Values of the array stored contiguously, non-sorted order uint32_t size; // Total capacity of the map and is a power of two uint32_t occupied; // Number of slots used in the hash table - Dqn_Allocator allocator; // Backing allocator for the hash table + Dqn_Arena *arena; // Backing arena for the hash table uint32_t initial_size; // Initial map size, map cannot shrink on erase below this size Dqn_DSMapHashFunction *hash_function; // Custom hashing function to use if field is set uint32_t hash_seed; // Seed for the hashing function, when 0, DQN_DS_MAP_DEFAULT_HASH_SEED is used }; -// NOTE: Setup ===================================================================================== -DQN_API template Dqn_DSMap Dqn_DSMap_Init (uint32_t size); -DQN_API template void Dqn_DSMap_Deinit (Dqn_DSMap *map); -DQN_API template bool Dqn_DSMap_IsValid (Dqn_DSMap const *map); - -// NOTE: Hash ====================================================================================== -DQN_API template uint32_t Dqn_DSMap_Hash (Dqn_DSMap const *map, Dqn_DSMapKey key); -DQN_API template uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap const *map, Dqn_DSMapKey key); - -// NOTE: Insert ==================================================================================== -template -struct Dqn_DSMapResult +template struct Dqn_DSMapResult { bool found; Dqn_DSMapSlot *slot; T *value; }; - -DQN_API template Dqn_DSMapResult Dqn_DSMap_Find (Dqn_DSMap const *map, Dqn_DSMapKey key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_Make (Dqn_DSMap *map, Dqn_DSMapKey key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_Set (Dqn_DSMap *map, Dqn_DSMapKey key, T const &value); - -DQN_API template Dqn_DSMapResult Dqn_DSMap_FindKeyU64 (Dqn_DSMap const *map, uint64_t key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_MakeKeyU64 (Dqn_DSMap *map, uint64_t key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_SetKeyU64 (Dqn_DSMap *map, uint64_t key, T const &value); - -DQN_API template Dqn_DSMapResult Dqn_DSMap_FindKeyStr8 (Dqn_DSMap const *map, Dqn_Str8 key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8 (Dqn_DSMap *map, Dqn_Str8 key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_SetKeyStr8 (Dqn_DSMap *map, Dqn_Str8 key, T const &value); - -DQN_API template Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8Copy(Dqn_DSMap *map, Dqn_Allocator allocator, Dqn_Str8 key); -DQN_API template Dqn_DSMapResult Dqn_DSMap_SetKeyStr8Copy (Dqn_DSMap *map, Dqn_Allocator allocator, Dqn_Str8 key, T const &value); - -DQN_API template bool Dqn_DSMap_Resize (Dqn_DSMap *map, uint32_t size); -DQN_API template bool Dqn_DSMap_Erase (Dqn_DSMap *map, Dqn_DSMapKey key); - -// NOTE: Table Keys ================================================================================ -DQN_API template Dqn_DSMapKey Dqn_DSMap_KeyBuffer (Dqn_DSMap const *map, void const *data, uint32_t size); -DQN_API template Dqn_DSMapKey Dqn_DSMap_KeyU64 (Dqn_DSMap const *map, uint64_t u64); -DQN_API template Dqn_DSMapKey Dqn_DSMap_KeyStr8 (Dqn_DSMap const *map, Dqn_Str8 string); -DQN_API template Dqn_DSMapKey Dqn_DSMap_KeyStr8Copy (Dqn_DSMap const *map, Dqn_Allocator allocator, Dqn_Str8 string); -#define Dqn_DSMap_KeyCStr8(map, string) Dqn_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) -DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash (uint64_t u64); -DQN_API bool Dqn_DSMap_KeyEquals (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); -DQN_API bool operator== (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); #endif // !defined(DQN_NO_DSMAP) #if !defined(DQN_NO_LIST) -// NOTE: [$LIST] Dqn_List ========================================================================== -// -// NOTE: API -// @proc Dqn_List_At -// @param at_chunk[out] (Optional) The chunk that the index belongs to will -// be set in this parameter if given -// @return The element, or null pointer if it is not a valid index. - -// @proc Dqn_List_Iterate -// @desc Produce an iterator for the data in the list -// -// @param[in] start_index The index to start iterating from -// -#if 0 - Dqn_List list = {}; - for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&list, &it, 0);) - { - int *item = it.data; - } -#endif - +// NOTE: [$LIST] Dqn_List ////////////////////////////////////////////////////////////////////////// template struct Dqn_ListChunk { T *data; @@ -461,26 +180,150 @@ template struct Dqn_List Dqn_ListChunk *head; Dqn_ListChunk *tail; }; - -// NOTE: API ======================================================================================= -template Dqn_List Dqn_List_Init (Dqn_Arena *arena, Dqn_usize chunk_size); -template Dqn_List Dqn_List_InitCArrayCopy (Dqn_Arena *arena, Dqn_usize chunk_size, T const (&array)[N]); -template T * Dqn_List_At (Dqn_List *list, Dqn_usize index, Dqn_ListChunk *at_chunk); -template bool Dqn_List_Iterate (Dqn_List *list, Dqn_ListIterator *it, Dqn_usize start_index); - -template T * Dqn_List_Make (Dqn_List *list, Dqn_usize count); -template T * Dqn_List_Add (Dqn_List *list, T const &value); -template void Dqn_List_AddList (Dqn_List *list, Dqn_List other); -template Dqn_Slice Dqn_List_ToSliceCopy(Dqn_List const *list, Dqn_Arena* arena); #endif // !defined(DQN_NO_LIST) -// NOTE: [$CARR] Dqn_CArray ======================================================================== +template Dqn_ArrayEraseResult Dqn_CArray_EraseRange (T *data, Dqn_usize *size, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); +template T * Dqn_CArray_MakeArray (T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize count, Dqn_ZeroMem zero_mem); +template T * Dqn_CArray_InsertArray (T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize index, T const *items, Dqn_usize count); +template T Dqn_CArray_PopFront (T *data, Dqn_usize *size, Dqn_usize count); +template T Dqn_CArray_PopBack (T *data, Dqn_usize *size, Dqn_usize count); +template Dqn_ArrayFindResult Dqn_CArray_Find (T *data, Dqn_usize size, T const &value); + +#if !defined(DQN_NO_VARRAY) +template Dqn_VArray Dqn_VArray_InitByteSize (Dqn_usize byte_size, uint8_t arena_flags); +template Dqn_VArray Dqn_VArray_Init (Dqn_usize max, uint8_t arena_flags); +template void Dqn_VArray_Deinit (Dqn_VArray *array); +template bool Dqn_VArray_IsValid (Dqn_VArray const *array); +template bool Dqn_VArray_Reserve (Dqn_VArray *array, Dqn_usize count); +template T * Dqn_VArray_AddArray (Dqn_VArray *array, T const *items, Dqn_usize count); +template T * Dqn_VArray_AddCArray (Dqn_VArray *array, T const (&items)[N]); +template T * Dqn_VArray_Add (Dqn_VArray *array, T const &item); +#define Dqn_VArray_AddArrayAssert(...) DQN_HARD_ASSERT(Dqn_VArray_AddArray(__VA_ARGS__)) +#define Dqn_VArray_AddCArrayAssert(...) DQN_HARD_ASSERT(Dqn_VArray_AddCArray(__VA_ARGS__)) +#define Dqn_VArray_AddAssert(...) DQN_HARD_ASSERT(Dqn_VArray_Add(__VA_ARGS__)) +template T * Dqn_VArray_MakeArray (Dqn_VArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); +template T * Dqn_VArray_Make (Dqn_VArray *array, Dqn_ZeroMem zero_mem); +#define Dqn_VArray_MakeArrayAssert(...) DQN_HARD_ASSERT(Dqn_VArray_MakeArray(__VA_ARGS__)) +#define Dqn_VArray_MakeAssert(...) DQN_HARD_ASSERT(Dqn_VArray_Make(__VA_ARGS__)) +template T * Dqn_VArray_InsertArray (Dqn_VArray *array, Dqn_usize index, T const *items, Dqn_usize count); +template T * Dqn_VArray_InsertCArray (Dqn_VArray *array, Dqn_usize index, T const (&items)[N]); +template T * Dqn_VArray_Insert (Dqn_VArray *array, Dqn_usize index, T const &item); +#define Dqn_VArray_InsertArrayAssert(...) DQN_HARD_ASSERT(Dqn_VArray_InsertArray(__VA_ARGS__)) +#define Dqn_VArray_InsertCArrayAssert(...) DQN_HARD_ASSERT(Dqn_VArray_InsertCArray(__VA_ARGS__)) +#define Dqn_VArray_InsertAssert(...) DQN_HARD_ASSERT(Dqn_VArray_Insert(__VA_ARGS__)) +template T Dqn_VArray_PopFront (Dqn_VArray *array, Dqn_usize count); +template T Dqn_VArray_PopBack (Dqn_VArray *array, Dqn_usize count); +template Dqn_ArrayEraseResult Dqn_VArray_EraseRange (Dqn_VArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); +template void Dqn_VArray_Clear (Dqn_VArray *array, Dqn_ZeroMem zero_mem); +#endif // !defined(DQN_NO_VARRAY) +#if !defined(DQN_NO_SARRAY) +template Dqn_SArray Dqn_SArray_Init (Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem); +template Dqn_SArray Dqn_SArray_InitCArray (Dqn_Arena *arena, T const (&array)[N]); +template bool Dqn_SArray_IsValid (Dqn_SArray const *array); +template Dqn_Slice Dqn_SArray_Slice (Dqn_SArray const *array); +template T * Dqn_SArray_AddArray (Dqn_SArray *array, T const *items, Dqn_usize count); +template T * Dqn_SArray_AddCArray (Dqn_SArray *array, T const (&items)[N]); +template T * Dqn_SArray_Add (Dqn_SArray *array, T const &item); +#define Dqn_SArray_AddArrayAssert(...) DQN_HARD_ASSERT(Dqn_SArray_AddArray(__VA_ARGS__)) +#define Dqn_SArray_AddCArrayAssert(...) DQN_HARD_ASSERT(Dqn_SArray_AddCArray(__VA_ARGS__)) +#define Dqn_SArray_AddAssert(...) DQN_HARD_ASSERT(Dqn_SArray_Add(__VA_ARGS__)) +template T * Dqn_SArray_MakeArray (Dqn_SArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); +template T * Dqn_SArray_Make (Dqn_SArray *array, Dqn_ZeroMem zero_mem); +#define Dqn_SArray_MakeArrayAssert(...) DQN_HARD_ASSERT(Dqn_SArray_MakeArray(__VA_ARGS__)) +#define Dqn_SArray_MakeAssert(...) DQN_HARD_ASSERT(Dqn_SArray_Make(__VA_ARGS__)) +template T * Dqn_SArray_InsertArray (Dqn_SArray *array, Dqn_usize index, T const *items, Dqn_usize count); +template T * Dqn_SArray_InsertCArray (Dqn_SArray *array, Dqn_usize index, T const (&items)[N]); +template T * Dqn_SArray_Insert (Dqn_SArray *array, Dqn_usize index, T const &item); +#define Dqn_SArray_InsertArrayAssert(...) DQN_HARD_ASSERT(Dqn_SArray_InsertArray(__VA_ARGS__)) +#define Dqn_SArray_InsertCArrayAssert(...) DQN_HARD_ASSERT(Dqn_SArray_InsertCArray(__VA_ARGS__)) +#define Dqn_SArray_InsertAssert(...) DQN_HARD_ASSERT(Dqn_SArray_Insert(__VA_ARGS__)) +template T Dqn_SArray_PopFront (Dqn_SArray *array, Dqn_usize count); +template T Dqn_SArray_PopBack (Dqn_SArray *array, Dqn_usize count); +template Dqn_ArrayEraseResult Dqn_SArray_EraseRange (Dqn_SArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); +template void Dqn_SArray_Clear (Dqn_SArray *array); +#endif // !defined(DQN_NO_SARRAY) +#if !defined(DQN_NO_FARRAY) +template Dqn_FArray Dqn_FArray_Init (T const *array, Dqn_usize count); +template Dqn_FArray Dqn_FArray_InitCArray (T const (&items)[K]); +template bool Dqn_FArray_IsValid (Dqn_FArray const *array); +template Dqn_usize Dqn_FArray_Max (Dqn_FArray const *) { return N; } +template Dqn_Slice Dqn_FArray_Slice (Dqn_FArray const *array); +template T * Dqn_FArray_AddArray (Dqn_FArray *array, T const *items, Dqn_usize count); +template T * Dqn_FArray_AddCArray (Dqn_FArray *array, T const (&items)[K]); +template T * Dqn_FArray_Add (Dqn_FArray *array, T const &item); +#define Dqn_FArray_AddArrayAssert(...) DQN_HARD_ASSERT(Dqn_FArray_AddArray(__VA_ARGS__)) +#define Dqn_FArray_AddCArrayAssert(...) DQN_HARD_ASSERT(Dqn_FArray_AddCArray(__VA_ARGS__)) +#define Dqn_FArray_AddAssert(...) DQN_HARD_ASSERT(Dqn_FArray_Add(__VA_ARGS__)) +template T * Dqn_FArray_MakeArray (Dqn_FArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem); +template T * Dqn_FArray_Make (Dqn_FArray *array, Dqn_ZeroMem zero_mem); +#define Dqn_FArray_MakeArrayAssert(...) DQN_HARD_ASSERT(Dqn_FArray_MakeArray(__VA_ARGS__)) +#define Dqn_FArray_MakeAssert(...) DQN_HARD_ASSERT(Dqn_FArray_Make(__VA_ARGS__)) +template T * Dqn_FArray_InsertArray (Dqn_FArray *array, T const &item, Dqn_usize index); +template T * Dqn_FArray_InsertCArray (Dqn_FArray *array, Dqn_usize index, T const (&items)[K]); +template T * Dqn_FArray_Insert (Dqn_FArray *array, Dqn_usize index, T const &item); +#define Dqn_FArray_InsertArrayAssert(...) DQN_HARD_ASSERT(Dqn_FArray_InsertArray(__VA_ARGS__)) +#define Dqn_FArray_InsertAssert(...) DQN_HARD_ASSERT(Dqn_FArray_Insert(__VA_ARGS__)) +template T Dqn_FArray_PopFront (Dqn_FArray *array, Dqn_usize count); +template T Dqn_FArray_PopBack (Dqn_FArray *array, Dqn_usize count); +template Dqn_ArrayFindResult Dqn_FArray_Find (Dqn_FArray *array, T const &find); +template Dqn_ArrayEraseResult Dqn_FArray_EraseRange (Dqn_FArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase); +template void Dqn_FArray_Clear (Dqn_FArray *array); +#endif // !defined(DQN_NO_FARRAY) +#if !defined(DQN_NO_SLICE) +template Dqn_Slice Dqn_Slice_Init (T* const data, Dqn_usize size); +template Dqn_Slice Dqn_Slice_InitCArray (Dqn_Arena *arena, T const (&array)[N]); +template Dqn_Slice Dqn_Slice_Alloc (Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem); + Dqn_Str8 Dqn_Slice_Str8Render (Dqn_Arena *arena, Dqn_Slice array, Dqn_Str8 separator); + Dqn_Str8 Dqn_Slice_Str8RenderSpaceSeparated (Dqn_Arena *arena, Dqn_Slice array); +#endif // !defined(DQN_NO_SLICE) +#if !defined(DQN_NO_DSMAP) +template Dqn_DSMap Dqn_DSMap_Init (Dqn_Arena *arena, uint32_t size); +template void Dqn_DSMap_Deinit (Dqn_DSMap *map, Dqn_ZeroMem zero_mem); +template bool Dqn_DSMap_IsValid (Dqn_DSMap const *map); +template uint32_t Dqn_DSMap_Hash (Dqn_DSMap const *map, Dqn_DSMapKey key); +template uint32_t Dqn_DSMap_HashToSlotIndex (Dqn_DSMap const *map, Dqn_DSMapKey key); +template Dqn_DSMapResult Dqn_DSMap_Find (Dqn_DSMap const *map, Dqn_DSMapKey key); +template Dqn_DSMapResult Dqn_DSMap_Make (Dqn_DSMap *map, Dqn_DSMapKey key); +template Dqn_DSMapResult Dqn_DSMap_Set (Dqn_DSMap *map, Dqn_DSMapKey key, T const &value); +template Dqn_DSMapResult Dqn_DSMap_FindKeyU64 (Dqn_DSMap const *map, uint64_t key); +template Dqn_DSMapResult Dqn_DSMap_MakeKeyU64 (Dqn_DSMap *map, uint64_t key); +template Dqn_DSMapResult Dqn_DSMap_SetKeyU64 (Dqn_DSMap *map, uint64_t key, T const &value); +template Dqn_DSMapResult Dqn_DSMap_FindKeyStr8 (Dqn_DSMap const *map, Dqn_Str8 key); +template Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8 (Dqn_DSMap *map, Dqn_Str8 key); +template Dqn_DSMapResult Dqn_DSMap_SetKeyStr8 (Dqn_DSMap *map, Dqn_Str8 key, T const &value); +template Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8Copy (Dqn_DSMap *map, Dqn_Arena *arena, Dqn_Str8 key); +template Dqn_DSMapResult Dqn_DSMap_SetKeyStr8Copy (Dqn_DSMap *map, Dqn_Arena *arena, Dqn_Str8 key, T const &value); +template bool Dqn_DSMap_Resize (Dqn_DSMap *map, uint32_t size); +template bool Dqn_DSMap_Erase (Dqn_DSMap *map, Dqn_DSMapKey key); +template Dqn_DSMapKey Dqn_DSMap_KeyBuffer (Dqn_DSMap const *map, void const *data, uint32_t size); +template Dqn_DSMapKey Dqn_DSMap_KeyU64 (Dqn_DSMap const *map, uint64_t u64); +template Dqn_DSMapKey Dqn_DSMap_KeyStr8 (Dqn_DSMap const *map, Dqn_Str8 string); +template Dqn_DSMapKey Dqn_DSMap_KeyStr8Copy (Dqn_DSMap const *map, Dqn_Arena *arena, Dqn_Str8 string); +#define Dqn_DSMap_KeyCStr8(map, string) Dqn_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) +DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash (uint64_t u64); +DQN_API bool Dqn_DSMap_KeyEquals (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); +DQN_API bool operator== (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); +#endif // !defined(DQN_NO_DSMAP) +#if !defined(DQN_NO_LIST) +template Dqn_List Dqn_List_Init (Dqn_Arena *arena, Dqn_usize chunk_size); +template Dqn_List Dqn_List_InitCArray (Dqn_Arena *arena, Dqn_usize chunk_size, T const (&array)[N]); +template T * Dqn_List_At (Dqn_List *list, Dqn_usize index, Dqn_ListChunk *at_chunk); +template bool Dqn_List_Iterate (Dqn_List *list, Dqn_ListIterator *it, Dqn_usize start_index); +template T * Dqn_List_Make (Dqn_List *list, Dqn_usize count); +template T * Dqn_List_Add (Dqn_List *list, T const &value); +template void Dqn_List_AddList (Dqn_List *list, Dqn_List other); +template Dqn_Slice Dqn_List_ToSliceCopy (Dqn_List const *list, Dqn_Arena* arena); +#endif // !defined(DQN_NO_LIST) + +// NOTE: [$CARR] Dqn_CArray //////////////////////////////////////////////////////////////////////// template Dqn_ArrayEraseResult Dqn_CArray_EraseRange(T* data, Dqn_usize *size, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) { Dqn_ArrayEraseResult result = {}; if (!data || !size || *size == 0 || count == 0) return result; + DQN_ASSERTF(count != -1, "There's a bug with negative element erases, see the Dqn_VArray section in dqn_docs.cpp"); + // NOTE: Caculate the end index of the erase range Dqn_isize abs_count = DQN_ABS(count); Dqn_usize end_index = 0; @@ -531,24 +374,24 @@ template T *Dqn_CArray_MakeArray(T* data, Dqn_usize *size, Dqn_usiz if (!data || !size || count == 0) return nullptr; - if (!DQN_CHECKF((*size + count) <= max, "Array is out of memory")) + if (!DQN_CHECKF((*size + count) <= max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, *size, max)) return nullptr; // TODO: Use placement new? Why doesn't this work? T *result = data + *size; *size += count; if (zero_mem == Dqn_ZeroMem_Yes) - DQN_MEMSET(result, DQN_MEMSET_BYTE, sizeof(*result) * count); + DQN_MEMSET(result, 0, sizeof(*result) * count); return result; } -DQN_API template T *Dqn_CArray_InsertArray(T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize index, T const *items, Dqn_usize count) +template T *Dqn_CArray_InsertArray(T *data, Dqn_usize *size, Dqn_usize max, Dqn_usize index, T const *items, Dqn_usize count) { T *result = nullptr; if (!data || !size || !items || count <= 0 || ((*size + count) > max)) return result; - Dqn_usize clamped_index = *size ? DQN_MIN(index, *size) : 0; + Dqn_usize clamped_index = DQN_MIN(index, *size); if (clamped_index != *size) { char const *src = DQN_CAST(char *)(data + clamped_index); char const *dest = DQN_CAST(char *)(data + (clamped_index + count)); @@ -606,51 +449,36 @@ template Dqn_ArrayFindResult Dqn_CArray_Find(T *data, Dqn_usize } #if !defined(DQN_NO_VARRAY) -// NOTE: [$VARR] Dqn_VArray ======================================================================== -DQN_API template Dqn_VArray Dqn_VArray_InitByteSize(Dqn_Arena *arena, Dqn_usize byte_size) +// NOTE: [$VARR] Dqn_VArray //////////////////////////////////////////////////////////////////////// +template Dqn_VArray Dqn_VArray_InitByteSize(Dqn_usize byte_size, uint8_t arena_flags) { Dqn_VArray result = {}; - result.block = Dqn_Arena_Grow(arena, byte_size, 0 /*commit*/, Dqn_MemBlockFlag_ArenaPrivate | Dqn_MemBlockFlag_AllocsAreContiguous); - result.data = DQN_CAST(T *)Dqn_MemBlock_Alloc(result.block, /*size*/ 0, alignof(T), Dqn_ZeroMem_No); - result.max = (result.block->size - result.block->used) / sizeof(T); + result.arena = Dqn_Arena_InitSize(DQN_ARENA_HEADER_SIZE + byte_size, 0 /*commit*/, arena_flags | Dqn_ArenaFlag_NoGrow | Dqn_ArenaFlag_NoPoison); + result.data = DQN_CAST(T *)(DQN_CAST(char *)result.arena.curr + result.arena.curr->used); + result.max = (result.arena.curr->reserve - result.arena.curr->used) / sizeof(T); return result; } -DQN_API template Dqn_VArray Dqn_VArray_Init(Dqn_Arena *arena, Dqn_usize max) +template Dqn_VArray Dqn_VArray_Init(Dqn_usize max, uint8_t arena_flags) { - Dqn_VArray result = Dqn_VArray_InitByteSize(arena, max * sizeof(T)); + Dqn_VArray result = Dqn_VArray_InitByteSize(max * sizeof(T), arena_flags); DQN_ASSERT(result.max >= max); return result; } -DQN_API template bool Dqn_VArray_IsValid(Dqn_VArray const *array) +template void Dqn_VArray_Deinit(Dqn_VArray *array) { - bool result = array && array->data && array->size <= array->max && array->block; + Dqn_Arena_Deinit(&array->arena); + *array = {}; +} + +template bool Dqn_VArray_IsValid(Dqn_VArray const *array) +{ + bool result = array && array->data && array->size <= array->max && array->arena.curr; return result; } -DQN_API template T *Dqn_VArray_MakeArray(Dqn_VArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) -{ - if (!Dqn_VArray_IsValid(array)) - return nullptr; - - if (!DQN_CHECKF((array->size + count) < array->max, "Array is out of virtual memory")) - return nullptr; - - // TODO: Use placement new? Why doesn't this work? - T *result = Dqn_MemBlock_NewArray(array->block, T, count, zero_mem); - if (result) - array->size += count; - return result; -} - -DQN_API template T *Dqn_VArray_Make(Dqn_VArray *array, Dqn_ZeroMem zero_mem) -{ - T *result = Dqn_VArray_MakeArray(array, 1, zero_mem); - return result; -} - -DQN_API template T *Dqn_VArray_AddArray(Dqn_VArray *array, T const *items, Dqn_usize count) +template T *Dqn_VArray_AddArray(Dqn_VArray *array, T const *items, Dqn_usize count) { T *result = Dqn_VArray_MakeArray(array, count, Dqn_ZeroMem_No); if (result) @@ -658,72 +486,109 @@ DQN_API template T *Dqn_VArray_AddArray(Dqn_VArray *array, T con return result; } -DQN_API template T *Dqn_VArray_Add(Dqn_VArray *array, T const &item) +template T *Dqn_VArray_AddCArray(Dqn_VArray *array, T const (&items)[N]) +{ + T *result = Dqn_VArray_AddArray(array, items, N); + return result; +} + +template T *Dqn_VArray_Add(Dqn_VArray *array, T const &item) { T *result = Dqn_VArray_AddArray(array, &item, 1); return result; } -DQN_API template T *Dqn_VArray_InsertArray(Dqn_VArray *array, Dqn_usize index, T const *items, Dqn_usize count) +template T *Dqn_VArray_MakeArray(Dqn_VArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) +{ + if (!Dqn_VArray_IsValid(array)) + return nullptr; + + if (!DQN_CHECKF((array->size + count) < array->max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, array->size, array->max)) + return nullptr; + + // TODO: Use placement new? Why doesn't this work? + + uint8_t prev_flags = array->arena.flags; + array->arena.flags |= (Dqn_ArenaFlag_NoGrow | Dqn_ArenaFlag_NoPoison); + T *result = Dqn_Arena_NewArray(&array->arena, T, count, zero_mem); + array->arena.flags = prev_flags; + if (result) + array->size += count; + return result; +} + +template T *Dqn_VArray_Make(Dqn_VArray *array, Dqn_ZeroMem zero_mem) +{ + T *result = Dqn_VArray_MakeArray(array, 1, zero_mem); + return result; +} + +template T *Dqn_VArray_InsertArray(Dqn_VArray *array, Dqn_usize index, T const *items, Dqn_usize count) { T *result = nullptr; if (!Dqn_VArray_IsValid(array)) return result; - Dqn_VArray_Reserve(array, array->size + count); - result = Dqn_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + if (Dqn_VArray_Reserve(array, count)) + result = Dqn_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); return result; } -DQN_API template T *Dqn_VArray_Insert(Dqn_VArray *array, Dqn_usize index, T const &item) +template T *Dqn_VArray_InsertCArray(Dqn_VArray *array, Dqn_usize index, T const (&items)[N]) +{ + T *result = Dqn_VArray_InsertArray(array, index, items, N); + return result; +} + +template T *Dqn_VArray_Insert(Dqn_VArray *array, Dqn_usize index, T const &item) { T *result = Dqn_VArray_InsertArray(array, index, &item, 1); return result; } -DQN_API template T *Dqn_VArray_PopFront(Dqn_VArray *array, Dqn_usize count) +template T *Dqn_VArray_PopFront(Dqn_VArray *array, Dqn_usize count) { T *result = Dqn_CArray_PopFront(array->data, &array->size, count); return result; } -DQN_API template T *Dqn_VArray_PopBack(Dqn_VArray *array, Dqn_usize count) +template T *Dqn_VArray_PopBack(Dqn_VArray *array, Dqn_usize count) { T *result = Dqn_CArray_PopBack(array->data, &array->size, count); return result; } -DQN_API template Dqn_ArrayEraseResult Dqn_VArray_EraseRange(Dqn_VArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) +template Dqn_ArrayEraseResult Dqn_VArray_EraseRange(Dqn_VArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) { Dqn_ArrayEraseResult result = {}; if (!Dqn_VArray_IsValid(array)) return result; result = Dqn_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); - Dqn_MemBlock_Pop(array->block, result.items_erased * sizeof(T)); + Dqn_Arena_Pop(&array->arena, result.items_erased * sizeof(T)); return result; } -DQN_API template void Dqn_VArray_Clear(Dqn_VArray *array, Dqn_ZeroMem zero_mem) +template void Dqn_VArray_Clear(Dqn_VArray *array, Dqn_ZeroMem zero_mem) { if (array) { if (zero_mem == Dqn_ZeroMem_Yes) DQN_MEMSET(array->data, 0, array->size * sizeof(T)); - Dqn_MemBlock_PopTo(array->block, 0); + Dqn_Arena_PopTo(&array->arena, 0); array->size = 0; } } -DQN_API template void Dqn_VArray_Reserve(Dqn_VArray *array, Dqn_usize count) +template bool Dqn_VArray_Reserve(Dqn_VArray *array, Dqn_usize count) { if (!Dqn_VArray_IsValid(array) || count == 0) - return; - - Dqn_Arena_CommitFromBlock(array->block, count * sizeof(T), Dqn_ArenaCommit_EnsureSpace); + return false; + bool result = Dqn_Arena_CommitTo(&array->arena, DQN_ARENA_HEADER_SIZE + (count * sizeof(T))); + return result; } #endif // !defined(DQN_NO_VARRAY) #if !defined(DQN_NO_SARRAY) -// NOTE: [$FARR] Dqn_SArray ======================================================================== -DQN_API template Dqn_SArray Dqn_SArray_Init(Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem) +// NOTE: [$FARR] Dqn_SArray //////////////////////////////////////////////////////////////////////// +template Dqn_SArray Dqn_SArray_Init(Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem) { Dqn_SArray result = {}; if (!arena || !size) @@ -734,7 +599,7 @@ DQN_API template Dqn_SArray Dqn_SArray_Init(Dqn_Arena *arena, Dq return result; } -template Dqn_SArray Dqn_SArray_InitCArrayCopy(Dqn_Arena *arena, T const (&array)[N]) +template Dqn_SArray Dqn_SArray_InitCArray(Dqn_Arena *arena, T const (&array)[N]) { Dqn_SArray result = {}; if (!arena || !N) @@ -742,19 +607,27 @@ template Dqn_SArray Dqn_SArray_InitCArrayCopy(Dqn_Aren result.data = Dqn_Arena_NewArray(arena, T, N, Dqn_ZeroMem_No); if (result.data) { DQN_MEMCOPY(result.data, array, sizeof(T) * N); - result.size = size; - result.max = size; + result.size = N; + result.max = N; } return result; } -DQN_API template bool Dqn_SArray_IsValid(Dqn_SArray const *array) +template bool Dqn_SArray_IsValid(Dqn_SArray const *array) { bool result = array && array->data && array->size <= array->max; return result; } -DQN_API template T *Dqn_SArray_MakeArray(Dqn_SArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) +template Dqn_Slice Dqn_SArray_Slice(Dqn_SArray const *array) +{ + Dqn_Slice result = {}; + if (array) + Dqn_Slice_Init(DQN_CAST(T *)array->data, array->size); + return result; +} + +template T *Dqn_SArray_MakeArray(Dqn_SArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) { if (!Dqn_SArray_IsValid(array)) return nullptr; @@ -762,60 +635,74 @@ DQN_API template T *Dqn_SArray_MakeArray(Dqn_SArray *array, Dqn_ return result; } -DQN_API template T *Dqn_SArray_Make(Dqn_SArray *array, Dqn_ZeroMem zero_mem) +template T *Dqn_SArray_Make(Dqn_SArray *array, Dqn_ZeroMem zero_mem) { T *result = Dqn_SArray_MakeArray(array, 1, zero_mem); return result; } -DQN_API template T *Dqn_SArray_AddArray(Dqn_SArray *array, T const *items, Dqn_usize count) +template T *Dqn_SArray_AddArray(Dqn_SArray *array, T const *items, Dqn_usize count) { T *result = Dqn_SArray_Make(array, count, Dqn_ZeroMem_No); if (result) DQN_MEMCPY(result, items, count * sizeof(T)); } -DQN_API template T *Dqn_SArray_Add(Dqn_SArray *array, T const &item) +template T *Dqn_SArray_AddCArray(Dqn_SArray *array, T const (&items)[N]) +{ + T *result = Dqn_SArray_AddArray(array, items, N); + return result; +} + +template T *Dqn_SArray_Add(Dqn_SArray *array, T const &item) { T *result = Dqn_SArray_AddArray(array, &item, 1); return result; } -DQN_API template T *Dqn_SArray_InsertArray(Dqn_SArray *array, Dqn_usize index, T const *items, Dqn_usize count) +template T *Dqn_SArray_InsertArray(Dqn_SArray *array, Dqn_usize index, T const *items, Dqn_usize count) { + T *result = nullptr; if (!Dqn_SArray_IsValid(array)) return result; - T *result = Dqn_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + result = Dqn_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); return result; } -DQN_API template T *Dqn_SArray_Insert(Dqn_SArray *array, Dqn_usize index, T const &item) +template T *Dqn_SArray_InsertCArray(Dqn_SArray *array, Dqn_usize index, T const (&items)[N]) +{ + T *result = Dqn_SArray_InsertArray(array, index, items, N); + return result; +} + +template T *Dqn_SArray_Insert(Dqn_SArray *array, Dqn_usize index, T const &item) { T *result = Dqn_SArray_InsertArray(array, index, &item, 1); return result; } -DQN_API template T Dqn_SArray_PopFront(Dqn_SArray *array, Dqn_usize count) +template T Dqn_SArray_PopFront(Dqn_SArray *array, Dqn_usize count) { T result = Dqn_CArray_PopFront(array->data, &array->size, count); return result; } -DQN_API template T Dqn_SArray_PopBack(Dqn_SArray *array, Dqn_usize count) +template T Dqn_SArray_PopBack(Dqn_SArray *array, Dqn_usize count) { T result = Dqn_CArray_PopBack(array->data, &array->size, count); return result; } -DQN_API template Dqn_ArrayEraseResult Dqn_SArray_EraseRange(Dqn_SArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) +template Dqn_ArrayEraseResult Dqn_SArray_EraseRange(Dqn_SArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) { + Dqn_ArrayEraseResult result = {}; if (!Dqn_SArray_IsValid(array) || array->size == 0 || count == 0) - return; - Dqn_ArrayEraseResult result = Dqn_CArray_EraseRange(array->data, &array->size, being_index, count, erase); + return result; + result = Dqn_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); return result; } -DQN_API template void Dqn_SArray_Clear(Dqn_SArray *array) +template void Dqn_SArray_Clear(Dqn_SArray *array) { if (array) array->size = 0; @@ -823,35 +710,38 @@ DQN_API template void Dqn_SArray_Clear(Dqn_SArray *array) #endif // !defined(DQN_NO_SARRAY) #if !defined(DQN_NO_FARRAY) -// NOTE: [$FARR] Dqn_FArray ======================================================================== -DQN_API template Dqn_FArray Dqn_FArray_Init(T const *array, Dqn_usize count) +// NOTE: [$FARR] Dqn_FArray //////////////////////////////////////////////////////////////////////// +template Dqn_FArray Dqn_FArray_Init(T const *array, Dqn_usize count) { Dqn_FArray result = {}; bool added = Dqn_FArray_AddArray(&result, array, count); DQN_ASSERT(added); return result; } -DQN_API template bool Dqn_FArray_IsValid(Dqn_FArray const *array) + +template Dqn_FArray Dqn_FArray_InitCArray(T const (&items)[K]) +{ + Dqn_FArray result = {}; + bool added = Dqn_FArray_AddArray(&result, items, K); + DQN_ASSERT(added); + return result; +} + +template bool Dqn_FArray_IsValid(Dqn_FArray const *array) { bool result = array && array->size <= DQN_ARRAY_UCOUNT(array->data); return result; } -DQN_API template T *Dqn_FArray_MakeArray(Dqn_FArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) +template Dqn_Slice Dqn_FArray_Slice(Dqn_FArray const *array) { - if (!Dqn_FArray_IsValid(array)) - return nullptr; - T *result = Dqn_CArray_MakeArray(array->data, &array->size, N, count, zero_mem); + Dqn_Slice result = {}; + if (array) + result = Dqn_Slice_Init(DQN_CAST(T *)array->data, array->size); return result; } -DQN_API template T *Dqn_FArray_Make(Dqn_FArray *array, Dqn_ZeroMem zero_mem) -{ - T *result = Dqn_FArray_MakeArray(array, 1, zero_mem); - return result; -} - -DQN_API template T *Dqn_FArray_AddArray(Dqn_FArray *array, T const *items, Dqn_usize count) +template T *Dqn_FArray_AddArray(Dqn_FArray *array, T const *items, Dqn_usize count) { T *result = Dqn_FArray_MakeArray(array, count, Dqn_ZeroMem_No); if (result) @@ -859,13 +749,35 @@ DQN_API template T *Dqn_FArray_AddArray(Dqn_FArray T *Dqn_FArray_Add(Dqn_FArray *array, T const &item) +template T *Dqn_FArray_AddCArray(Dqn_FArray *array, T const (&items)[K]) +{ + T *result = Dqn_FArray_MakeArray(array, K, Dqn_ZeroMem_No); + if (result) + DQN_MEMCPY(result, items, K * sizeof(T)); + return result; +} + +template T *Dqn_FArray_Add(Dqn_FArray *array, T const &item) { T *result = Dqn_FArray_AddArray(array, &item, 1); return result; } -DQN_API template T *Dqn_FArray_InsertArray(Dqn_FArray *array, Dqn_usize index, T const *items, Dqn_usize count) +template T *Dqn_FArray_MakeArray(Dqn_FArray *array, Dqn_usize count, Dqn_ZeroMem zero_mem) +{ + if (!Dqn_FArray_IsValid(array)) + return nullptr; + T *result = Dqn_CArray_MakeArray(array->data, &array->size, N, count, zero_mem); + return result; +} + +template T *Dqn_FArray_Make(Dqn_FArray *array, Dqn_ZeroMem zero_mem) +{ + T *result = Dqn_FArray_MakeArray(array, 1, zero_mem); + return result; +} + +template T *Dqn_FArray_InsertArray(Dqn_FArray *array, Dqn_usize index, T const *items, Dqn_usize count) { T *result = nullptr; if (!Dqn_FArray_IsValid(array)) @@ -874,31 +786,37 @@ DQN_API template T *Dqn_FArray_InsertArray(Dqn_FArray< return result; } -DQN_API template T *Dqn_FArray_Insert(Dqn_FArray *array, Dqn_usize index, T const &item) +template T *Dqn_FArray_InsertCArray(Dqn_FArray *array, Dqn_usize index, T const (&items)[K]) +{ + T *result = Dqn_FArray_InsertArray(array, index, items, K); + return result; +} + +template T *Dqn_FArray_Insert(Dqn_FArray *array, Dqn_usize index, T const &item) { T *result = Dqn_FArray_InsertArray(array, index, &item, 1); return result; } -DQN_API template T Dqn_FArray_PopFront(Dqn_FArray *array, Dqn_usize count) +template T Dqn_FArray_PopFront(Dqn_FArray *array, Dqn_usize count) { T result = Dqn_CArray_PopFront(array->data, &array->size, count); return result; } -DQN_API template T Dqn_FArray_PopBack(Dqn_FArray *array, Dqn_usize count) +template T Dqn_FArray_PopBack(Dqn_FArray *array, Dqn_usize count) { T result = Dqn_CArray_PopBack(array->data, &array->size, count); return result; } -DQN_API template Dqn_ArrayFindResult Dqn_FArray_Find(Dqn_FArray *array, T const &find) +template Dqn_ArrayFindResult Dqn_FArray_Find(Dqn_FArray *array, T const &find) { Dqn_ArrayFindResult result = Dqn_CArray_Find(array->data, array->size, find); return result; } -DQN_API template Dqn_ArrayEraseResult Dqn_FArray_EraseRange(Dqn_FArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) +template Dqn_ArrayEraseResult Dqn_FArray_EraseRange(Dqn_FArray *array, Dqn_usize begin_index, Dqn_isize count, Dqn_ArrayErase erase) { Dqn_ArrayEraseResult result = {}; if (!Dqn_FArray_IsValid(array) || array->size == 0 || count == 0) @@ -907,7 +825,7 @@ DQN_API template Dqn_ArrayEraseResult Dqn_FArray_Erase return result; } -DQN_API template void Dqn_FArray_Clear(Dqn_FArray *array) +template void Dqn_FArray_Clear(Dqn_FArray *array) { if (array) array->size = 0; @@ -926,7 +844,7 @@ template Dqn_Slice Dqn_Slice_Init(T* const data, Dqn_usize size) } template -Dqn_Slice Dqn_Slice_InitCArrayCopy(Dqn_Arena *arena, T const (&array)[N]) +Dqn_Slice Dqn_Slice_InitCArray(Dqn_Arena *arena, T const (&array)[N]) { Dqn_Slice result = Dqn_Slice_Alloc(arena, N, Dqn_ZeroMem_No); if (result.data) @@ -948,39 +866,35 @@ template Dqn_Slice Dqn_Slice_Alloc(Dqn_Arena *arena, Dqn_usize s #endif // !defined(DQN_NO_SLICE) #if !defined(DQN_NO_DSMAP) -// NOTE: [$DMAP] Dqn_DSMap ========================================================================= +// NOTE: [$DMAP] Dqn_DSMap ///////////////////////////////////////////////////////////////////////// uint32_t const DQN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; -uint32_t const DQN_DS_MAP_SENTINEL_SLOT = 0; +uint32_t const DQN_DS_MAP_SENTINEL_SLOT = 0; -template -Dqn_DSMap Dqn_DSMap_Init(uint32_t size) +template Dqn_DSMap Dqn_DSMap_Init(Dqn_Arena *arena, uint32_t size) { Dqn_DSMap result = {}; - if (DQN_CHECKF((size & (size - 1)) == 0, "Power-of-two size required")) { - result.hash_to_slot = Dqn_Allocator_NewArray(result.allocator, uint32_t, size, Dqn_ZeroMem_Yes); - if (result.hash_to_slot) { - result.slots = Dqn_Allocator_NewArray(result.allocator, Dqn_DSMapSlot, size, Dqn_ZeroMem_Yes); - if (result.slots) { - result.occupied = 1; // For sentinel - result.size = size; - result.initial_size = size; - } else { - Dqn_Allocator_Dealloc(result.allocator, result.hash_to_slot, size * sizeof(*result.hash_to_slot)); - } - } - } + if (!DQN_CHECKF(Dqn_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size)) + return result; + if (!arena) + return result; + result.arena = arena; + result.hash_to_slot = Dqn_Arena_NewArray(result.arena, uint32_t, size, Dqn_ZeroMem_No); + result.slots = Dqn_Arena_NewArray(result.arena, Dqn_DSMapSlot, size, Dqn_ZeroMem_No); + result.occupied = 1; // For sentinel + result.size = size; + result.initial_size = size; + DQN_ASSERTF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?"); return result; } template -void Dqn_DSMap_Deinit(Dqn_DSMap *map) +void Dqn_DSMap_Deinit(Dqn_DSMap *map, Dqn_ZeroMem zero_mem) { if (!map) return; - if (map->slots) - Dqn_Allocator_Dealloc(map->allocator, map->slots, sizeof(*map->slots) * map->size); - if (map->hash_to_slot) - Dqn_Allocator_Dealloc(map->allocator, map->hash_to_slot, sizeof(*map->hash_to_slot) * map->size); + // TODO(doyle): Use zero_mem + (void)zero_mem; + Dqn_Arena_Deinit(map->arena); *map = {}; } @@ -988,6 +902,7 @@ template bool Dqn_DSMap_IsValid(Dqn_DSMap const *map) { bool result = map && + map->arena && map->hash_to_slot && // Hash to slot mapping array must be allocated map->slots && // Slots array must be allocated (map->size & (map->size - 1)) == 0 && // Must be power of two size @@ -1195,18 +1110,26 @@ Dqn_DSMapResult Dqn_DSMap_SetKeyStr8(Dqn_DSMap *map, Dqn_Str8 key, T const } template -Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8Copy(Dqn_DSMap *map, Dqn_Allocator allocator, Dqn_Str8 key) +Dqn_DSMapResult Dqn_DSMap_MakeKeyStr8Copy(Dqn_DSMap *map, Dqn_Arena *arena, Dqn_Str8 key) { - Dqn_DSMapKey map_key = Dqn_DSMap_KeyStr8Copy(map, allocator, key); - Dqn_DSMapResult result = Dqn_DSMap_Make(map, map_key); + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(arena); + Dqn_DSMapKey map_key = Dqn_DSMap_KeyStr8Copy(map, arena, key); + Dqn_DSMapResult result = Dqn_DSMap_Make(map, map_key); + // NOTE: If it already exists then we already have the key, we can deallocate + if (result.found) + Dqn_Arena_TempMemEnd(temp_mem); return result; } template -Dqn_DSMapResult Dqn_DSMap_SetKeyStr8Copy(Dqn_DSMap *map, Dqn_Allocator allocator, Dqn_Str8 key, T const &value) +Dqn_DSMapResult Dqn_DSMap_SetKeyStr8Copy(Dqn_DSMap *map, Dqn_Arena *arena, Dqn_Str8 key, T const &value) { - Dqn_DSMapKey map_key = Dqn_DSMap_KeyStr8Copy(map, allocator, key); - Dqn_DSMapResult result = Dqn_DSMap_Set(map, map_key); + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(arena); + Dqn_DSMapKey map_key = Dqn_DSMap_KeyStr8Copy(map, arena, key); + Dqn_DSMapResult result = Dqn_DSMap_Set(map, map_key); + // NOTE: If it already exists then we already have the key, we can deallocate + if (result.found) + Dqn_Arena_TempMemEnd(temp_mem); return result; } @@ -1216,7 +1139,11 @@ bool Dqn_DSMap_Resize(Dqn_DSMap *map, uint32_t size) if (!Dqn_DSMap_IsValid(map) || size < map->occupied || size < map->initial_size) return false; - Dqn_DSMap new_map = Dqn_DSMap_Init(size); + Dqn_Arena *prev_arena = map->arena; + Dqn_Arena new_arena = {}; + new_arena.flags = prev_arena->flags; + + Dqn_DSMap new_map = Dqn_DSMap_Init(&new_arena, size); if (!Dqn_DSMap_IsValid(&new_map)) return false; @@ -1229,8 +1156,10 @@ bool Dqn_DSMap_Resize(Dqn_DSMap *map, uint32_t size) } DQN_MEMCPY(new_map.slots, map->slots, sizeof(*map->slots) * map->occupied); - Dqn_DSMap_Deinit(map); - *map = new_map; + Dqn_DSMap_Deinit(map, Dqn_ZeroMem_No); + *map = new_map; // Update the map inplace + map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed + *map->arena = new_arena; // Re-init the old arena with the new data return true; } @@ -1331,17 +1260,17 @@ DQN_API Dqn_DSMapKey Dqn_DSMap_KeyStr8(Dqn_DSMap const *map, Dqn_Str8 string) } template -DQN_API Dqn_DSMapKey Dqn_DSMap_KeyStr8Copy(Dqn_DSMap const *map, Dqn_Allocator allocator, Dqn_Str8 string) +DQN_API Dqn_DSMapKey Dqn_DSMap_KeyStr8Copy(Dqn_DSMap const *map, Dqn_Arena *arena, Dqn_Str8 string) { - Dqn_Str8 copy = Dqn_Str8_Copy(allocator, string); + Dqn_Str8 copy = Dqn_Str8_Copy(arena, string); Dqn_DSMapKey result = Dqn_DSMap_KeyStr8(map, copy); return result; } #endif // !defined(DQN_NO_DSMAP) #if !defined(DQN_NO_LIST) -// NOTE: [$LIST] Dqn_List ========================================================================== -template DQN_API Dqn_List Dqn_List_Init(Dqn_Arena *arena, Dqn_usize chunk_size) +// NOTE: [$LIST] Dqn_List ////////////////////////////////////////////////////////////////////////// +template Dqn_List Dqn_List_Init(Dqn_Arena *arena, Dqn_usize chunk_size) { Dqn_List result = {}; result.arena = arena; @@ -1349,7 +1278,7 @@ template DQN_API Dqn_List Dqn_List_Init(Dqn_Arena *arena, Dqn_us return result; } -template Dqn_List Dqn_List_InitCArrayCopy(Dqn_Arena *arena, Dqn_usize chunk_size, T const (&array)[N]) +template Dqn_List Dqn_List_InitCArray(Dqn_Arena *arena, Dqn_usize chunk_size, T const (&array)[N]) { Dqn_List result = Dqn_List_Init(arena, chunk_size); DQN_FOR_UINDEX (index, N) @@ -1405,6 +1334,17 @@ template DQN_API T *Dqn_List_Add(Dqn_List *list, T const &value) return result; } +template DQN_API bool Dqn_List_AddCArray(Dqn_List *list, T const (&array)[N]) +{ + if (!list || list->chunk_size <= 0) + return false; + for (T const &item : array) { + if (!Dqn_List_Add(list, item)) + return false; + } + return true; +} + template DQN_API void Dqn_List_AddList(Dqn_List *list, Dqn_List other) { if (!list || list->chunk_size <= 0) diff --git a/dqn_cppbuild.h b/dqn_cppbuild.h index fd3f27c..430ccd7 100644 --- a/dqn_cppbuild.h +++ b/dqn_cppbuild.h @@ -1,9 +1,27 @@ #if !defined(DQN_CPP_BUILD_H) #define DQN_CPP_BUILD_H +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$\ $$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ $$$$$$$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ | $$ |\_$$ _|$$ | $$ __$$\ +// $$ / \__|$$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ | +// $$ | $$$$$$$ |$$$$$$$ | $$$$$$$\ |$$ | $$ | $$ | $$ | $$ | $$ | +// $$ | $$ ____/ $$ ____/ $$ __$$\ $$ | $$ | $$ | $$ | $$ | $$ | +// $$ | $$\ $$ | $$ | $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ | +// \$$$$$$ |$$ | $$ | $$$$$$$ |\$$$$$$ |$$$$$$\ $$$$$$$$\ $$$$$$$ | +// \______/ \__| \__| \_______/ \______/ \______|\________|\_______/ +// +// dqn_cppbuild.h -- Helper functions to make build scripts in C++ +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#include // exit + struct Dqn_CPPBuildCompileFile { - Dqn_Slice flags; + Dqn_Slice prefix_flags; + Dqn_Slice suffix_flags; Dqn_Str8 input_file_path; Dqn_Str8 output_file_path; }; @@ -11,15 +29,25 @@ struct Dqn_CPPBuildCompileFile Dqn_Str8 const DQN_CPP_BUILD_OBJ_SUFFIX_OBJ = DQN_STR8(".obj"); Dqn_Str8 const DQN_CPP_BUILD_OBJ_SUFFIX_O = DQN_STR8(".o"); -enum Dqn_CPPBuildCompiler +enum Dqn_CPPBuildFlagsStyle { - Dqn_CPPBuildCompiler_MSVC, - Dqn_CPPBuildCompiler_GCC, + Dqn_CPPBuildFlagsStyle_MSVC, + Dqn_CPPBuildFlagsStyle_GCC, + Dqn_CPPBuildFlagsStyle_CLANG, +}; + +enum Dqn_CPPBuildAppendCompilerToCommand +{ + Dqn_CPPBuildAppendCompilerToCommand_No, + Dqn_CPPBuildAppendCompilerToCommand_Yes, }; struct Dqn_CPPBuildContext { - Dqn_CPPBuildCompiler compiler; + // Dictates the type of compiler flags the functions may append to the + // build command line + Dqn_CPPBuildFlagsStyle flags_style; + Dqn_Str8 compile_file_obj_suffix; Dqn_Slice compile_files; Dqn_Slice compile_flags; @@ -46,17 +74,18 @@ enum Dqn_CPPBuildMode Dqn_CPPBuildMode_CacheBuild, }; -DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLine(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Allocator allocator); -DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async (Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode); -DQN_API void Dqn_CPPBuild_ExecOrAbort (Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode); +DQN_API Dqn_Slice Dqn_CPPBuild_ToCommandLine (Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Arena *arena); +DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLineStr8(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Arena *arena); +DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async (Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode); +DQN_API void Dqn_CPPBuild_ExecOrAbort (Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode); #endif // DQN_CPP_BUILD_H #if defined(DQN_CPP_BUILD_IMPLEMENTATION) -DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLine(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Allocator allocator) +DQN_API Dqn_Slice Dqn_CPPBuild_ToCommandLine(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Arena *arena) { - // NOTE: Check if object files are newer than the source files ================================= - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); - Dqn_Str8 result = {}; + // NOTE: Check if object files are newer than the source files ///////////////////////////////// + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Slice result = {}; Dqn_Slice dirtied_compile_files = build_context.compile_files; if (mode == Dqn_CPPBuildMode_CacheBuild) { @@ -77,15 +106,15 @@ DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLine(Dqn_CPPBuildContext build_context, D // NOTE: Create the object file path Dqn_Str8 file_stem = Dqn_Str8_FileNameNoExtension(file.input_file_path); - obj_file_name = Dqn_Str8_InitF(scratch.allocator, "%.*s%.*s", DQN_STR_FMT(file_stem), DQN_STR_FMT(compile_file_obj_suffix)); + obj_file_name = Dqn_Str8_InitF(scratch.arena, "%.*s%.*s", DQN_STR_FMT(file_stem), DQN_STR_FMT(compile_file_obj_suffix)); } Dqn_Str8 obj_file_path = obj_file_name; if (build_context.build_dir.size) - obj_file_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STR_FMT(build_context.build_dir), DQN_STR_FMT(obj_file_name)); + obj_file_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/%.*s", DQN_STR_FMT(build_context.build_dir), DQN_STR_FMT(obj_file_name)); - Dqn_FsInfo file_info = Dqn_Fs_GetInfo(file.input_file_path); - Dqn_FsInfo obj_file_info = Dqn_Fs_GetInfo(obj_file_path); + Dqn_OSPathInfo file_info = Dqn_OS_PathInfo(file.input_file_path); + Dqn_OSPathInfo obj_file_info = Dqn_OS_PathInfo(obj_file_path); if (obj_file_info.last_write_time_in_s >= file_info.last_write_time_in_s) continue; @@ -96,88 +125,77 @@ DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLine(Dqn_CPPBuildContext build_context, D return result; } - // NOTE: Build the command line invocation ===================================================== + // NOTE: Build the command line invocation ///////////////////////////////////////////////////// Dqn_Str8Builder builder = {}; - builder.allocator = allocator; - DQN_FOR_UINDEX (index, build_context.compile_flags.size) { - Dqn_Str8 flag = build_context.compile_flags.data[index]; - if (index) - Dqn_Str8Builder_AppendF(&builder, " "); - Dqn_Str8Builder_AppendRef(&builder, flag); - } + builder.arena = scratch.arena; + Dqn_Str8Builder_AppendRefArray(&builder, build_context.compile_flags); - DQN_FOR_UINDEX (index, build_context.include_dirs.size) { + DQN_FOR_UINDEX(index, build_context.include_dirs.size) { Dqn_Str8 include_dir = build_context.include_dirs.data[index]; - if (builder.count) - Dqn_Str8Builder_AppendF(&builder, " "); - Dqn_Str8Builder_AppendF(&builder, "/I %.*s", DQN_STR_FMT(include_dir)); + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8("-I")); + Dqn_Str8Builder_AppendRef(&builder, include_dir); } - DQN_FOR_UINDEX (index, dirtied_compile_files.size) { + DQN_FOR_UINDEX(index, dirtied_compile_files.size) { Dqn_CPPBuildCompileFile file = dirtied_compile_files.data[index]; - Dqn_Str8 obj_file = {}; - if (builder.count) - Dqn_Str8Builder_AppendF(&builder, " "); - - if (file.output_file_path.size) { - switch (build_context.compiler) { - case Dqn_CPPBuildCompiler_MSVC: { - Dqn_Str8Builder_AppendF(&builder, "/Fo%.*s ", DQN_STR_FMT(file.output_file_path)); + if (Dqn_Str8_HasData(file.output_file_path)) { + switch (build_context.flags_style) { + case Dqn_CPPBuildFlagsStyle_MSVC: { + Dqn_Str8Builder_AppendF(&builder, "-Fo%.*s", DQN_STR_FMT(file.output_file_path)); } break; - case Dqn_CPPBuildCompiler_GCC: { - Dqn_Str8Builder_AppendF(&builder, "-o %.*s ", DQN_STR_FMT(file.output_file_path)); + case Dqn_CPPBuildFlagsStyle_GCC: /*FALLTHRU*/ + case Dqn_CPPBuildFlagsStyle_CLANG: { + Dqn_Str8Builder_AppendF (&builder, "-o"); + Dqn_Str8Builder_AppendRef(&builder, file.output_file_path); } break; } } - DQN_FOR_UINDEX (flag_index, file.flags.size) { - Dqn_Str8 flag = file.flags.data[flag_index]; - Dqn_Str8Builder_AppendF(&builder, "%s%.*s", flag_index ? " " : "", DQN_STR_FMT(flag)); - } + // TODO(doyle): Check if the file exists, error if it doesn't - if (file.flags.size) - Dqn_Str8Builder_AppendF(&builder, " "); + Dqn_Str8Builder_AppendRefArray(&builder, file.prefix_flags); Dqn_Str8Builder_AppendRef(&builder, file.input_file_path); + Dqn_Str8Builder_AppendRefArray(&builder, file.suffix_flags); } - DQN_FOR_UINDEX (index, build_context.link_flags.size) { - Dqn_Str8 file = build_context.link_flags.data[index]; - if (builder.count) - Dqn_Str8Builder_AppendF(&builder, " "); - Dqn_Str8Builder_AppendRef(&builder, file); - } - - result = Dqn_Str8Builder_Build(&builder, allocator); + Dqn_Str8Builder_AppendRefArray(&builder, build_context.link_flags); + result = Dqn_Str8Builder_BuildSlice(&builder, arena); return result; } +DQN_API Dqn_Str8 Dqn_CPPBuild_ToCommandLineStr8(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode, Dqn_Arena *arena) +{ + Dqn_Slice cmd_line = Dqn_CPPBuild_ToCommandLine(build_context, mode, arena); + Dqn_Str8 result = Dqn_Slice_Str8Render(arena, cmd_line, DQN_STR8(" ") /*separator*/); + return result; +} DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 cmd = Dqn_CPPBuild_ToCommandLine(build_context, mode, scratch.allocator); - Dqn_CPPBuildAsyncResult result = {}; - if (!cmd.size) + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Slice cmd_line = Dqn_CPPBuild_ToCommandLine(build_context, mode, scratch.arena); + Dqn_CPPBuildAsyncResult result = {}; + if (!cmd_line.size) return result; - if (!Dqn_Fs_MakeDir(build_context.build_dir)) { + if (!Dqn_OS_DirMake(build_context.build_dir)) { result.status = Dqn_CPPBuildStatus_BuildDirectoryFailedToBeMade; return result; } - result.async_handle = Dqn_OS_ExecAsync(cmd, build_context.build_dir); + result.async_handle = Dqn_OS_ExecAsync(cmd_line, build_context.build_dir); return result; } void Dqn_CPPBuild_ExecOrAbort(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode) { - if (!Dqn_Fs_MakeDir(build_context.build_dir)) { + if (!Dqn_OS_DirMake(build_context.build_dir)) { Dqn_Log_ErrorF("Failed to make build dir '%.*s'", DQN_STR_FMT(build_context.build_dir)); exit(-1); } - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 cmd = Dqn_CPPBuild_ToCommandLine(build_context, mode, scratch.allocator); - Dqn_OS_ExecOrAbort(cmd, build_context.build_dir); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Slice cmd_line = Dqn_CPPBuild_ToCommandLine(build_context, mode, scratch.arena); + Dqn_OS_ExecOrAbort(cmd_line, build_context.build_dir); } #endif // DQN_CPP_BUILD_IMPLEMENTATION diff --git a/dqn_debug.cpp b/dqn_debug.cpp index ffc2cd1..3785fd4 100644 --- a/dqn_debug.cpp +++ b/dqn_debug.cpp @@ -1,7 +1,25 @@ -// NOTE: [$ASAN] Dqn_Asan ========================================================================== === +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\ $$ _____|$$ __$$\ $$ | $$ |$$ __$$\ +// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ / \__| +// $$ | $$ |$$$$$\ $$$$$$$\ |$$ | $$ |$$ |$$$$\ +// $$ | $$ |$$ __| $$ __$$\ $$ | $$ |$$ |\_$$ | +// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ | $$ | +// $$$$$$$ |$$$$$$$$\ $$$$$$$ |\$$$$$$ |\$$$$$$ | +// \_______/ \________|\_______/ \______/ \______/ +// +// dqn_debug.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$ASAN] Dqn_Asan ////////////////////////////////////////////////////////////////////////// /// DQN_API void Dqn_ASAN_PoisonMemoryRegion(void const volatile *ptr, Dqn_usize size) { - #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (!ptr || !size) + return; + + #if DQN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) __asan_poison_memory_region(ptr, size); if (DQN_ASAN_VET_POISON) { DQN_HARD_ASSERT(__asan_address_is_poisoned(ptr)); @@ -14,7 +32,10 @@ DQN_API void Dqn_ASAN_PoisonMemoryRegion(void const volatile *ptr, Dqn_usize siz DQN_API void Dqn_ASAN_UnpoisonMemoryRegion(void const volatile *ptr, Dqn_usize size) { - #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (!ptr || !size) + return; + + #if DQN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) __asan_unpoison_memory_region(ptr, size); if (DQN_ASAN_VET_POISON) { DQN_HARD_ASSERT(__asan_region_is_poisoned((void *)ptr, size) == 0); @@ -31,17 +52,18 @@ DQN_API Dqn_StackTraceWalkResult Dqn_StackTrace_Walk(Dqn_Arena *arena, uint16_t if (!arena) return result; - static Dqn_TicketMutex mutex = {}; + static Dqn_TicketMutex mutex = {}; Dqn_TicketMutex_Begin(&mutex); HANDLE thread = GetCurrentThread(); result.process = GetCurrentProcess(); - for (static bool init = false; !init; init = true) { + if (!g_dqn_library->win32_sym_initialised) { + g_dqn_library->win32_sym_initialised = true; SymSetOptions(SYMOPT_LOAD_LINES); if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); Dqn_Log_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DQN_STR_FMT(error.msg)); } } @@ -57,10 +79,8 @@ DQN_API Dqn_StackTraceWalkResult Dqn_StackTrace_Walk(Dqn_Arena *arena, uint16_t frame.AddrStack.Offset = context.Rsp; frame.AddrStack.Mode = AddrModeFlat; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_List raw_frames = Dqn_List_Init(scratch.arena, 32 /*chunk size*/); - - while (raw_frames.count < limit) { + Dqn_FArray raw_frames = {}; + while (raw_frames.size < limit) { if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, result.process, thread, @@ -75,22 +95,53 @@ DQN_API Dqn_StackTraceWalkResult Dqn_StackTrace_Walk(Dqn_Arena *arena, uint16_t // NOTE: It might be useful one day to use frame.AddrReturn.Offset. // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. - Dqn_List_Add(&raw_frames, frame.AddrPC.Offset); + Dqn_FArray_Add(&raw_frames, frame.AddrPC.Offset); } Dqn_TicketMutex_End(&mutex); - result.base_addr = Dqn_Arena_NewArray(arena, uint64_t, raw_frames.count, Dqn_ZeroMem_No); - for (Dqn_ListChunk *chunk = raw_frames.head; chunk; chunk = chunk->next) { - DQN_MEMCPY(result.base_addr + result.size, chunk->data, sizeof(*chunk->data) * chunk->count); - result.size += DQN_CAST(uint16_t)chunk->count; - } + result.base_addr = Dqn_Arena_NewArray(arena, uint64_t, raw_frames.size, Dqn_ZeroMem_No); + result.size = DQN_CAST(uint16_t)raw_frames.size; + DQN_MEMCPY(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); #else (void)limit; (void)arena; #endif return result; } -DQN_API bool Dqn_StackTrace_WalkResultIterate(Dqn_StackTraceWalkResultIterator *it, Dqn_StackTraceWalkResult *walk) +DQN_API Dqn_Str8 Dqn_StackTrace_WalkStr8CRT(uint16_t limit, uint16_t skip) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_StackTraceWalkResult walk_result = Dqn_StackTrace_Walk(scratch.arena, limit); + Dqn_Str8 result = Dqn_StackTrace_WalkResultStr8CRT(&walk_result, skip); + return result; +} + +static void Dqn_StackTrace_AddWalkToStr8Builder_(Dqn_StackTraceWalkResult const *walk, Dqn_Str8Builder *builder, Dqn_usize skip) +{ + Dqn_StackTraceRawFrame raw_frame = {}; + raw_frame.process = walk->process; + for (Dqn_usize index = skip; index < walk->size; index++) { + raw_frame.base_addr = walk->base_addr[index]; + Dqn_StackTraceFrame frame = Dqn_StackTrace_RawFrameToFrame(builder->arena, raw_frame); + Dqn_Str8Builder_AppendF(builder, "%.*s(%I64u): %.*s%s", DQN_STR_FMT(frame.file_name), frame.line_number, DQN_STR_FMT(frame.function_name), (index == walk->size - 1) ? "" : "\n"); + } +} + +DQN_API Dqn_Str8 Dqn_StackTrace_WalkStr8CRTNoScratch(uint16_t limit, uint16_t skip) +{ + Dqn_Arena arena = {}; + arena.flags |= Dqn_ArenaFlag_NoAllocTrack; + Dqn_StackTraceWalkResult walk = Dqn_StackTrace_Walk(&arena, limit); + + Dqn_Str8Builder builder = {}; + builder.arena = &arena; + Dqn_StackTrace_AddWalkToStr8Builder_(&walk, &builder, skip); + Dqn_Str8 result = Dqn_Str8Builder_BuildCRT(&builder); + Dqn_Arena_Deinit(&arena); + return result; +} + +DQN_API bool Dqn_StackTrace_WalkResultIterate(Dqn_StackTraceWalkResultIterator *it, Dqn_StackTraceWalkResult const *walk) { bool result = false; if (!it || !walk || !walk->base_addr || !walk->process) @@ -105,53 +156,43 @@ DQN_API bool Dqn_StackTrace_WalkResultIterate(Dqn_StackTraceWalkResultIterator * return result; } -DQN_API Dqn_StackTraceFrame Dqn_StackTrace_RawFrameToFrame(Dqn_Arena *arena, Dqn_StackTraceRawFrame raw_frame) +DQN_API Dqn_Str8 Dqn_StackTrace_WalkResultStr8(Dqn_Arena *arena, Dqn_StackTraceWalkResult const *walk, uint16_t skip) { - #if defined(DQN_OS_WIN32) - // NOTE: Get line+filename ===================================================================== + Dqn_Str8 result {}; + if (!walk || !arena) + return result; - // TODO: Why does zero-initialising this with `line = {};` cause - // SymGetLineFromAddr64 function to fail once we are at - // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The - // line and file number are still valid in the result which we use, so, - // we silently ignore this error. - IMAGEHLP_LINEW64 line; - line.SizeOfStruct = sizeof(line); - DWORD line_displacement = 0; - SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line); - - // NOTE: Get function name ===================================================================== - - alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; - SYMBOL_INFOW *symbol = DQN_CAST(SYMBOL_INFOW *)buffer; - symbol->SizeOfStruct = sizeof(*symbol); - symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); - - uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address - SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); - - // NOTE: Construct result ====================================================================== - - Dqn_Str16 file_name16 = Dqn_Str16{line.FileName, Dqn_CStr16_Size(line.FileName)}; - Dqn_Str16 function_name16 = Dqn_Str16{symbol->Name, symbol->NameLen}; - - Dqn_StackTraceFrame result = {}; - result.address = raw_frame.base_addr; - result.line_number = line.LineNumber; - result.file_name = Dqn_Win_Str16ToStr8(arena, file_name16); - result.function_name = Dqn_Win_Str16ToStr8(arena, function_name16); - #else - Dqn_StackTraceFrame result = {}; - #endif + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str8Builder builder = {}; + builder.arena = scratch.arena; + Dqn_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); + result = Dqn_Str8Builder_Build(&builder, arena); return result; } +DQN_API Dqn_Str8 Dqn_StackTrace_WalkResultStr8CRT(Dqn_StackTraceWalkResult const *walk, uint16_t skip) +{ + Dqn_Str8 result {}; + if (!walk) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8Builder builder = {}; + builder.arena = scratch.arena; + Dqn_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); + result = Dqn_Str8Builder_BuildCRT(&builder); + return result; +} + + DQN_API Dqn_Slice Dqn_StackTrace_GetFrames(Dqn_Arena *arena, uint16_t limit) { Dqn_Slice result = {}; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_StackTraceWalkResult walk = Dqn_StackTrace_Walk(scratch.arena, limit); + if (!arena) + return result; + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_StackTraceWalkResult walk = Dqn_StackTrace_Walk(scratch.arena, limit); if (!walk.size) return result; @@ -163,138 +204,173 @@ DQN_API Dqn_Slice Dqn_StackTrace_GetFrames(Dqn_Arena *arena return result; } +DQN_API Dqn_StackTraceFrame Dqn_StackTrace_RawFrameToFrame(Dqn_Arena *arena, Dqn_StackTraceRawFrame raw_frame) +{ + #if defined(DQN_OS_WIN32) + // NOTE: Get line+filename ///////////////////////////////////////////////////////////////////// + + // TODO: Why does zero-initialising this with `line = {};` cause + // SymGetLineFromAddr64 function to fail once we are at + // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The + // line and file number are still valid in the result which we use, so, + // we silently ignore this error. + IMAGEHLP_LINEW64 line; + line.SizeOfStruct = sizeof(line); + DWORD line_displacement = 0; + if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) { + line = {}; + } + + // NOTE: Get function name ///////////////////////////////////////////////////////////////////// + + alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; + SYMBOL_INFOW *symbol = DQN_CAST(SYMBOL_INFOW *)buffer; + symbol->SizeOfStruct = sizeof(*symbol); + symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); + + uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address + SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); + + // NOTE: Construct result ////////////////////////////////////////////////////////////////////// + + Dqn_Str16 file_name16 = Dqn_Str16{line.FileName, Dqn_CStr16_Size(line.FileName)}; + Dqn_Str16 function_name16 = Dqn_Str16{symbol->Name, symbol->NameLen}; + + Dqn_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = Dqn_Win_Str16ToStr8(arena, file_name16); + result.function_name = Dqn_Win_Str16ToStr8(arena, function_name16); + + if (!Dqn_Str8_HasData(result.function_name)) + result.function_name = DQN_STR8(""); + if (!Dqn_Str8_HasData(result.file_name)) + result.file_name = DQN_STR8(""); + #else + Dqn_StackTraceFrame result = {}; + #endif + return result; +} + DQN_API void Dqn_StackTrace_Print(uint16_t limit) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Slice stack_trace = Dqn_StackTrace_GetFrames(scratch.arena, limit); - for (Dqn_StackTraceFrame& frame : stack_trace) + for (Dqn_StackTraceFrame &frame : stack_trace) Dqn_Print_ErrLnF("%.*s(%I64u): %.*s", DQN_STR_FMT(frame.file_name), frame.line_number, DQN_STR_FMT(frame.function_name)); } -// NOTE: [$DEBG] Dqn_Debug ========================================================================= -#if defined(DQN_LEAK_TRACING) -DQN_API void Dqn_Debug_TrackAlloc_(Dqn_Str8 stack_trace, void *ptr, Dqn_usize size, bool leak_permitted) +DQN_API void Dqn_StackTrace_ReloadSymbols() +{ + #if defined(DQN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); + #endif +} + +// NOTE: [$DEBG] Dqn_Debug ///////////////////////////////////////////////////////////////////////// +#if defined(DQN_LEAK_TRACKING) +DQN_API void Dqn_Debug_TrackAlloc(void *ptr, Dqn_usize size, bool leak_permitted) { if (!ptr) return; - if (g_dqn_library->alloc_tracking_disabled) - return; - - // NOTE: In this function we can create alloc records and hence it's - // possible for the alloc table to resize. This can cause a nested call - // into the tracking alloc function and dead-lock. We don't - // care about tracking these alloc records for the alloc table itself so we - // disable alloc tracking for the duration of this function. - // TODO(doyle): @robust This is not thread safe. Dqn_TicketMutex_Begin(&g_dqn_library->alloc_table_mutex); - g_dqn_library->alloc_tracking_disabled = true; - DQN_DEFER { - g_dqn_library->alloc_tracking_disabled = false; Dqn_TicketMutex_End(&g_dqn_library->alloc_table_mutex); }; // 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_DSMap *alloc_table = &g_dqn_library->alloc_table; - Dqn_DSMapKey key = Dqn_DSMap_KeyU64(alloc_table, DQN_CAST(uintptr_t)ptr); - Dqn_AllocRecord *alloc = Dqn_DSMap_Find(alloc_table, key); - if (alloc) { - if ((alloc->flags & Dqn_AllocRecordFlag_Freed) == 0) { - Dqn_Str8 alloc_stack_trace = Dqn_Str8_Init(alloc->stack_trace, alloc->stack_trace_size); - Dqn_Str8 alloc_clean_stack_trace = Dqn_Str8_Slice(alloc_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_stack_trace.size); - Dqn_Str8 clean_stack_trace = Dqn_Str8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); + // 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_Str8 stack_trace = Dqn_StackTrace_WalkStr8CRTNoScratch(128, 3 /*skip*/); + Dqn_DSMap *alloc_table = &g_dqn_library->alloc_table; + Dqn_DSMapResult alloc_entry = Dqn_DSMap_MakeKeyU64(alloc_table, DQN_CAST(uint64_t) ptr); + Dqn_DebugAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & Dqn_DebugAllocFlag_Freed) == 0) { + Dqn_Str8 alloc_size = Dqn_U64ToByteSizeStr8(alloc_table->arena, alloc->size, Dqn_U64ByteSizeType_Auto); + Dqn_Str8 new_alloc_size = Dqn_U64ToByteSizeStr8(alloc_table->arena, size, Dqn_U64ByteSizeType_Auto); DQN_HARD_ASSERTF( - alloc->flags & Dqn_AllocRecordFlag_Freed, - "\n\nThis pointer is already in the leak tracker, however it has not " + alloc->flags & Dqn_DebugAllocFlag_Freed, + "This pointer is already in the leak tracker, however it has not " "been freed yet. This same pointer is being ask to be tracked " "twice in the allocation table, e.g. one if its previous free " "calls has not being marked freed with an equivalent call to " "Dqn_Debug_TrackDealloc()\n" "\n" - "The pointer (0x%p) originally allocated %_$$zu at:\n" + "The pointer (0x%p) originally allocated %.*s at:\n" "\n" - "%.*s" + "%.*s\n" "\n" - "The pointer is being allocated again at:\n" - "%.*s" + "The pointer is allocating %.*s again at:\n" + "\n" + "%.*s\n" , - ptr, alloc->size, - DQN_STR_FMT(alloc_clean_stack_trace), - DQN_STR_FMT(clean_stack_trace)); + ptr, DQN_STR_FMT(alloc_size), + DQN_STR_FMT(alloc->stack_trace), + DQN_STR_FMT(new_alloc_size), + DQN_STR_FMT(stack_trace)); } // NOTE: Pointer was reused, clean up the prior entry - free(alloc->stack_trace); - free(alloc->freed_stack_trace); + free(alloc->stack_trace.data); + free(alloc->freed_stack_trace.data); *alloc = {}; - } else { - alloc = Dqn_DSMap_Make(alloc_table, key, /*found*/ nullptr); } - alloc->ptr = ptr; - alloc->size = size; - alloc->stack_trace = stack_trace.data; - alloc->stack_trace_size = DQN_CAST(uint16_t)stack_trace.size; - - // TODO(doyle): @robust The global flag is not multi-thread safe - if (leak_permitted || g_dqn_library->alloc_is_allowed_to_leak) - alloc->flags |= Dqn_AllocRecordFlag_LeakPermitted; + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace; + alloc->flags |= leak_permitted ? Dqn_DebugAllocFlag_LeakPermitted : 0; } -DQN_API void Dqn_Debug_TrackDealloc_(Dqn_Str8 stack_trace, void *ptr) +DQN_API void Dqn_Debug_TrackDealloc(void *ptr) { - if (!ptr || g_dqn_library->alloc_tracking_disabled) + if (!ptr) return; Dqn_TicketMutex_Begin(&g_dqn_library->alloc_table_mutex); DQN_DEFER { Dqn_TicketMutex_End(&g_dqn_library->alloc_table_mutex); }; - Dqn_DSMap *alloc_table = &g_dqn_library->alloc_table; - Dqn_DSMapKey key = Dqn_DSMap_KeyU64(alloc_table, DQN_CAST(uintptr_t)ptr); - Dqn_AllocRecord *alloc = Dqn_DSMap_Find(alloc_table, key); + Dqn_Str8 stack_trace = Dqn_StackTrace_WalkStr8CRTNoScratch(128, 3 /*skip*/); + Dqn_DSMap *alloc_table = &g_dqn_library->alloc_table; + Dqn_DSMapResult alloc_entry = Dqn_DSMap_FindKeyU64(alloc_table, DQN_CAST(uintptr_t) ptr); + DQN_HARD_ASSERTF(alloc_entry.found, + "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(alloc, "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); - - if (alloc->flags & Dqn_AllocRecordFlag_Freed) { - Dqn_Str8 alloc_stack_trace = Dqn_Str8_Init(alloc->stack_trace, alloc->stack_trace_size); - Dqn_Str8 alloc_clean_stack_trace = Dqn_Str8_Slice(alloc_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_stack_trace.size); - - Dqn_Str8 alloc_freed_stack_trace = Dqn_Str8_Init(alloc->freed_stack_trace, alloc->freed_stack_trace_size); - Dqn_Str8 alloc_freed_clean_stack_trace = Dqn_Str8_Slice(alloc_freed_stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, alloc_freed_stack_trace.size); - - Dqn_Str8 dealloc_stack_trace = Dqn_Str8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); - - DQN_HARD_ASSERTF((alloc->flags & Dqn_AllocRecordFlag_Freed) == 0, - "\n\nDouble free detected, pointer to free was already marked " - " as freed. Either the pointer was reallocated but not" - " traced, or, the pointer was freed twice.\n" + Dqn_DebugAlloc *alloc = alloc_entry.value; + if (alloc->flags & Dqn_DebugAllocFlag_Freed) { + Dqn_Str8 freed_size = Dqn_U64ToByteSizeStr8(alloc_table->arena, alloc->freed_size, Dqn_U64ByteSizeType_Auto); + DQN_HARD_ASSERTF((alloc->flags & Dqn_DebugAllocFlag_Freed) == 0, + "Double free detected, pointer to free was already marked " + "as freed. Either the pointer was reallocated but not " + "traced, or, the pointer was freed twice.\n" "\n" - "The pointer (0x%p) originally allocated %_$$zu at:\n" + "The pointer (0x%p) originally allocated %.*s at:\n" "\n" - "%.*s" + "%.*s\n" "\n" "The pointer was freed at:\n" "\n" - "%.*s" + "%.*s\n" "\n" "The pointer is being freed again at:\n" - "%.*s" + "\n" + "%.*s\n" , - ptr, alloc->freed_size, - DQN_STR_FMT(alloc_clean_stack_trace), - DQN_STR_FMT(alloc_freed_clean_stack_trace), - DQN_STR_FMT(dealloc_stack_trace)); + ptr, DQN_STR_FMT(freed_size), + DQN_STR_FMT(alloc->stack_trace), + DQN_STR_FMT(alloc->freed_stack_trace), + DQN_STR_FMT(stack_trace)); } - alloc->flags |= Dqn_AllocRecordFlag_Freed; - alloc->freed_size = alloc->size; - alloc->freed_stack_trace = stack_trace.data; - alloc->freed_stack_trace_size = DQN_CAST(uint16_t)stack_trace.size; + DQN_ASSERT(!Dqn_Str8_HasData(alloc->freed_stack_trace)); + alloc->flags |= Dqn_DebugAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; } DQN_API void Dqn_Debug_DumpLeaks() @@ -302,25 +378,24 @@ DQN_API void Dqn_Debug_DumpLeaks() uint64_t leak_count = 0; uint64_t leaked_bytes = 0; for (Dqn_usize index = 1; index < g_dqn_library->alloc_table.occupied; index++) { - Dqn_DSMapSlot *slot = g_dqn_library->alloc_table.slots + index; - Dqn_AllocRecord *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & Dqn_AllocRecordFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & Dqn_AllocRecordFlag_LeakPermitted); + Dqn_DSMapSlot *slot = g_dqn_library->alloc_table.slots + index; + Dqn_DebugAlloc *alloc = &slot->value; + bool alloc_leaked = (alloc->flags & Dqn_DebugAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & Dqn_DebugAllocFlag_LeakPermitted); if (alloc_leaked && !leak_permitted) { leaked_bytes += alloc->size; leak_count++; - - Dqn_Str8 stack_trace = Dqn_Str8_Init(alloc->stack_trace, alloc->stack_trace_size); - Dqn_Str8 clean_stack_trace = Dqn_Str8_Slice(stack_trace, g_dqn_library->stack_trace_offset_to_our_call_stack, stack_trace.size); - Dqn_Log_WarningF("Pointer (0x%p) leaked %_$$zu at:\n" + Dqn_Str8 alloc_size = Dqn_U64ToByteSizeStr8(g_dqn_library->alloc_table.arena, alloc->size, Dqn_U64ByteSizeType_Auto); + Dqn_Log_WarningF("Pointer (0x%p) leaked %.*s at:\n" "%.*s", - alloc->ptr, alloc->size, - DQN_STR_FMT(clean_stack_trace)); + alloc->ptr, DQN_STR_FMT(alloc_size), + DQN_STR_FMT(alloc->stack_trace)); } } if (leak_count) { - Dqn_Log_WarningF("There were %I64u leaked allocations totalling %_$$I64u", leak_count, leaked_bytes); + Dqn_Str8 leak_size = Dqn_U64ToByteSizeStr8(&g_dqn_library->arena, leaked_bytes, Dqn_U64ByteSizeType_Auto); + Dqn_Log_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DQN_STR_FMT(leak_size)); } } -#endif // defined(DQN_LEAK_TRACING) +#endif // DQN_LEAK_TRACKING diff --git a/dqn_debug.h b/dqn_debug.h index 879b25b..4e17c9c 100644 --- a/dqn_debug.h +++ b/dqn_debug.h @@ -1,3 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\ $$ _____|$$ __$$\ $$ | $$ |$$ __$$\ +// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ / \__| +// $$ | $$ |$$$$$\ $$$$$$$\ |$$ | $$ |$$ |$$$$\ +// $$ | $$ |$$ __| $$ __$$\ $$ | $$ |$$ |\_$$ | +// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ | $$ | +// $$$$$$$ |$$$$$$$$\ $$$$$$$ |\$$$$$$ |\$$$$$$ | +// \_______/ \________|\_______/ \______/ \______/ +// +// dqn_debug.h -- Tools for debugging +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$ASAN] Dqn_Asan -- Helpers to manually poison memory using ASAN +// [$STKT] Dqn_StackTrace -- Create stack traces +// [$DEBG] Dqn_Debug -- Allocation leak tracking API +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$ASAN] Dqn_Asan ////////////////////////////////////////////////////////////////////////// #if !defined(DQN_ASAN_POISON) #define DQN_ASAN_POISON 0 #endif @@ -7,6 +29,7 @@ #endif #define DQN_ASAN_POISON_ALIGNMENT 8 + #if !defined(DQN_ASAN_POISON_GUARD_SIZE) #define DQN_ASAN_POISON_GUARD_SIZE 128 #endif @@ -14,53 +37,11 @@ static_assert(Dqn_IsPowerOfTwoAligned(DQN_ASAN_POISON_GUARD_SIZE, DQN_ASAN_POISO "ASAN poison guard size must be a power-of-two and aligned to ASAN's alignment" "requirement (8 bytes)"); -// NOTE: MSVC does not support the feature detection macro for instance so we -// compile it out -#if !defined(__has_feature) - #define __has_feature(x) 0 -#endif - -// NOTE: [$ASAN] Dqn_Asan ========================================================================== -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#if DQN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) #include #endif -DQN_API void Dqn_ASAN_PoisonMemoryRegion(void const volatile *ptr, Dqn_usize size); -DQN_API void Dqn_ASAN_UnpoisonMemoryRegion(void const volatile *ptr, Dqn_usize size); - -// NOTE: [$STKT] Dqn_StackTrace ==================================================================== -// Create a stack trace at the calling site that these functions are invoked -// from. -// -// NOTE: API ======================================================================================= -// @proc Dqn_StackTrace_Walk -// @desc This functions generates the stack trace as a series of U64's that -// represent the base address of the functions on the call-stack at the point -// of execution. These functions are stored in order from the current -// executing function first and the most ancestor function last in the walk. - -// @proc Dqn_StackTrace_WalkResultIterate -// @desc Create an iterator to iterate the walk result and produce -// `Dqn_StackTraceRawFrame` from each level of the call-stack. These frames -// can then be converted into `Dqn_StackTraceFrame` which is a human readable -// representation of the frame. -#if 0 - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_StackTraceWalkResult walk = Dqn_StackTrace_Walk(scratch.arena, 128 /*limit*/); - for (Dqn_StackTraceWalkResultIterator it = {}; Dqn_StackTrace_WalkResultIterate(&it, &walk); ) { - Dqn_StackTraceFrame frame = Dqn_StackTrace_RawFrameToFrame(scratch.arena, it.raw_frame); - Dqn_Print_LnF("%.*s(%I64u): %.*s", DQN_STRING_FMT(frame.file_name), frame.line_number, DQN_STRING_FMT(frame.function_name)); - } -#endif - -// @proc Dqn_StackTrace_GetFrames -// @desc Create a stack trace at the point of execution and return the frames -// converted into its human readable format. - -// @proc Dqn_StackTrace_RawFrameToFrame -// @desc Convert a raw frame from a stack trace walk into the human readable -// format (e.g. with function names, line number and file name). - +// NOTE: [$STKT] Dqn_StackTrace //////////////////////////////////////////////////////////////////// struct Dqn_StackTraceFrame { uint64_t address; @@ -88,45 +69,52 @@ struct Dqn_StackTraceWalkResultIterator uint16_t index; }; -DQN_API Dqn_StackTraceWalkResult Dqn_StackTrace_Walk (Dqn_Arena *arena, uint16_t limit); -DQN_API bool Dqn_StackTrace_WalkResultIterate(Dqn_StackTraceWalkResultIterator *it, Dqn_StackTraceWalkResult *walk); -DQN_API Dqn_Slice Dqn_StackTrace_GetFrames (Dqn_Arena *arena, uint16_t limit); -DQN_API void Dqn_StackTrace_Print (uint16_t limit); -DQN_API Dqn_StackTraceFrame Dqn_StackTrace_RawFrameToFrame (Dqn_Arena *arena, Dqn_StackTraceRawFrame raw_frame); - -// NOTE: [$DEBG] Dqn_Debug ========================================================================= -enum Dqn_AllocRecordFlag +// NOTE: [$DEBG] Dqn_Debug ///////////////////////////////////////////////////////////////////////// +enum Dqn_DebugAllocFlag { - Dqn_AllocRecordFlag_Freed = 1 << 0, - Dqn_AllocRecordFlag_LeakPermitted = 1 << 1, + Dqn_DebugAllocFlag_Freed = 1 << 0, + Dqn_DebugAllocFlag_LeakPermitted = 1 << 1, }; -struct Dqn_AllocRecord +struct Dqn_DebugAlloc { - void *ptr; // Pointer to the allocation being tracked - Dqn_usize size; // Size of the allocation - Dqn_usize freed_size; // Store the size of the allocation when it is freed - char *stack_trace; // Stack trace at the point of allocation - char *freed_stack_trace; // Stack trace of where the allocation was freed - uint16_t stack_trace_size; // Size of the `stack_trace` - uint16_t freed_stack_trace_size; // Size of `freed_stack_trace` - uint16_t flags; // Bit flags from `Dqn_AllocRecordFlag` - char padding[2]; + void *ptr; // 8 Pointer to the allocation being tracked + Dqn_usize size; // 16 Size of the allocation + Dqn_usize freed_size; // 24 Store the size of the allocation when it is freed + Dqn_Str8 stack_trace; // 40 Stack trace at the point of allocation + Dqn_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + uint16_t flags; // 72 Bit flags from `Dqn_DebugAllocFlag` }; -static_assert(sizeof(Dqn_AllocRecord) == 48 || sizeof(Dqn_AllocRecord) == 28, // NOTE: 64 bit vs 32 bit pointers respectively + +static_assert(sizeof(Dqn_DebugAlloc) == 64 || sizeof(Dqn_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively "We aim to keep the allocation record as light as possible as " "memory tracking can get expensive. Enforce that there is no " "unexpected padding."); -#if defined(DQN_LEAK_TRACING) -#define Dqn_Debug_TrackAlloc(ptr, size, leak_permitted) Dqn_Debug_TrackAlloc_ (Dqn_Str8_InitCString8(b_stacktrace_get_string()), ptr, size, leak_permitted) -#define Dqn_Debug_TrackDealloc(ptr) Dqn_Debug_TrackDealloc_(Dqn_Str8_InitCString8(b_stacktrace_get_string()), ptr) +// NOTE: [$ASAN] Dqn_Asan ////////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_ASAN_PoisonMemoryRegion (void const volatile *ptr, Dqn_usize size); +DQN_API void Dqn_ASAN_UnpoisonMemoryRegion (void const volatile *ptr, Dqn_usize size); -DQN_API void Dqn_Debug_TrackAlloc_(Dqn_Str8 stack_trace, void *ptr, Dqn_usize size, bool leak_permitted); -DQN_API void Dqn_Debug_TrackDealloc_(Dqn_Str8 stack_trace, void *ptr); -DQN_API void Dqn_Debug_DumpLeaks(); +// NOTE: [$STKT] Dqn_StackTrace //////////////////////////////////////////////////////////////////// +DQN_API Dqn_StackTraceWalkResult Dqn_StackTrace_Walk (Dqn_Arena *arena, uint16_t limit); +DQN_API Dqn_Str8 Dqn_StackTrace_WalkStr8CRT (uint16_t limit, uint16_t skip); +DQN_API bool Dqn_StackTrace_WalkResultIterate(Dqn_StackTraceWalkResultIterator *it, Dqn_StackTraceWalkResult const *walk); +DQN_API Dqn_Str8 Dqn_StackTrace_WalkResultStr8 (Dqn_Arena *arena, Dqn_StackTraceWalkResult const *walk, uint16_t skip); +DQN_API Dqn_Str8 Dqn_StackTrace_WalkResultStr8CRT(Dqn_StackTraceWalkResult const *walk, uint16_t skip); +DQN_API Dqn_Slice Dqn_StackTrace_GetFrames (Dqn_Arena *arena, uint16_t limit); +DQN_API Dqn_StackTraceFrame Dqn_StackTrace_RawFrameToFrame (Dqn_Arena *arena, Dqn_StackTraceRawFrame raw_frame); +DQN_API void Dqn_StackTrace_Print (uint16_t limit); +DQN_API void Dqn_StackTrace_ReloadSymbols (); + +// NOTE: [$DEBG] Dqn_Debug ///////////////////////////////////////////////////////////////////////// +#if defined(DQN_LEAK_TRACKING) +DQN_API void Dqn_Debug_TrackAlloc (void *ptr, Dqn_usize size, bool alloc_can_leak); +DQN_API void Dqn_Debug_TrackDealloc (void *ptr); +DQN_API void Dqn_Debug_DumpLeaks (); #else -#define Dqn_Debug_TrackAlloc(...) -#define Dqn_Debug_TrackDealloc(...) -#define Dqn_Debug_DumpLeaks(...) +#define Dqn_Debug_TrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define Dqn_Debug_TrackDealloc(ptr) do { (void)ptr; } while (0) +#define Dqn_Debug_DumpLeaks() do { } while (0) #endif + + diff --git a/dqn_docs.cpp b/dqn_docs.cpp new file mode 100644 index 0000000..94e9a9f --- /dev/null +++ b/dqn_docs.cpp @@ -0,0 +1,1091 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ +// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| +// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ +// $$ | $$ |$$ | $$ |$$ | \____$$\ +// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | +// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | +// \_______/ \______/ \______/ \______/ +// +// dqn_docs.cpp -- Library documentation via real code examples +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Use this file for documentation and examples of the various APIs in this +// library. Normally docs are written as inline comments in header files, +// however, these quickly go out of date as APIs change. Instead, I provide +// some example code that compiles here that serves to also document the API. +// +// The library header files then become a very minimal reference of exactly the +// function prototypes and definitions instead of massive reams of inline +// comments that visually space out the functions and hinders discoverability +// and/or conciseness of being able to learn the breadth of the APIs. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +DQN_MSVC_WARNING_PUSH +DQN_MSVC_WARNING_DISABLE(4702) // unreachable code +void Dqn_Docs_Demo() +{ + Dqn_Library_Init(Dqn_LibraryOnInit_Nil); + + // NOTE: Dqn_Atomic_SetValue64 ///////////////////////////////////////////////////////////////// + // NOTE: Dqn_Atomic_SetValue32 ///////////////////////////////////////////////////////////////// + // Atomically set the value into the target using an atomic compare and swap + // idiom. The return value of the function is the value that was last stored + // in the target. + { + uint64_t target = 8; + uint64_t value_to_set = 0xCAFE; + if (Dqn_Atomic_SetValue64(&target, value_to_set) == 8) { + // Atomic swap was successful, e.g. the last value that this thread + // observed was '8' which is the value we initialised with e.g. no + // other thread has modified the value. + } + } + + // NOTE: DQN_CHECK ///////////////////////////////////////////////////////////////////////////// + // + // Check the expression trapping in debug, whilst in release- trapping is + // removed and the expression is evaluated as if it were a normal 'if' branch. + // + // This allows handling of the condition gracefully when compiled out but + // traps to notify the developer in builds when it's compiled in. + { + bool flag = true; + if (DQN_CHECKF(flag, "Flag was false!")) { + /// This branch will execute! + } + } + + // NOTE: Dqn_CPUID ///////////////////////////////////////////////////////////////////////////// + // Execute the 'CPUID' instruction which lets you query the capabilities of + // the current CPU. + + // NOTE: DQN_DEFER + // + // A macro that expands to a C++ lambda that executes arbitrary code on + // scope exit. + { + int x = 0; + DQN_DEFER { + x = 3; + }; + x = 1; + // On scope exit, DQN_DEFER object executes and assigns x = 3 + } + + // NOTE: Dqn_DSMap ///////////////////////////////////////////////////////////////////////////// + // + // A hash table configured using the presets recommended by Demitri Spanos + // from the Handmade Network (HMN), + // + // - power of two capacity + // - grow by 2x on load >= 75% + // - open-addressing with linear probing + // - separate large values (esp. variable length values) into a separate table + // - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) + // - chain-repair on delete (rehash items in the probe chain after delete) + // - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) + // + // Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 + // + // This hash-table stores slots (values) separate from the hash mapping. + // Hashes are mapped to slots using the hash-to-slot array which is an array + // of slot indexes. This array intentionally only stores indexes to maximise + // usage of the cache line. Linear probing on collision will only cost a + // couple of cycles to fetch from L1 cache the next slot index to attempt. + // + // The slots array stores values contiguously, non-sorted allowing iteration + // of the map. On element erase, the last element is swapped into the + // deleted element causing the non-sorted property of this table. + // + // The 0th slot (DQN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved + // for a sentinel value, e.g. all zeros value. After map initialisation the + // 'occupied' value of the array will be set to 1 to exclude the sentinel + // from the capacity of the table. Skip the first value if you are iterating + // the hash table! + // + // This hash-table accept either a U64 or a buffer (ptr + len) as the key. + // In practice this covers a majority of use cases (with string, buffer and + // number keys). It also allows us to minimise our C++ templates to only + // require 1 variable which is the Value part of the hash-table simplifying + // interface complexity and cruft brought by C++. + // + // Keys are value-copied into the hash-table. If the key uses a pointer to a + // buffer, this buffer must be valid throughout the lifetime of the hash + // table! + { + // NOTE: Dqn_DSMap_Init ////////////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_Deinit ////////////////////////////////////////////////////////////////// + // + // Initialise a hash table where the table size *must* be a + // power-of-two, otherwise an assert will be triggered. If + // initialisation fails (e.g. memory allocation failure) the table is + // returned zero-initialised where a call to 'IsValid' will return + // false. + // + // The map takes ownership of the arena. This means in practice that if the + // map needs to resize (e.g. because the load threshold of the table is + // exceeded), the arena associated with it will be released and the memory + // will be reallocated with the larger capacity and reassigned to the arena. + // + // In simple terms, when the map resizes it invalidates all memory that was + // previously allocated with the given arena! + // + // A 'Deinit' of the map will similarly deallocate the passed in arena (as + // the map takes ownership of the arena). + Dqn_Arena arena = {}; + Dqn_DSMap map = Dqn_DSMap_Init(&arena, /*size*/ 1024); // Size must be PoT! + DQN_ASSERT(Dqn_DSMap_IsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure) + + // NOTE: Dqn_DSMap_KeyCStringLit /////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_KeyU64 /////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_KeyU64NoHash /////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_KeyBuffer /////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_KeyStr8 /////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_KeyStr8Copy /////////////////////////////////////////////////////////// + // Create a hash-table key where: + // + // KeyCStringLit: Uses a Hash(cstring literal) + // KeyU64: Uses a Hash(U64) + // KeyU64NoHash: Uses a U64 (where it's truncated to 4 bytes) + // KeyBuffer: Uses a Hash(ptr+len) slice of bytes + // KeyStr8: Uses a Hash(string) + // KeyStr8Copy: Uses a Hash(string) that is copied first using the arena + // + // Buffer-based keys memory must persist throughout lifetime of the map. + // Keys are valued copied into the map, alternatively, copy the + // key/buffer before constructing the key. + // + // You *can't* use the map's arena to allocate keys because on resize it + // will deallocate then reallocate the entire arena. + // + // KeyU64NoHash may be useful if you have a source of data that is + // already sufficiently uniformly distributed already (e.g. using 8 + // bytes taken from a SHA256 hash as the key) and the first 4 bytes + // will be used verbatim. + Dqn_DSMapKey key = Dqn_DSMap_KeyStr8(&map, DQN_STR8("Sample Key")); + + // NOTE: Dqn_DSMap_Find //////////////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_Make //////////////////////////////////////////////////////////////////// + // NOTE: Dqn_DSMap_Set //////////////////////////////////////////////////////////////////// + // + // Query or commit key-value pair to the table, where: + // + // Find: does a key-lookup on the table and returns the hash table slot's value + // Make: assigns the key to the table and returns the hash table slot's value + // Set: assigns the key-value to the table and returns the hash table slot's value + // + // A find query will set 'found' to false if it does not exist. + // + // For 'Make' and 'Set', 'found' can be set to 'true' if the item already + // existed in the map prior to the call. If it's the first time the + // key-value pair is being inserted 'found' will be set to 'false'. + // + // If by adding the key-value pair to the table puts the table over 75% load, + // the table will be grown to 2x the current the size before insertion + // completes. + { + Dqn_DSMapResult set_result = Dqn_DSMap_Set(&map, key, 0xCAFE); + DQN_ASSERT(!set_result.found); // First time we are setting the key-value pair, it wasn't previously in the table + DQN_ASSERT(map.occupied == 2); // Sentinel + new element == 2 + } + + // Iterating elements in the array, note that index '0' is the sentinel + // slot! You typically don't care about it! + for (Dqn_usize index = 1; index < map.occupied; index++) { + Dqn_DSMapSlot *it = map.slots + index; + Dqn_DSMapKey it_key = it->key; + int *it_value = &it->value; + DQN_ASSERT(*it_value == 0xCAFE); + + DQN_ASSERT(Dqn_Str8_Init(it_key.payload.buffer.data, it_key.payload.buffer.size) == DQN_STR8("Sample Key")); + } + + // NOTE: Dqn_DSMap_Erase /////////////////////////////////////////////////////////////////// + // + // Remove the key-value pair from the table. If by erasing the key-value + // pair from the table puts the table under 25% load, the table will be + // shrunk by 1/2 the current size after erasing. The table will not shrink + // below the initial size that the table was initialised as. + { + bool erased = Dqn_DSMap_Erase(&map, key); + DQN_ASSERT(erased); + DQN_ASSERT(map.occupied == 1); // Sentinel element + } + + Dqn_DSMap_Deinit(&map, Dqn_ZeroMem_Yes); // Deallocates the 'arena' for us! + } + + // NOTE: Dqn_DSMap_Hash //////////////////////////////////////////////////////////////////////// + // + // Hash the input key using the custom hash function if it's set on the map, + // otherwise uses the default hashing function (32bit Murmur3). + + // NOTE: Dqn_DSMap_HashToSlotIndex ///////////////////////////////////////////////////////////// + // + // Calculate the index into the map's 'slots' array from the given hash. + + // NOTE: Dqn_DSMap_Resize ////////////////////////////////////////////////////////////////////// + // + // Resize the table and move all elements to the new map, note that the new + // size must be a power of two. This function wil fail on memory allocation + // failure, or the requested size is smaller than the current number of + // elements in the map to resize. + + // NOTE: Dqn_FStr8_Max ///////////////////////////////////////////////////////////////////////// + // + // Return the maximum capacity of the string, e.g. the 'N' template + // parameter of FStr8 + + // NOTE: Dqn_FStr8_ToStr8 ////////////////////////////////////////////////////////////////////// + // + // Create a slice of the string into a pointer and length string (Dqn_Str8). + // The lifetime of the slice is bound to the lifetime of the FStr8 and is + // invalidated when the FStr8 is. + + // NOTE: Dqn_JSONBuilder_Build ///////////////////////////////////////////////////////////////// + // + // Convert the internal JSON buffer in the builder into a string. + + // NOTE: Dqn_JSONBuilder_KeyValue, Dqn_JSONBuilder_KeyValueF + // + // Add a JSON key value pair untyped. The value is emitted directly without + // checking the contents of value. + // + // All other functions internally call into this function which is the main + // workhorse of the builder. + + // NOTE: Dqn_JSON_Builder_ObjectEnd + // + // End a JSON object in the builder, generates internally a '}' string + + // NOTE: Dqn_JSON_Builder_ArrayEnd + // + // End a JSON array in the builder, generates internally a ']' string + + // NOTE: Dqn_JSONBuilder_LiteralNamed + // + // Add a named JSON key-value object whose value is directly written to + // the following '"": ' (e.g. useful for emitting the 'null' + // value) + + // NOTE: Dqn_JSONBuilder_U64 ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_U64Named ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_I64 ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_I64Named ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_F64 ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_F64Named ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_Bool ///////////////////////////////////////////////////////////// + // NOTE: Dqn_JSONBuilder_BoolNamed ///////////////////////////////////////////////////////////// + // + // Add the named JSON data type as a key-value object. The named variants + // generates internally the key-value pair, e.g. + // + // "": + // + // And the non-named version emit just the 'value' portion + + // NOTE: Dqn_List_Iterate ////////////////////////////////////////////////////////////////////// + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_List list = Dqn_List_Init(scratch.arena, /*chunk_size*/ 128); + for (Dqn_ListIterator it = {}; Dqn_List_Iterate(&list, &it, 0);) { + int *item = it.data; + (void)item; + } + } + + // NOTE: Dqn_LogProc /////////////////////////////////////////////////////////////////////////// + // + // Function prototype of the logging interface exposed by this library. Logs + // emitted using the Dqn_Log_* family of functions are routed through this + // routine. + + // NOTE: Dqn_FNV1A ///////////////////////////////////////////////////////////////////////////// + { + // Using the default hash as defined by DQN_FNV1A32_SEED and + // DQN_FNV1A64_SEED for 32/64bit hashes respectively + uint32_t buffer1 = 0xCAFE0000; + uint32_t buffer2 = 0xDEAD0000; + { + uint64_t hash = Dqn_FNV1A64_Hash(&buffer1, sizeof(buffer1)); + hash = Dqn_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); // Chained hashing + (void)hash; + } + + // You can use a custom seed by skipping the 'Hash' call and instead + // calling 'Iterate' immediately. + { + uint64_t custom_seed = 0xABCDEF12; + uint64_t hash = Dqn_FNV1A64_Iterate(&buffer1, sizeof(buffer1), custom_seed); + hash = Dqn_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); + (void)hash; + } + } + + // NOTE: Dqn_FmtBuffer3DotTruncate ////////////////////////////////////////////////////////////// + { + char buffer[8] = {}; + int buffer_chars_written = Dqn_FmtBuffer3DotTruncate(buffer, sizeof(buffer), "This string is longer than %d characters", DQN_CAST(int)(sizeof(buffer) - 1)); + if (0) { // Prints "This ..." which is exactly 8 characters long + printf("%.*s", buffer_chars_written, buffer); + } + } + + // NOTE: Dqn_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. + + // NOTE: Dqn_OS_DateUnixTime + // + // Produce the time elapsed since the unix epoch + { + uint64_t now = Dqn_OS_DateUnixTime(); + (void)now; + } + + // NOTE: Dqn_OS_FileDelete + // + // This function can only delete files and it can *only* delete directories + // if it is empty otherwise this function fails. + + // NOTE: Dqn_OS_WriteAllSafe + // Writes the file at the path first by appending '.tmp' to the 'path' to + // write to. If the temporary file is written successfully then the file is + // copied into 'path', for example: + // + // path: C:/Home/my.txt + // tmp_path: C:/Home/my.txt.tmp + // + // If 'tmp_path' is written to successfuly, the file will be copied over into + // 'path'. + if (0) { + Dqn_OS_WriteAllSafe(/*path*/ DQN_STR8("C:/Home/my.txt"), /*buffer*/ DQN_STR8("Hello world")); + } + + // NOTE: Dqn_OS_EstimateTSCPerSecond /////////////////////////////////////////////////////////// + // + // Estimate how many timestamp count's (TSC) there are per second. TSC + // is evaluated by calling __rdtsc() or the equivalent on the platform. This + // value can be used to convert TSC durations into seconds. + // + // The 'duration_ms_to_gauge_tsc_frequency' parameter specifies how many + // milliseconds to spend measuring the TSC rate of the current machine. + // 100ms is sufficient to produce a fairly accurate result with minimal + // blocking in applications if calculated on startup.. + // + // This may return 0 if querying the CPU timestamp counter is not supported + // on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). + + // NOTE: Dqn_OS_EXEDir ///////////////////////////////////////////////////////////////////////// + // + // Retrieve the executable directory without the trailing '/' or ('\' for + // windows). If this fails an empty string is returned. + + // NOTE: Dqn_OS_PerfCounterFrequency /////////////////////////////////////////////////////////// + // + // Get the number of ticks in the performance counter per second for the + // operating system you're running on. This value can be used to calculate + // duration from OS performance counter ticks. + + // NOTE: Dqn_OS_Path* ////////////////////////////////////////////////////////////////////////// + // Construct paths ensuring the native OS path separators are used in the + // string. In 99% of cases you can use 'PathConvertF' which converts the + // given path in one shot ensuring native path separators in the string. + // + // path: C:\Home/My/Folder + // converted: C:/Home/My/Folder (On Unix) + // C:\Home\My\Folder (On Windows) + // + // If you need to construct a path dynamically you can use the builder-esque + // interface to build a path's step-by-step using the 'OSPath' data structure. + // With this API you can append paths piece-meal to build the path after all + // pieces are appended. + // + // You may append a singular or nested path to the builder. In the builder, + // the string is scanned and separated into path separated chunks and stored + // in the builder, e.g. these are all valid to pass into 'PathAdd', + // 'PathAddRef' ... e.t.c + // + // "path/to/your/desired/folder" is valid + // "path" is valid + // "path/to\your/desired\folder" is valid + // + // 'PathPop' removes the last appended path from the current path stored in + // the 'OSPath': + // + // path: path/to/your/desired/folder + // popped_path: path/to/your/desired + + // NOTE: Dqn_OS_SecureRNGBytes ///////////////////////////////////////////////////////////////// + // + // Generate cryptographically secure bytes + + // NOTE: Dqn_PCG32 ///////////////////////////////////////////////////////////////////////////// + // + // Random number generator of the PCG family. Implementation taken from + // Martins Mmozeiko from Handmade Network. + // https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 + { + Dqn_PCG32 rng = Dqn_PCG32_Init(0xb917'a66c'1d9b'3bd8); + + // NOTE: Dqn_PCG32_Range /////////////////////////////////////////////////////////////////// + // + // Generate a value in the [low, high) interval + uint32_t u32_value = Dqn_PCG32_Range(&rng, 32, 64); + DQN_ASSERT(u32_value >= 32 && u32_value < 64); + + // NOTE: Dqn_PCG32_NextF32 ///////////////////////////////////////////////////////////////// + // NOTE: Dqn_PCG32_NextF64 ///////////////////////////////////////////////////////////////// + // + // Generate a float/double in the [0, 1) interval + Dqn_f64 f64_value = Dqn_PCG32_NextF64(&rng); + DQN_ASSERT(f64_value >= 0.f && f64_value < 1.f); + + // NOTE: Dqn_PCG32_Advance ///////////////////////////////////////////////////////////////// + // + // Step the random number generator by 'delta' steps + Dqn_PCG32_Advance(&rng, /*delta*/ 5); + } + + #if !defined(DQN_NO_PROFILER) + // NOTE: [$PROF] Dqn_Profiler ////////////////////////////////////////////////////////////////// + // + // A profiler based off Casey Muratori's Computer Enhance course, Performance + // Aware Programming. This profiler measures function elapsed time using the + // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count + // that can be converted into a duration. + // + // This profiler uses a double buffer scheme for storing profiling markers. + // After an application's typical update/frame cycle you can swap the profiler's + // buffer whereby the front buffer contains the previous frames profiling + // metrics and the back buffer will be populated with the new frame's profiling + // metrics. + { + uint64_t tsc_per_seconds = Dqn_OS_EstimateTSCPerSecond(/*duration_ms_to_gauge_tsc_frequency*/ 100); + enum Zone { Zone_MainLoop, Zone_Count }; + Dqn_ProfilerZone profiler_zone_main_update = Dqn_Profiler_BeginZone(Zone_MainLoop); + + // NOTE: Dqn_Profiler_AnchorBuffer ///////////////////////////////////////////////////// + // + // Retrieve the requested buffer from the profiler for + // writing/reading profiling metrics. Pass in the enum to specify + // which buffer to grab from the profiler. + // + // The front buffer contains the previous frame's profiling metrics + // and the back buffer is where the profiler is currently writing + // to. + // + // For end user intents and purposes, you likely only need to read + // the front buffer which contain the metrics that you can visualise + // regarding the most profiling metrics recorded. + + Dqn_ProfilerAnchor *anchors = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Front); + for (size_t index = 0; index < Zone_Count; index++) { + Dqn_ProfilerAnchor *anchor = anchors + index; + + // Print the result like so + if (0) { + printf("%.*s[%u] %zu cycles (%.1fms)\n", + DQN_STR_FMT(anchor->name), + anchor->hit_count, + anchor->tsc_inclusive, + anchor->tsc_inclusive * tsc_per_seconds * 1000.f); + } + } + Dqn_Profiler_EndZone(profiler_zone_main_update); + Dqn_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! + + *g_dqn_library->profiler = {}; + } + #endif // !defined(DQN_NO_PROFILER) + + // NOTE: Dqn_Raycast_LineIntersectV2 /////////////////////////////////////////////////////////// + // Calculate the intersection point of 2 rays returning a `t` value + // which is how much along the direction of the 'ray' did the intersection + // occur. + // + // The arguments passed in do not need to be normalised for the function to + // work. + + // NOTE: Dqn_Safe_* //////////////////////////////////////////////////////////////////////////// + // + // Performs the arithmetic operation and uses DQN_CHECK on the operation to + // check if it overflows. If it overflows the MAX value of the integer is + // returned in add and multiply operations, and, the minimum is returned in + // subtraction and division. + + // NOTE: Dqn_Safe_SaturateCast* //////////////////////////////////////////////////////////////// + // + // Truncate the passed in value to the return type clamping the resulting + // value to the max value of the desired data type. It DQN_CHECK's the + // truncation. + // + // The following sentinel values are returned when saturated, + // USize -> Int: INT_MAX + // USize -> I8: INT8_MAX + // USize -> I16: INT16_MAX + // USize -> I32: INT32_MAX + // USize -> I64: INT64_MAX + // + // U64 -> UInt: UINT_MAX + // U64 -> U8: UINT8_MAX + // U64 -> U16: UINT16_MAX + // U64 -> U32: UINT32_MAX + // + // USize -> U8: UINT8_MAX + // USize -> U16: UINT16_MAX + // USize -> U32: UINT32_MAX + // USize -> U64: UINT64_MAX + // + // ISize -> Int: INT_MIN or INT_MAX + // ISize -> I8: INT8_MIN or INT8_MAX + // ISize -> I16: INT16_MIN or INT16_MAX + // ISize -> I32: INT32_MIN or INT32_MAX + // ISize -> I64: INT64_MIN or INT64_MAX + // + // ISize -> UInt: 0 or UINT_MAX + // ISize -> U8: 0 or UINT8_MAX + // ISize -> U16: 0 or UINT16_MAX + // ISize -> U32: 0 or UINT32_MAX + // ISize -> U64: 0 or UINT64_MAX + // + // I64 -> ISize: DQN_ISIZE_MIN or DQN_ISIZE_MAX + // I64 -> I8: INT8_MIN or INT8_MAX + // I64 -> I16: INT16_MIN or INT16_MAX + // I64 -> I32: INT32_MIN or INT32_MAX + // + // Int -> I8: INT8_MIN or INT8_MAX + // Int -> I16: INT16_MIN or INT16_MAX + // Int -> U8: 0 or UINT8_MAX + // Int -> U16: 0 or UINT16_MAX + // Int -> U32: 0 or UINT32_MAX + // Int -> U64: 0 or UINT64_MAX + + // NOTE: Dqn_StackTrace //////////////////////////////////////////////////////////////////////// + // Emit stack traces at the calling site that these functions are invoked + // from. + // + // For some applications, it may be viable to generate raw stack traces and + // store just the base addresses of the call stack from the 'Walk' + // functions. This reduces the memory overhead and required to hold onto + // stack traces and resolve the addresses on-demand when required. + // + // However if your application is loading and/or unloading shared libraries, + // on Windows it may be impossible for the application to resolve raw base + // addresses if they become invalid over time. In these applications you + // must convert the raw stack traces before the unloading occurs, and when + // loading new shared libraries, 'ReloadSymbols' must be called to ensure + // the debug APIs are aware of how to resolve the new addresses imported + // into the address space. + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + + // NOTE: Dqn_StackTrace_Walk /////////////////////////////////////////////////////////////// + // + // Generate a stack trace as a series of addresses to the base of the + // functions on the call-stack at the current instruction pointer. The + // addresses are stored in order from the current executing function + // first to the most ancestor function last in the walk. + Dqn_StackTraceWalkResult walk = Dqn_StackTrace_Walk(scratch.arena, /*depth limit*/ 128); + + // Loop over the addresses produced in the stack trace + for (Dqn_StackTraceWalkResultIterator it = {}; Dqn_StackTrace_WalkResultIterate(&it, &walk); ) { + + // NOTE: Dqn_StackTrace_RawFrameToFrame //////////////////////////////////////////////// + // + // Converts the base address into a human readable stack trace + // entry (e.g. address, line number, file and function name). + Dqn_StackTraceFrame frame = Dqn_StackTrace_RawFrameToFrame(scratch.arena, it.raw_frame); + + // You may then print out the frame like so + if (0) + printf("%.*s(%I64u): %.*s\n", DQN_STR_FMT(frame.file_name), frame.line_number, DQN_STR_FMT(frame.function_name)); + } + + // If you load new shared-libraries into the address space it maybe + // necessary to call into 'ReloadSymbols' to ensure that the OS is able + // to resolve the new addresses. + Dqn_StackTrace_ReloadSymbols(); + + // NOTE: Dqn_StackTrace_GetFrames ////////////////////////////////////////////////////////// + // + // Helper function to create a stack trace and automatically convert the + // raw frames into human readable frames. This function effectively + // calls 'Walk' followed by 'RawFrameToFrame'. + Dqn_Slice frames = Dqn_StackTrace_GetFrames(scratch.arena, /*depth limit*/ 128); + (void)frames; + } + + // NOTE: Dqn_Str8_Alloc //////////////////////////////////////////////////////////////////////// + // + // Allocates a string with the requested 'size'. An additional byte is + // always requested from the allocator to null-terminate the buffer. This + // allows the string to be used with C-style string APIs. + // + // The returned string's 'size' member variable does *not* include this + // additional null-terminating byte. + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_Str8_Alloc(scratch.arena, /*size*/ 1, Dqn_ZeroMem_Yes); + DQN_ASSERT(string.size == 1); + DQN_ASSERT(string.data[string.size] == 0); // It is null-terminated! + } + + // NOTE: Dqn_Str8_BinarySplit ////////////////////////////////////////////////////////////////// + // + // Splits a string into 2 substrings occuring prior and after the first + // occurence of the delimiter. Neither strings include the matched + // delimiter. If no delimiter is found, the 'rhs' of the split will be + // empty. + { + Dqn_Str8BinarySplitResult dot_split = Dqn_Str8_BinarySplit(/*string*/ DQN_STR8("abc.def.ghi"), /*delimiter*/ DQN_STR8(".")); + Dqn_Str8BinarySplitResult slash_split = Dqn_Str8_BinarySplit(/*string*/ DQN_STR8("abc.def.ghi"), /*delimiter*/ DQN_STR8("/")); + DQN_ASSERT(dot_split.lhs == DQN_STR8("abc") && dot_split.rhs == DQN_STR8("def.ghi")); + DQN_ASSERT(slash_split.lhs == DQN_STR8("abc.def.ghi") && slash_split.rhs == DQN_STR8("")); + + // Loop that walks the string and produces ("abc", "def", "ghi") + for (Dqn_Str8 it = DQN_STR8("abc.def.ghi"); it.size; ) { + Dqn_Str8BinarySplitResult split = Dqn_Str8_BinarySplit(it, DQN_STR8(".")); + Dqn_Str8 chunk = split.lhs; // "abc", "def", ... + it = split.rhs; + (void)chunk; + } + } + + // NOTE: Dqn_Str8_FileNameFromPath ///////////////////////////////////////////////////////////// + // + // Takes a slice to the file name from a file path. The file name is + // evaluated by searching from the end of the string backwards to the first + // occurring path separator '/' or '\'. If no path separator is found, the + // original string is returned. This function preserves the file extension + // if there were any. + { + { + Dqn_Str8 string = Dqn_Str8_FileNameFromPath(DQN_STR8("C:/Folder/item.txt")); + DQN_ASSERT(string == DQN_STR8("item.txt")); + } + { + // TODO(doyle): Intuitively this seems incorrect. Empty string instead? + Dqn_Str8 string = Dqn_Str8_FileNameFromPath(DQN_STR8("C:/Folder/")); + DQN_ASSERT(string == DQN_STR8("C:/Folder")); + } + { + Dqn_Str8 string = Dqn_Str8_FileNameFromPath(DQN_STR8("C:/Folder")); + DQN_ASSERT(string == DQN_STR8("Folder")); + } + } + + // NOTE: Dqn_Str8_FilePathNoExtension ////////////////////////////////////////////////////////// + // + // This function preserves the original string if no extension was found. + // An extension is defined as the substring after the last '.' encountered + // in the string. + { + Dqn_Str8 string = Dqn_Str8_FilePathNoExtension(DQN_STR8("C:/Folder/item.txt.bak")); + DQN_ASSERT(string == DQN_STR8("C:/Folder/item.txt")); + } + + // NOTE: Dqn_Str8_FileNameNoExtension ////////////////////////////////////////////////////////// + // + // This function is the same as calling 'FileNameFromPath' followed by + // 'FilePathNoExtension' + { + Dqn_Str8 string = Dqn_Str8_FileNameNoExtension(DQN_STR8("C:/Folder/item.txt.bak")); + DQN_ASSERT(string == DQN_STR8("item.txt")); + } + + // NOTE: Dqn_Str8_Replace /////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8_ReplaceInsensitive /////////////////////////////////////////////////////////// + // + // Replace any matching substring 'find' with 'replace' in the passed in + // 'string'. The 'start_index' may be specified to offset which index the + // string will start doing replacements from. + // + // String replacements are not done inline and the returned string will + // always be a newly allocated copy, irrespective of if any replacements + // were done or not. + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_Str8_Replace(/*string*/ DQN_STR8("Foo Foo Bar"), + /*find*/ DQN_STR8("Foo"), + /*replace*/ DQN_STR8("Moo"), + /*start_index*/ 1, + /*arena*/ scratch.arena, + /*eq_case*/ Dqn_Str8EqCase_Sensitive); + DQN_ASSERT(string == DQN_STR8("Foo Moo Bar")); + } + + // NOTE: Dqn_Str8_Segment ////////////////////////////////////////////////////////////////////// + // + // Add a delimiting 'segment_char' every 'segment_size' number of characters + // in the string. + // + // Reverse segment delimits the string counting 'segment_size' from the back + // of the string. + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_Str8_Segment(scratch.arena, /*string*/ DQN_STR8("123456789"), /*segment_size*/ 3, /*segment_char*/ ','); + DQN_ASSERT(string == DQN_STR8("123,456,789")); + } + + // NOTE: Dqn_Str8_Split //////////////////////////////////////////////////////////////////////// + { + // Splits the string at each delimiter into substrings occuring prior and + // after until the next delimiter. + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + { + Dqn_Slice splits = Dqn_Str8_SplitAlloc(/*arena*/ scratch.arena, + /*string*/ DQN_STR8("192.168.8.1"), + /*delimiter*/ DQN_STR8("."), + /*mode*/ Dqn_Str8SplitIncludeEmptyStrings_No); + DQN_ASSERT(splits.size == 4); + DQN_ASSERT(splits.data[0] == DQN_STR8("192") && splits.data[1] == DQN_STR8("168") && splits.data[2] == DQN_STR8("8") && splits.data[3] == DQN_STR8("1")); + } + + // You can include empty strings that occur when splitting by setting + // the split mode to include empty strings. + { + Dqn_Slice splits = Dqn_Str8_SplitAlloc(/*arena*/ scratch.arena, + /*string*/ DQN_STR8("a--b"), + /*delimiter*/ DQN_STR8("-"), + /*mode*/ Dqn_Str8SplitIncludeEmptyStrings_Yes); + DQN_ASSERT(splits.size == 3); + DQN_ASSERT(splits.data[0] == DQN_STR8("a") && splits.data[1] == DQN_STR8("") && splits.data[2] == DQN_STR8("b")); + } + } + + // NOTE: Dqn_Str8_ToI64 //////////////////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8_ToU64 //////////////////////////////////////////////////////////////////////// + // + // Convert a number represented as a string to a signed 64 bit number. + // + // The 'separator' is an optional digit separator for example, if + // 'separator' is set to ',' then '1,234' will successfully be parsed to + // '1234'. If no separator is desired, you may pass in '0' in which + // '1,234' will *not* be succesfully parsed. + // + // Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, + // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of + // digits, the seperator or the permitted prefixes as previously mentioned + // otherwise this function will return false, i.e. "1234 dog" will cause the + // function to return false, however, the output is greedily converted and + // will be evaluated to "1234". + // + // 'ToU64' only '+' prefix is permitted + // 'ToI64' either '+' or '-' prefix is permitted + { + { + Dqn_Str8ToI64Result result = Dqn_Str8_ToI64(DQN_STR8("-1,234"), /*separator*/ ','); + DQN_ASSERT(result.success && result.value == -1234); + } + { + Dqn_Str8ToI64Result result = Dqn_Str8_ToI64(DQN_STR8("-1,234"), /*separator*/ 0); + DQN_ASSERT(!result.success && result.value == 1); // 1 because it's a greedy conversion + } + } + + // NOTE: Dqn_Str8_TrimByteOrderMark //////////////////////////////////////////////////////////// + // + // Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the + // string if it's present. + + // NOTE: DQN_STR_FMT /////////////////////////////////////////////////////////////////////////// + // + // Unpacks a string struct that has the fields {.data, .size} for printing a + // pointer and length style string using the printf format specifier "%.*s" + // + // printf("%.*s\n", DQN_STR_FMT(DQN_STR8("Hello world"))); + + // NOTE: Dqn_Str8Builder_AppendF //////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8Builder_AppendFV //////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8Builder_AppendRef //////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8Builder_AppendCopy //////////////////////////////////////////////////////////// + // + // - Appends a string to the string builder as follows + // + // AppendRef: Stores the string slice by value + // AppendCopy: Stores the string slice by copy (with builder's arena) + // AppendF/V: Constructs a format string and calls 'AppendRef' + + // NOTE: Dqn_Str8Builder_Build /////////////////////////////////////////////////////////// + // NOTE: Dqn_Str8Builder_BuildCRT /////////////////////////////////////////////////////////// + // + // Constructs the final string by merging all the appended strings into + // one merged string. + // + // The CRT variant calls into 'malloc' and the string *must* be released + // using 'free'. + + // NOTE: Dqn_Str8Builder_BuildSlice /////////////////////////////////////////////////////////// + // + // Constructs the final string into an array of strings (e.g. a slice) + + // NOTE: Dqn_TicketMutex /////////////////////////////////////////////////////////////////////// + // + // 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. + { + Dqn_TicketMutex mutex = {}; + Dqn_TicketMutex_Begin(&mutex); // Simple procedural mutual exclusion lock + Dqn_TicketMutex_End(&mutex); + + // NOTE: Dqn_TicketMutex_MakeTicket //////////////////////////////////////////////////////// + // + // Request the next available ticket for locking from the mutex. + Dqn_uint ticket = Dqn_TicketMutex_MakeTicket(&mutex); + + if (Dqn_TicketMutex_CanLock(&mutex, ticket)) { + // NOTE: Dqn_TicketMutex_BeginTicket /////////////////////////////////////////////////// + // + // Locks the mutex using the given ticket if possible. If it's not + // the next ticket to be locked the executing thread will block + // until the mutex can lock the ticket, i.e. All prior tickets are + // returned, in sequence, to the mutex. + Dqn_TicketMutex_BeginTicket(&mutex, ticket); + Dqn_TicketMutex_End(&mutex); + } + } + + // NOTE: Dqn_ThreadContext ///////////////////////////////////////////////////////////////////// + // + // 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. + // + // 99% of the time you will want Dqn_Scratch_Get(...) which returns you a + // temporary arena for function lifetime allocations. On scope exit, the + // arena is cleared out. + // + // This library's paradigm revolves heavily around arenas including scratch + // arenas into child functions for temporary calculations. If an arena is + // passed into a function, this poses a problem sometimes known as + // 'arena aliasing'. + // + // If an arena aliases another arena (e.g. the arena passed in) is the same + // as the scratch arena requested in the function, we risk the scratch arena + // on scope exit deallocating memory belonging to the caller. + // + // To avoid this we the 'Dqn_Scratch_Get(...)' API takes in a list of arenas + // to ensure that we provide a scratch arena that *won't* alias with the + // caller's arena. If arena aliasing occurs, with ASAN on, generally + // the library will trap and report use-after-poison once violated. + { + Dqn_Scratch scratch_a = Dqn_Scratch_Get(nullptr); + + // Now imagine we call a function where we pass scratch_a.arena down + // into it .. If we call scratch again, we need to pass in the arena + // to prevent aliasing. + Dqn_Scratch scratch_b = Dqn_Scratch_Get(scratch_a.arena); + DQN_ASSERT(scratch_a.arena != scratch_b.arena); + } + + // @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 + + // NOTE: Dqn_U64ToStr8 ///////////////////////////////////////////////////////////////////////// + { + Dqn_U64Str8 string = Dqn_U64ToStr8(123123, ','); + if (0) // Prints "123,123" + printf("%.*s", DQN_STR_FMT(string)); + } + + // NOTE: Dqn_U64ToAge ////////////////////////////////////////////////////////////////////////// + { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_U64ToAge(scratch.arena, DQN_HOURS_TO_S(2) + DQN_MINS_TO_S(30), Dqn_U64AgeUnit_All); + if (0) // Prints "2hr 30m" + printf("%.*s", DQN_STR_FMT(string)); + } + + // NOTE: Dqn_VArray //////////////////////////////////////////////////////////////////////////// + // + // An array that is backed by virtual memory by reserving addressing space + // and comitting pages as items are allocated in the array. This array never + // reallocs, instead you should reserve the upper bound of the memory you + // will possibly ever need (e.g. 16GB) and let the array commit physical + // pages on demand. + // + // On 64 bit operating systems you are given 48 bits of addressable space + // giving you 256 TB of reservable memory. This gives you practically + // an unlimited array capacity that avoids reallocs and only consumes memory + // that is actually occupied by the array. + // + // Each page that is committed into the array will be at page/allocation + // granularity which are always cache aligned. This array essentially retains + // all the benefits of normal arrays, + // + // - contiguous memory + // - O(1) random access + // - O(N) iterate + // + // In addition to no realloc on expansion or shrinking. + // + { + // NOTE: Dqn_VArray_Init /////////////////////////////////////////////////////////// + // NOTE: Dqn_VArray_InitByteSize /////////////////////////////////////////////////////////// + // + // Initialise an array with the requested byte size or item capacity + // respectively. The returned array may have a higher capacity than the + // requested amount since requested memory from the OS may have a certain + // alignment requirement (e.g. on Windows reserve/commit are 64k/4k + // aligned). + Dqn_VArray array = Dqn_VArray_Init(1024, Dqn_ArenaFlag_Nil); + DQN_ASSERT(array.size == 0 && array.max >= 1024); + + // NOTE: Dqn_VArray_Make ////////////////////////////////////////////////////////////// + // NOTE: Dqn_VArray_Add ////////////////////////////////////////////////////////////// + // NOTE: Dqn_VArray_MakeArray ////////////////////////////////////////////////////////////// + // NOTE: Dqn_VArray_AddArray ////////////////////////////////////////////////////////////// + // + // Allocate items from the array where: + // + // Make: creates a zero-init item from the array + // Add: creates a zero-init item and memcpy passed in data into the item + // + // If the array has run out of capacity or was never initialised, a null + // pointer is returned. + int *item = Dqn_VArray_Add(&array, 0xCAFE); + DQN_ASSERT(*item == 0xCAFE && array.size == 1); + + // NOTE: Dqn_VArray_AddCArray ///////////////////////////////////////////////////////////// + Dqn_VArray_AddCArray(&array, {1, 2, 3}); + DQN_ASSERT(array.size == 4); + + // TODO(doyle): There's a bug here with the negative erase! + // Loop over the array items and erase 1 item. + #if 0 + for (Dqn_usize index = 0; index < array.size; index++) { + if (index != 1) + continue; + + // NOTE: Dqn_VArray_EraseRange ///////////////////////////////////////////////////////// + // + // Erase the next 'count' items at 'begin_index' in the array. + // 'count' can be positive or negative which dictates the if we + // erase forward from the 'begin_index' or in reverse. + // + // This operation will invalidate all pointers to the array! + // + // A stable erase will shift all elements after the erase ranged + // into the range preserving the order of prior elements. Unstable + // erase will move the tail elements into the range being erased. + // + // Erase range returns a result that contains the next iterator + // index that can be used to update the your for loop index if you + // are trying to iterate over the array. + + // TODO(doyle): There's a bug here! This doesn't work. + // Erase index 0 with the negative count! + Dqn_ArrayEraseResult erase_result = Dqn_VArray_EraseRange(&array, + /*begin_index*/ index, + /*count*/ -1, + /*erase*/ Dqn_ArrayErase_Stable); + DQN_ASSERT(erase_result.items_erased == 1); + + // Use the index returned to continue linearly iterating the array + index = erase_result.it_index; + DQN_ASSERT(array.data[index + 1] == 2); // Next loop iteration will process item '2' + } + + DQN_ASSERT(array.size == 3 && + array.data[0] == 1 && + array.data[1] == 2 && + array.data[2] == 3); + #endif + + // NOTE: Dqn_VArray_Reserve //////////////////////////////////////////////////////////////////// + // + // Ensure that the requested number of items are backed by physical pages + // from the OS. Calling this pre-emptively will minimise syscalls into the + // kernel to request memory. The requested items will be rounded up to the + // in bytes to the allocation granularity of OS allocation APIs hence the + // reserved space may be greater than the requested amount (e.g. this is 4k + // on Windows). + Dqn_VArray_Reserve(&array, /*count*/ 8); + + Dqn_VArray_Deinit(&array); + } + + // NOTE: Dqn_Win_LastError ///////////////////////////////////////////////////////////// + // NOTE: Dqn_Win_ErrorCodeToMsg ///////////////////////////////////////////////////////////// + if (0) { + // Generate the error string for the last Win32 API called that return + // an error value. + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_WinError get_last_error = Dqn_Win_LastError(scratch.arena); + printf("Error (%lu): %.*s", get_last_error.code, DQN_STR_FMT(get_last_error.msg)); + + // Alternatively, pass in the error code directly + Dqn_WinError error_msg_for_code = Dqn_Win_ErrorCodeToMsg(scratch.arena, /*error_code*/ 0); + printf("Error (%lu): %.*s", error_msg_for_code.code, DQN_STR_FMT(error_msg_for_code.msg)); + } + + // NOTE: Dqn_Win_MakeProcessDPIAware /////////////////////////////////////////////////////////// + // + // 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. + + // NOTE: Dqn_Win_Str8ToStr16 ///////////////////////////////////////////////////////////// + // NOTE: Dqn_Win_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// + // NOTE: Dqn_Win_Str16ToStr8 ///////////////////////////////////////////////////////////// + // NOTE: Dqn_Win_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// + // + // Convert a UTF8 <-> 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. + // + // Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) + // written/required for conversion. 0 if there was a conversion error and can be + // queried using 'Dqn_Win_LastError' + + // NOTE: Dqn_Win_FolderIterate ///////////////////////////////////////////////////////////////// + // + // Iterate the files within the passed in folder + if (0) { + for (Dqn_Win_FolderIterator it = {}; Dqn_Win_FolderIterate(DQN_STR8("C:/your/path/"), &it); ) { + printf("%.*s\n", DQN_STR_FMT(it.file_name)); + } + } +} +DQN_MSVC_WARNING_POP diff --git a/dqn_external.cpp b/dqn_external.cpp index 5a6890d..e5f6b16 100644 --- a/dqn_external.cpp +++ b/dqn_external.cpp @@ -1,5 +1,20 @@ -// NOTE: [$STBS] stb_sprintf ======================================================================= -#if !defined(DQN_STB_SPRINTF_HEADER_ONLY) +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ +// $$ _____|$$ | $$ |\__$$ __|$$ _____|$$ __$$\ $$$\ $$ |$$ __$$\ $$ | +// $$ | \$$\ $$ | $$ | $$ | $$ | $$ |$$$$\ $$ |$$ / $$ |$$ | +// $$$$$\ \$$$$ / $$ | $$$$$\ $$$$$$$ |$$ $$\$$ |$$$$$$$$ |$$ | +// $$ __| $$ $$< $$ | $$ __| $$ __$$< $$ \$$$$ |$$ __$$ |$$ | +// $$ | $$ /\$$\ $$ | $$ | $$ | $$ |$$ |\$$$ |$$ | $$ |$$ | +// $$$$$$$$\ $$ / $$ | $$ | $$$$$$$$\ $$ | $$ |$$ | \$$ |$$ | $$ |$$$$$$$$\ +// \________|\__| \__| \__| \________|\__| \__|\__| \__|\__| \__|\________| +// +// dqn_external.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#if !defined(DQN_USE_STD_PRINTF) && !defined(DQN_STB_SPRINTF_HEADER_ONLY) +// NOTE: [$STBS] stb_sprintf /////////////////////////////////////////////////////////////////////// #define STB_SPRINTF_IMPLEMENTATION #ifdef STB_SPRINTF_IMPLEMENTATION @@ -132,7 +147,6 @@ static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uin return (stbsp__uint32)(sn - s); } - #if defined(__clang__) __attribute__((no_sanitize("undefined"))) #endif @@ -1160,7 +1174,7 @@ done: #undef stbsp__flush_cb #undef stbsp__cb_buf_clamp -// ============================================================================ +// //////////////////////////////////////////////////////////////////////////// // wrapper functions STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) @@ -1264,7 +1278,7 @@ STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); } -// ======================================================================= +// /////////////////////////////////////////////////////////////////////// // low level float utility functions #ifndef STB_SPRINTF_NOFLOAT @@ -1694,4 +1708,4 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ -#endif // DQN_STB_SPRINTF_HEADER_ONLY +#endif // !defined(DQN_USE_STD_PRINTF) && !defined(DQN_STB_SPRINTF_HEADER_ONLY) diff --git a/dqn_external.h b/dqn_external.h index 4f47476..3f1b7c8 100644 --- a/dqn_external.h +++ b/dqn_external.h @@ -1,4 +1,24 @@ -// NOTE: [$OS_H] OS Headers ======================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ +// $$ _____|$$ | $$ |\__$$ __|$$ _____|$$ __$$\ $$$\ $$ |$$ __$$\ $$ | +// $$ | \$$\ $$ | $$ | $$ | $$ | $$ |$$$$\ $$ |$$ / $$ |$$ | +// $$$$$\ \$$$$ / $$ | $$$$$\ $$$$$$$ |$$ $$\$$ |$$$$$$$$ |$$ | +// $$ __| $$ $$< $$ | $$ __| $$ __$$< $$ \$$$$ |$$ __$$ |$$ | +// $$ | $$ /\$$\ $$ | $$ | $$ | $$ |$$ |\$$$ |$$ | $$ |$$ | +// $$$$$$$$\ $$ / $$ | $$ | $$$$$$$$\ $$ | $$ |$$ | \$$ |$$ | $$ |$$$$$$$$\ +// \________|\__| \__| \__| \________|\__| \__|\__| \__|\__| \__|\________| +// +// dqn_external.h -- Third party dependencies +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$OS_H] OS Headers //////////////////////////////////////////////////////////////////////// +#if !defined(DQN_OS_WIN32) || defined(DQN_OS_WIN32_USE_PTHREADS) + #include + #include +#endif + #if defined(DQN_OS_UNIX) || defined(DQN_PLATFORM_EMSCRIPTEN) #include // errno #include // O_RDONLY ... etc @@ -18,21 +38,38 @@ #endif #endif -// NOTE: [$STBS] stb_sprintf ======================================================================= - -#if defined(DQN_COMPILER_MSVC) - // NOTE: stb_sprintf assumes c-string literals are 4 byte aligned which is - // always true, however, reading past the end of a string whose size is not - // a multiple of 4 is UB causing ASAN to complain. This is practically safe - // and guaranteed by all compilers so we mute this. - // - // ==12072==ERROR: AddressSanitizer: global-buffer-overflow on address - // READ of size 4 at 0x7ff6f442a0d8 thread T0 - // #0 0x7ff6f42d3be8 in stbsp_vsprintfcb C:\Home\Code\dqn\dqn_external.cpp:199 - - #define STBSP__ASAN __declspec(no_sanitize_address) +#if defined(DQN_PLATFORM_EMSCRIPTEN) + #include // emscripten_fetch (for Dqn_OSHttpResponse) #endif +// NOTE: [$STBS] stb_sprintf /////////////////////////////////////////////////////////////////////// +#if defined(DQN_USE_STD_PRINTF) + #include + #define DQN_SPRINTF(...) sprintf(__VA_ARGS__) + #define DQN_SNPRINTF(...) snprintf(__VA_ARGS__) + #define DQN_VSPRINTF(...) vsprintf(__VA_ARGS__) + #define DQN_VSNPRINTF(...) vsnprintf(__VA_ARGS__) +#else + #define DQN_SPRINTF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) + #define DQN_SNPRINTF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) + #define DQN_VSPRINTF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) + #define DQN_VSNPRINTF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) + + #if (DQN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)) && defined(DQN_COMPILER_MSVC) + #error The STB implementation of sprintf triggers MSVC's implementation of ASAN. Compiling ASAN with STB sprintf is not supported. + + // NOTE: stb_sprintf assumes c-string literals are 4 byte aligned which is + // always true, however, reading past the end of a string whose size is not + // a multiple of 4 is UB causing ASAN to complain. This is practically safe + // and guaranteed by all compilers so we mute this. + // + // ==12072==ERROR: AddressSanitizer: global-buffer-overflow on address + // READ of size 4 at 0x7ff6f442a0d8 thread T0 + // #0 0x7ff6f42d3be8 in stbsp_vsprintfcb C:\Home\Code\dqn\dqn_external.cpp:199 + + #define STBSP__ASAN __declspec(no_sanitize_address) + #endif + // stb_sprintf - v1.10 - public domain snprintf() implementation // originally by Jeff Roberts / RAD Game Tools, 2015/10/20 // http://github.com/nothings/stb @@ -96,7 +133,7 @@ As a comparison, when using MSVC static libs, calling sprintf drags in 16K. API: -==== +//// int stbsp_sprintf( char * buf, char const * fmt, ... ) int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) Convert an arg list into a buffer. stbsp_snprintf always returns @@ -119,7 +156,7 @@ void stbsp_set_separators( char comma, char period ) Set the comma and period characters to use. FLOATS/DOUBLES: -=============== +/////////////// This code uses a internal float->ascii conversion method that uses doubles with error correction (double-doubles, for ~105 bits of precision). This conversion is round-trip perfect - that is, an atof @@ -134,13 +171,13 @@ If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT and you'll save 4K of code space. 64-BIT INTS: -============ +//////////// This library also supports 64-bit integers and you can use MSVC style or GCC style indicators (%I64d or %lld). It supports the C99 specifiers for size_t and ptr_diff_t (%jd %zd) as well. EXTRAS: -======= +/////// Like some GCCs, for integers and floats, you can use a ' (single quote) specifier and commas will be inserted on the thousands: "%'d" on 12345 would print 12,345. @@ -157,7 +194,7 @@ In addition to octal and hexadecimal conversions, you can print integers in binary: "%b" for 256 would print 100. PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== +/////////////////////////////////////////////////////////////////// "%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) "%24d" across all 32-bit ints (4.5x/4.2x faster) "%x" across all 32-bit ints (4.5x/3.8x faster) @@ -248,5 +285,4 @@ STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char c STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); #endif // STB_SPRINTF_H_INCLUDE - - +#endif // !defined(DQN_USE_STD_PRINTF) diff --git a/dqn_hash.cpp b/dqn_hash.cpp index 3eb7ae9..24023d7 100644 --- a/dqn_hash.cpp +++ b/dqn_hash.cpp @@ -1,4 +1,19 @@ -// NOTE: [$FNV1] Dqn_FNV1A ========================================================================= +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$\ $$$$$$\ $$\ $$\ +// $$ | $$ |$$ __$$\ $$ __$$\ $$ | $$ | +// $$ | $$ |$$ / $$ |$$ / \__|$$ | $$ | +// $$$$$$$$ |$$$$$$$$ |\$$$$$$\ $$$$$$$$ | +// $$ __$$ |$$ __$$ | \____$$\ $$ __$$ | +// $$ | $$ |$$ | $$ |$$\ $$ |$$ | $$ | +// $$ | $$ |$$ | $$ |\$$$$$$ |$$ | $$ | +// \__| \__|\__| \__| \______/ \__| \__| +// +// dqn_hash.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$FNV1] Dqn_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) { @@ -28,7 +43,7 @@ DQN_API uint64_t Dqn_FNV1A64_Hash(void const *bytes, Dqn_usize size) return result; } -// NOTE: [$MMUR] Dqn_MurmurHash3 =================================================================== +// NOTE: [$MMUR] Dqn_MurmurHash3 /////////////////////////////////////////////////////////////////// #if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) #define DQN_MMH3_ROTL32(x, y) _rotl(x, y) #define DQN_MMH3_ROTL64(x, y) _rotl64(x, y) @@ -244,4 +259,3 @@ DQN_API Dqn_MurmurHash3 Dqn_MurmurHash3_x64U128(void const *key, int len, uint32 result.e[1] = h2; return result; } - diff --git a/dqn_hash.h b/dqn_hash.h index 621bfa6..75ed240 100644 --- a/dqn_hash.h +++ b/dqn_hash.h @@ -1,12 +1,24 @@ -// NOTE: [$FNV1] Dqn_FNV1A ========================================================================= -// NOTE: API ======================================================================================= -#if 0 - 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 -#endif +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$\ $$$$$$\ $$\ $$\ +// $$ | $$ |$$ __$$\ $$ __$$\ $$ | $$ | +// $$ | $$ |$$ / $$ |$$ / \__|$$ | $$ | +// $$$$$$$$ |$$$$$$$$ |\$$$$$$\ $$$$$$$$ | +// $$ __$$ |$$ __$$ | \____$$\ $$ __$$ | +// $$ | $$ |$$ | $$ |$$\ $$ |$$ | $$ | +// $$ | $$ |$$ | $$ |\$$$$$$ |$$ | $$ | +// \__| \__|\__| \__| \______/ \__| \__| +// +// dqn_hash.h -- Hashing functions +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$FNV1] Dqn_FNV1A -- Hash(x) -> 32/64bit via FNV1a +// [$MMUR] Dqn_MurmurHash3 -- Hash(x) -> 32/128bit via MurmurHash3 +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// NOTE: [$FNV1] Dqn_FNV1A ///////////////////////////////////////////////////////////////////////// #if !defined(DQN_FNV1A32_SEED) #define DQN_FNV1A32_SEED 2166136261U #endif @@ -15,24 +27,18 @@ #define DQN_FNV1A64_SEED 14695981039346656037ULL #endif -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); - -// NOTE: [$MMUR] Dqn_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. +// NOTE: [$MMUR] Dqn_MurmurHash3 /////////////////////////////////////////////////////////////////// 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]) +// NOTE: [$FNV1] Dqn_FNV1A ///////////////////////////////////////////////////////////////////////// +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); + +// NOTE: [$MMUR] Dqn_MurmurHash3 /////////////////////////////////////////////////////////////////// +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]) diff --git a/dqn_helpers.cpp b/dqn_helpers.cpp index 7d032e3..5b95e43 100644 --- a/dqn_helpers.cpp +++ b/dqn_helpers.cpp @@ -1,7 +1,31 @@ -// NOTE: [$PCGX] Dqn_PCG32 ========================================================================= +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| +// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | +// \__| \__|\________|\________|\__| \________|\__| \__| \______/ +// +// dqn_helpers.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$PCGX] Dqn_PCG32 ///////////////////////////////////////////////////////////////////////// #define DQN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL #define DQN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL +DQN_API Dqn_PCG32 Dqn_PCG32_Init(uint64_t seed) +{ + Dqn_PCG32 result = {}; + Dqn_PCG32_Next(&result); + result.state += seed; + Dqn_PCG32_Next(&result); + return result; +} + DQN_API uint32_t Dqn_PCG32_Next(Dqn_PCG32 *rng) { uint64_t state = rng->state; @@ -45,14 +69,6 @@ DQN_API double Dqn_PCG32_NextF64(Dqn_PCG32 *rng) return (double)(int64_t)(x >> 11) * 0x1.0p-53; } -DQN_API void Dqn_PCG32_Seed(Dqn_PCG32 *rng, uint64_t seed) -{ - rng->state = 0ULL; - Dqn_PCG32_Next(rng); - rng->state += seed; - Dqn_PCG32_Next(rng); -} - DQN_API void Dqn_PCG32_Advance(Dqn_PCG32 *rng, uint64_t delta) { uint64_t cur_mult = DQN_PCG_DEFAULT_MULTIPLIER_64; @@ -75,18 +91,18 @@ DQN_API void Dqn_PCG32_Advance(Dqn_PCG32 *rng, uint64_t delta) } #if !defined(DQN_NO_JSON_BUILDER) -// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== -DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init(Dqn_Allocator allocator, int spaces_per_indent) +// NOTE: [$JSON] Dqn_JSONBuilder /////////////////////////////////////////////////////////////////// +DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init(Dqn_Arena *arena, int spaces_per_indent) { - Dqn_JSONBuilder result = {}; - result.spaces_per_indent = spaces_per_indent; - result.string_builder.allocator = allocator; + Dqn_JSONBuilder result = {}; + result.spaces_per_indent = spaces_per_indent; + result.string_builder.arena = arena; return result; } -DQN_API Dqn_Str8 Dqn_JSONBuilder_Build(Dqn_JSONBuilder const *builder, Dqn_Allocator allocator) +DQN_API Dqn_Str8 Dqn_JSONBuilder_Build(Dqn_JSONBuilder const *builder, Dqn_Arena *arena) { - Dqn_Str8 result = Dqn_Str8Builder_Build(&builder->string_builder, allocator); + Dqn_Str8 result = Dqn_Str8Builder_Build(&builder->string_builder, arena); return result; } @@ -97,19 +113,18 @@ DQN_API void Dqn_JSONBuilder_KeyValue(Dqn_JSONBuilder *builder, Dqn_Str8 key, Dq Dqn_JSONBuilderItem item = Dqn_JSONBuilderItem_KeyValue; if (value.size == 1) { - if (value.data[0] == '{' || value.data[0] == '[') { + if (value.data[0] == '{' || value.data[0] == '[') item = Dqn_JSONBuilderItem_OpenContainer; - } else if (value.data[0] == '}' || value.data[0] == ']') { + else if (value.data[0] == '}' || value.data[0] == ']') item = Dqn_JSONBuilderItem_CloseContainer; - } } - bool adding_to_container_with_items = item != Dqn_JSONBuilderItem_CloseContainer && - (builder->last_item == Dqn_JSONBuilderItem_KeyValue || - builder->last_item == Dqn_JSONBuilderItem_CloseContainer); + bool adding_to_container_with_items = + item != Dqn_JSONBuilderItem_CloseContainer && (builder->last_item == Dqn_JSONBuilderItem_KeyValue || + builder->last_item == Dqn_JSONBuilderItem_CloseContainer); uint8_t prefix_size = 0; - char prefix[2] = {0}; + char prefix[2] = {0}; if (adding_to_container_with_items) prefix[prefix_size++] = ','; @@ -124,17 +139,16 @@ DQN_API void Dqn_JSONBuilder_KeyValue(Dqn_JSONBuilder *builder, Dqn_Str8 key, Dq if (key.size) { Dqn_Str8Builder_AppendF(&builder->string_builder, - "%.*s%*c\"%.*s\": %.*s", - prefix_size, prefix, - spaces, ' ', - DQN_STR_FMT(key), - DQN_STR_FMT(value)); + "%.*s%*c\"%.*s\": %.*s", + prefix_size, + prefix, + spaces, + ' ', + DQN_STR_FMT(key), + DQN_STR_FMT(value)); } else { - Dqn_Str8Builder_AppendF(&builder->string_builder, - "%.*s%*c%.*s", - prefix_size, prefix, - spaces, ' ', - DQN_STR_FMT(value)); + Dqn_Str8Builder_AppendF( + &builder->string_builder, "%.*s%*c%.*s", prefix_size, prefix, spaces, ' ', DQN_STR_FMT(value)); } if (item == Dqn_JSONBuilderItem_OpenContainer) @@ -145,8 +159,8 @@ DQN_API void Dqn_JSONBuilder_KeyValue(Dqn_JSONBuilder *builder, Dqn_Str8 key, Dq DQN_API void Dqn_JSONBuilder_KeyValueFV(Dqn_JSONBuilder *builder, Dqn_Str8 key, char const *value_fmt, va_list args) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(builder->string_builder.allocator.user_context); - Dqn_Str8 value = Dqn_Str8_InitFV(scratch.allocator, value_fmt, args); + Dqn_Scratch scratch = Dqn_Scratch_Get(builder->string_builder.arena); + Dqn_Str8 value = Dqn_Str8_InitFV(scratch.arena, value_fmt, args); Dqn_JSONBuilder_KeyValue(builder, key, value); } @@ -211,17 +225,17 @@ DQN_API void Dqn_JSONBuilder_F64Named(Dqn_JSONBuilder *builder, Dqn_Str8 key, do char float_fmt[16]; if (decimal_places > 0) { // NOTE: Emit the format string "%.f" i.e. %.1f - STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); + DQN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); } else { // NOTE: Emit the format string "%f" - STB_SPRINTF_DECORATE(snprintf)(float_fmt, sizeof(float_fmt), "%%f"); + DQN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%f"); } char fmt[32]; if (key.size) - STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "\"%%.*s\": %s", float_fmt); + DQN_SNPRINTF(fmt, sizeof(fmt), "\"%%.*s\": %s", float_fmt); else - STB_SPRINTF_DECORATE(snprintf)(fmt, sizeof(fmt), "%s", float_fmt); + DQN_SNPRINTF(fmt, sizeof(fmt), "%s", float_fmt); Dqn_JSONBuilder_KeyValueF(builder, key, fmt, value); } @@ -234,7 +248,7 @@ DQN_API void Dqn_JSONBuilder_BoolNamed(Dqn_JSONBuilder *builder, Dqn_Str8 key, b #endif // !defined(DQN_NO_JSON_BUILDER) #if !defined(DQN_NO_BIN) -// NOTE: [$BHEX] Dqn_Bin =========================================================================== +// NOTE: [$BHEX] Dqn_Bin /////////////////////////////////////////////////////////////////////////// DQN_API char const *Dqn_Bin_HexBufferTrim0x(char const *hex, Dqn_usize size, Dqn_usize *real_size) { Dqn_Str8 result = Dqn_Str8_TrimWhitespaceAround(Dqn_Str8_Init(hex, size)); @@ -260,11 +274,11 @@ DQN_API Dqn_BinHexU64Str8 Dqn_Bin_U64ToHexU64Str8(uint64_t number, uint32_t flag Dqn_BinHexU64Str8 result = {}; DQN_MEMCPY(result.data, prefix.data, prefix.size); - result.size += DQN_CAST(int8_t)prefix.size; + result.size += DQN_CAST(int8_t) prefix.size; char const *fmt = (flags & Dqn_BinHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; - int size = STB_SPRINTF_DECORATE(snprintf)(result.data + result.size, DQN_ARRAY_UCOUNT(result.data) - result.size, fmt, number); - result.size += DQN_CAST(uint8_t)size; + int size = DQN_SNPRINTF(result.data + result.size, DQN_ARRAY_UCOUNT(result.data) - result.size, fmt, number); + result.size += DQN_CAST(uint8_t) size; DQN_ASSERT(result.size < DQN_ARRAY_UCOUNT(result.data)); // NOTE: snprintf returns the required size of the format string @@ -274,41 +288,34 @@ DQN_API Dqn_BinHexU64Str8 Dqn_Bin_U64ToHexU64Str8(uint64_t number, uint32_t flag return result; } -DQN_API Dqn_Str8 Dqn_Bin_U64ToHex(Dqn_Allocator allocator, uint64_t number, uint32_t flags) +DQN_API Dqn_Str8 Dqn_Bin_U64ToHex(Dqn_Arena *arena, uint64_t number, uint32_t flags) { Dqn_Str8 prefix = {}; if (!(flags & Dqn_BinHexU64Str8Flags_No0xPrefix)) prefix = DQN_STR8("0x"); - char const *fmt = (flags & Dqn_BinHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; - Dqn_usize required_size = Dqn_CStr8_FSize(fmt, number) + prefix.size; - Dqn_Str8 result = Dqn_Str8_Allocate(allocator, required_size, Dqn_ZeroMem_No); + char const *fmt = (flags & Dqn_BinHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; + Dqn_usize required_size = Dqn_CStr8_FSize(fmt, number) + prefix.size; + Dqn_Str8 result = Dqn_Str8_Alloc(arena, required_size, Dqn_ZeroMem_No); - if (Dqn_Str8_IsValid(result)) { + if (Dqn_Str8_HasData(result)) { DQN_MEMCPY(result.data, prefix.data, prefix.size); - int space = DQN_CAST(int)DQN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/ - STB_SPRINTF_DECORATE(snprintf)(result.data + prefix.size, space, fmt, number); + int space = DQN_CAST(int) DQN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/ + DQN_SNPRINTF(result.data + prefix.size, space, fmt, number); } return result; } -DQN_API uint64_t Dqn_Bin_HexBufferToU64(char const *hex, Dqn_usize size) +DQN_API uint64_t Dqn_Bin_HexToU64(Dqn_Str8 hex) { - Dqn_usize trim_size = size; - char const *trim_hex = hex; - if (trim_size >= 2) { - if (trim_hex[0] == '0' && (trim_hex[1] == 'x' || trim_hex[1] == 'X')) { - trim_size -= 2; - trim_hex += 2; - } - } + Dqn_Str8 real_hex = Dqn_Str8_TrimPrefix(Dqn_Str8_TrimPrefix(hex, DQN_STR8("0x")), DQN_STR8("0X")); + Dqn_usize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/; + DQN_ASSERT(real_hex.size <= max_hex_size); - DQN_ASSERT(DQN_CAST(Dqn_usize)(trim_size * 4 / 8) /*maximum amount of bytes represented in the hex string*/ <= sizeof(uint64_t)); - - uint64_t result = 0; - Dqn_usize max_size = DQN_MIN(size, 8 /*bytes*/ * 2 /*hex chars per byte*/); - for (Dqn_usize hex_index = 0; hex_index < max_size; hex_index++) { - char ch = trim_hex[hex_index]; + Dqn_usize size = DQN_MIN(max_hex_size, real_hex.size); + uint64_t result = 0; + for (Dqn_usize index = 0; index < size; index++) { + char ch = real_hex.data[index]; if (!Dqn_Char_IsHex(ch)) break; uint8_t val = Dqn_Char_HexToU8(ch); @@ -317,9 +324,9 @@ DQN_API uint64_t Dqn_Bin_HexBufferToU64(char const *hex, Dqn_usize size) return result; } -DQN_API uint64_t Dqn_Bin_HexToU64(Dqn_Str8 hex) +DQN_API uint64_t Dqn_Bin_HexPtrToU64(char const *hex, Dqn_usize size) { - uint64_t result = Dqn_Bin_HexBufferToU64(hex.data, hex.size); + uint64_t result = Dqn_Bin_HexToU64(Dqn_Str8_Init(hex, size)); return result; } @@ -331,8 +338,8 @@ DQN_API bool Dqn_Bin_BytesToHexBuffer(void const *src, Dqn_usize src_size, char if (!DQN_CHECK(dest_size >= src_size * 2)) return false; - char const *HEX = "0123456789abcdef"; - unsigned char const *src_u8 = DQN_CAST(unsigned char const *)src; + char const *HEX = "0123456789abcdef"; + unsigned char const *src_u8 = DQN_CAST(unsigned char const *) src; for (Dqn_usize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { char byte = src_u8[src_index]; char hex01 = (byte >> 4) & 0b1111; @@ -346,7 +353,8 @@ DQN_API bool Dqn_Bin_BytesToHexBuffer(void const *src, Dqn_usize src_size, char DQN_API char *Dqn_Bin_BytesToHexBufferArena(Dqn_Arena *arena, void const *src, Dqn_usize size) { - char *result = size > 0 ? Dqn_Arena_NewArray(arena, char, (size * 2) + 1 /*null terminate*/, Dqn_ZeroMem_No) : nullptr; + char *result = + size > 0 ? Dqn_Arena_NewArray(arena, char, (size * 2) + 1 /*null terminate*/, Dqn_ZeroMem_No) : nullptr; if (result) { bool converted = Dqn_Bin_BytesToHexBuffer(src, size, result, size * 2); DQN_ASSERT(converted); @@ -358,7 +366,7 @@ DQN_API char *Dqn_Bin_BytesToHexBufferArena(Dqn_Arena *arena, void const *src, D DQN_API Dqn_Str8 Dqn_Bin_BytesToHexArena(Dqn_Arena *arena, void const *src, Dqn_usize size) { Dqn_Str8 result = {}; - result.data = Dqn_Bin_BytesToHexBufferArena(arena, src, size); + result.data = Dqn_Bin_BytesToHexBufferArena(arena, src, size); if (result.data) result.size = size * 2; return result; @@ -370,10 +378,8 @@ DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes(char const *hex, Dqn_usize hex_size, if (!hex || hex_size <= 0) return result; - Dqn_usize trim_size = 0; - char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, - hex_size, - &trim_size); + Dqn_usize trim_size = 0; + char const *trim_hex = Dqn_Bin_HexBufferTrim0x(hex, hex_size, &trim_size); // 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 @@ -385,34 +391,31 @@ DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes(char const *hex, Dqn_usize hex_size, return result; } - result = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, - trim_size, - dest, - dest_size); + result = Dqn_Bin_HexBufferToBytesUnchecked(trim_hex, trim_size, dest, dest_size); return result; } -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size) +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, + Dqn_usize hex_size, + void *dest, + Dqn_usize dest_size) { - Dqn_usize result = 0; - unsigned char *dest_u8 = DQN_CAST(unsigned char *)dest; + Dqn_usize result = 0; + unsigned char *dest_u8 = DQN_CAST(unsigned char *) dest; - for (Dqn_usize hex_index = 0; - hex_index < hex_size; - hex_index += 2, result += 1) - { - char hex01 = hex[hex_index]; - char hex02 = (hex_index + 1 < hex_size) ? hex[hex_index + 1] : 0; + for (Dqn_usize hex_index = 0; hex_index < hex_size; hex_index += 2, result += 1) { + char hex01 = hex[hex_index]; + char hex02 = (hex_index + 1 < hex_size) ? hex[hex_index + 1] : 0; - char bit4_01 = (hex01 >= '0' && hex01 <= '9') ? 0 + (hex01 - '0') + char bit4_01 = (hex01 >= '0' && hex01 <= '9') ? 0 + (hex01 - '0') : (hex01 >= 'a' && hex01 <= 'f') ? 10 + (hex01 - 'a') : (hex01 >= 'A' && hex01 <= 'F') ? 10 + (hex01 - 'A') - : 0; + : 0; - char bit4_02 = (hex02 >= '0' && hex02 <= '9') ? 0 + (hex02 - '0') + char bit4_02 = (hex02 >= '0' && hex02 <= '9') ? 0 + (hex02 - '0') : (hex02 >= 'a' && hex02 <= 'f') ? 10 + (hex02 - 'a') : (hex02 >= 'A' && hex02 <= 'F') ? 10 + (hex02 - 'A') - : 0; + : 0; char byte = (bit4_01 << 4) | (bit4_02 << 0); dest_u8[result] = byte; @@ -460,30 +463,30 @@ DQN_API Dqn_Str8 Dqn_Bin_HexToBytesArena(Dqn_Arena *arena, Dqn_Str8 hex) } #endif // !defined(DQN_NO_BIN) -// NOTE: [$BITS] Dqn_Bit =========================================================================== -DQN_API void Dqn_Bit_UnsetInplace(uint64_t *flags, uint64_t bitfield) +// NOTE: [$BITS] Dqn_Bit /////////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_Bit_UnsetInplace(Dqn_usize *flags, Dqn_usize bitfield) { *flags = (*flags & ~bitfield); } -DQN_API void Dqn_Bit_SetInplace(uint64_t *flags, uint64_t bitfield) +DQN_API void Dqn_Bit_SetInplace(Dqn_usize *flags, Dqn_usize bitfield) { *flags = (*flags | bitfield); } -DQN_API bool Dqn_Bit_IsSet(uint64_t bits, uint64_t bits_to_set) +DQN_API bool Dqn_Bit_IsSet(Dqn_usize bits, Dqn_usize 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) +DQN_API bool Dqn_Bit_IsNotSet(Dqn_usize bits, Dqn_usize bits_to_check) { auto result = !Dqn_Bit_IsSet(bits, bits_to_check); return result; } -// NOTE: [$SAFE] Dqn_Safe ========================================================================== +// NOTE: [$SAFE] Dqn_Safe ////////////////////////////////////////////////////////////////////////// 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; @@ -526,31 +529,31 @@ DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b) // 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; + 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; + 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; + 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; + 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; + int64_t result = DQN_CHECK(DQN_CAST(uintmax_t) val <= INT64_MAX) ? DQN_CAST(int64_t) val : INT64_MAX; return result; } @@ -560,57 +563,64 @@ DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val) // 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; + 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; + 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; + 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(DQN_CAST(uint64_t)val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX; + uint64_t result = DQN_CHECK(DQN_CAST(uint64_t) val <= UINT64_MAX) ? DQN_CAST(uint64_t) val : UINT64_MAX; return result; } -// NOTE: Dqn_Safe_SaturateCastU64ToU* +// NOTE: Dqn_Safe_SaturateCastU64To* // ----------------------------------------------------------------------------- +// INT*_MAX literals will be promoted to the type of val as val is +// the highest possible rank (unsigned > signed). +DQN_API int Dqn_Safe_SaturateCastU64ToInt(uint64_t val) +{ + int result = DQN_CHECK(val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX; + return result; +} + // 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; + 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; + 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; + 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; + 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 @@ -618,35 +628,35 @@ DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val) 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); + 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); + 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); + 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); + 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(DQN_CAST(int64_t)val >= INT64_MIN && DQN_CAST(int64_t)val <= INT64_MAX); - int64_t result = DQN_CAST(int64_t)DQN_CLAMP(DQN_CAST(int64_t)val, INT64_MIN, INT64_MAX); + DQN_ASSERT(DQN_CAST(int64_t) val >= INT64_MIN && DQN_CAST(int64_t) val <= INT64_MAX); + int64_t result = DQN_CAST(int64_t) DQN_CLAMP(DQN_CAST(int64_t) val, INT64_MIN, INT64_MAX); return result; } @@ -658,9 +668,9 @@ DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val) 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; + 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; } @@ -670,9 +680,9 @@ DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val) 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; + 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; } @@ -682,9 +692,9 @@ DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val) 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; + 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; } @@ -694,9 +704,9 @@ DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val) 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; + 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; } @@ -706,9 +716,9 @@ DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val) 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; + 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; } @@ -722,28 +732,28 @@ DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val) 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); + 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); + 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); + 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); + int32_t result = DQN_CAST(int32_t) DQN_CLAMP(val, INT32_MIN, INT32_MAX); return result; } @@ -752,23 +762,23 @@ DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val) 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); + 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); + 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; + 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; } @@ -778,9 +788,9 @@ DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val) 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; + 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; } @@ -792,7 +802,7 @@ 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; + result = DQN_CAST(uint32_t) val; return result; } @@ -801,16 +811,16 @@ 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; + result = DQN_CAST(uint64_t) val; return result; } -// NOTE: [$MISC] Misc ============================================================================== -DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, DQN_FMT_ATTRIB char const *fmt, ...) +// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// +DQN_API int Dqn_FmtBuffer3DotTruncate(char *buffer, int size, DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); - int size_required = STB_SPRINTF_DECORATE(vsnprintf)(buffer, size, fmt, args); + int size_required = DQN_VSNPRINTF(buffer, size, fmt, args); int result = DQN_MAX(DQN_MIN(size_required, size - 1), 0); if (result == size - 1) { buffer[size - 2] = '.'; @@ -842,9 +852,10 @@ DQN_API Dqn_U64Str8 Dqn_U64ToStr8(uint64_t val, char separator) // NOTE: Reverse the string DQN_MSVC_WARNING_PUSH DQN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop - DQN_MSVC_WARNING_DISABLE(6385) // Reading invalid data from 'temp.data' NOTE(doyle): Unsigned overflow is valid for loop termination + DQN_MSVC_WARNING_DISABLE( + 6385) // Reading invalid data from 'temp.data' NOTE(doyle): Unsigned overflow is valid for loop termination for (Dqn_usize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { - char ch = temp.data[temp_index]; + char ch = temp.data[temp_index]; result.data[result.size++] = ch; } DQN_MSVC_WARNING_POP @@ -853,7 +864,105 @@ DQN_API Dqn_U64Str8 Dqn_U64ToStr8(uint64_t val, char separator) return result; } -// NOTE: [$DLIB] Dqn_Library ======================================================================= +DQN_API Dqn_U64ByteSize Dqn_U64ToByteSize(uint64_t bytes, Dqn_U64ByteSizeType desired_type) +{ + Dqn_U64ByteSize result = {}; + result.bytes = DQN_CAST(Dqn_f64)bytes; + if (!DQN_CHECK(desired_type != Dqn_U64ByteSizeType_Count)) { + result.suffix = Dqn_U64ByteSizeTypeString(result.type); + return result; + } + + if (desired_type == Dqn_U64ByteSizeType_Auto) { + for (; result.type < Dqn_U64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DQN_CAST(Dqn_U64ByteSizeType)(DQN_CAST(Dqn_usize)result.type + 1)) + result.bytes /= 1024.0; + } else { + for (; result.type < desired_type; result.type = DQN_CAST(Dqn_U64ByteSizeType)(DQN_CAST(Dqn_usize)result.type + 1)) + result.bytes /= 1024.0; + } + + result.suffix = Dqn_U64ByteSizeTypeString(result.type); + return result; +} + +DQN_API Dqn_Str8 Dqn_U64ToByteSizeStr8(Dqn_Arena *arena, uint64_t bytes, Dqn_U64ByteSizeType desired_type) +{ + Dqn_U64ByteSize byte_size = Dqn_U64ToByteSize(bytes, desired_type); + Dqn_Str8 result = Dqn_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DQN_STR_FMT(byte_size.suffix)); + return result; +} + +DQN_API Dqn_Str8 Dqn_U64ByteSizeTypeString(Dqn_U64ByteSizeType type) +{ + Dqn_Str8 result = DQN_STR8(""); + switch (type) { + case Dqn_U64ByteSizeType_B: result = DQN_STR8("B"); break; + case Dqn_U64ByteSizeType_KiB: result = DQN_STR8("KiB"); break; + case Dqn_U64ByteSizeType_MiB: result = DQN_STR8("MiB"); break; + case Dqn_U64ByteSizeType_GiB: result = DQN_STR8("GiB"); break; + case Dqn_U64ByteSizeType_TiB: result = DQN_STR8("TiB"); break; + case Dqn_U64ByteSizeType_Count: result = DQN_STR8(""); break; + case Dqn_U64ByteSizeType_Auto: result = DQN_STR8(""); break; + } + return result; +} + +DQN_API Dqn_Str8 Dqn_U64ToAge(Dqn_Arena *arena, uint64_t age_s, Dqn_usize type) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str8Builder builder = {}; + builder.arena = arena; + uint64_t remainder = age_s; + + if (type & Dqn_U64AgeUnit_Year) { + Dqn_usize unit = remainder / DQN_YEARS_TO_S(1); + remainder -= DQN_YEARS_TO_S(unit); + if (unit) + Dqn_Str8Builder_AppendF(&builder, "%s%I64uyr", builder.string_size ? " " : "", unit); + } + + if (type & Dqn_U64AgeUnit_Week) { + Dqn_usize unit = remainder / DQN_WEEKS_TO_S(1); + remainder -= DQN_WEEKS_TO_S(unit); + if (unit) + Dqn_Str8Builder_AppendF(&builder, "%s%I64uw", builder.string_size ? " " : "", unit); + } + + if (type & Dqn_U64AgeUnit_Day) { + Dqn_usize unit = remainder / DQN_DAYS_TO_S(1); + remainder -= DQN_DAYS_TO_S(unit); + if (unit) + Dqn_Str8Builder_AppendF(&builder, "%s%I64ud", builder.string_size ? " " : "", unit); + } + + if (type & Dqn_U64AgeUnit_Hr) { + Dqn_usize unit = remainder / DQN_HOURS_TO_S(1); + remainder -= DQN_HOURS_TO_S(unit); + if (unit) + Dqn_Str8Builder_AppendF(&builder, "%s%I64uh", builder.string_size ? " " : "", unit); + } + + if (type & Dqn_U64AgeUnit_Min) { + Dqn_usize unit = remainder / DQN_MINS_TO_S(1); + remainder -= DQN_MINS_TO_S(unit); + if (unit) + Dqn_Str8Builder_AppendF(&builder, "%s%I64um", builder.string_size ? " " : "", unit); + } + + if (type & Dqn_U64AgeUnit_Sec) { + Dqn_usize unit = remainder; + Dqn_Str8Builder_AppendF(&builder, "%s%I64us", builder.string_size ? " " : "", unit); + } + + result = Dqn_Str8Builder_Build(&builder, arena); + return result; +} + +// NOTE: [$DLIB] Dqn_Library /////////////////////////////////////////////////////////////////////// Dqn_Library *g_dqn_library; DQN_API Dqn_Library *Dqn_Library_Init(Dqn_LibraryOnInit on_init) @@ -863,83 +972,104 @@ DQN_API Dqn_Library *Dqn_Library_Init(Dqn_LibraryOnInit on_init) g_dqn_library = &default_instance; } - // NOTE: Init check =========================================================================== - + // NOTE: Init check /////////////////////////////////////////////////////////////////////////// Dqn_Library *result = g_dqn_library; Dqn_TicketMutex_Begin(&result->lib_mutex); - DQN_DEFER { Dqn_TicketMutex_End(&result->lib_mutex); }; + DQN_DEFER { + Dqn_TicketMutex_End(&result->lib_mutex); + }; + if (result->lib_init) return result; + result->lib_init = true; - // NOTE: Query OS page size ==================================================================== - + // NOTE: Query OS info ///////////////////////////////////////////////////////////////////////// { - #if defined(DQN_OS_WIN32) + #if defined(DQN_OS_WIN32) SYSTEM_INFO system_info = {}; GetSystemInfo(&system_info); result->os_page_size = system_info.dwPageSize; result->os_alloc_granularity = system_info.dwAllocationGranularity; - #else + + QueryPerformanceFrequency(&result->win32_qpc_frequency); + #else // TODO(doyle): Get the proper page size from the OS. result->os_page_size = DQN_KILOBYTES(4); result->os_alloc_granularity = DQN_KILOBYTES(64); - #endif + #endif } - // NOTE Initialise fields ====================================================================== + // NOTE Initialise fields ////////////////////////////////////////////////////////////////////// #if !defined(DQN_NO_PROFILER) result->profiler = &result->profiler_default_instance; #endif - result->lib_init = true; - Dqn_ArenaCatalog_Init(&result->arena_catalog, &result->arena); - result->exe_dir = Dqn_OS_EXEDir(&result->arena); - - // NOTE: Leak tracing ========================================================================== - - #if defined(DQN_LEAK_TRACING) // NOTE: Initialise the allocation leak tracker - { - result->alloc_tracking_disabled = true; // TODO(doyle): @robust Does this need to be atomic? - - Dqn_Str8 sample_backtrace = Dqn_Str8_InitCStr8(b_stacktrace_get_string()); - Dqn_Str8 clean_backtrace = Dqn_Debug_CleanStackTrace(sample_backtrace); - result->stack_trace_offset_to_our_call_stack = DQN_CAST(uint16_t)(sample_backtrace.size - clean_backtrace.size); - free(sample_backtrace.data); - - result->alloc_table = Dqn_DSMap_Init(4096); - result->alloc_tracking_disabled = false; - } + // NOTE: BEGIN IMPORTANT ORDER OF STATEMENTS /////////////////////////////////////////////////// + #if defined(DQN_LEAK_TRACKING) + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + result->alloc_table_arena.flags |= Dqn_ArenaFlag_NoAllocTrack; + result->alloc_table = Dqn_DSMap_Init(&result->alloc_table_arena, 4096); #endif - // NOTE: Print out init features =============================================================== + result->arena = Dqn_Arena_InitSize(0, 0, Dqn_ArenaFlag_AllocCanLeak); + result->pool = Dqn_ChunkPool_Init(&result->arena, /*align*/ 0); + Dqn_ArenaCatalog_Init(&result->arena_catalog, &result->pool); + Dqn_ArenaCatalog_AddF(&result->arena_catalog, &result->arena, "Dqn Library"); + + #if defined(DQN_LEAK_TRACING) + Dqn_ArenaCatalog_AddF(&result->arena_catalog, &result->alloc_table_arena, "Dqn Allocation Table"); + #endif + + // NOTE: Initialise scratch arenas which allocate memory and will be + // recorded to the now initialised allocation table. The initialisation + // of scratch memory may request scratch memory itself in leak tracing mode. + // This is supported as the scratch arenas defer allocation tracking until + // initialisation is done. + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + // NOTE: END IMPORTANT ORDER OF STATEMENTS ///////////////////////////////////////////////////// + + result->exe_dir = Dqn_OS_EXEDir(&result->arena); + + // NOTE: Print out init features /////////////////////////////////////////////////////////////// if (on_init == Dqn_LibraryOnInit_LogFeatures) { - Dqn_Log_DebugF("Dqn Library initialised:\n"); + Dqn_Str8Builder builder = {}; + builder.arena = scratch.arena; - // NOTE: %$$_I32u is a stb_sprintf format specifier, non-standard - DQN_MSVC_WARNING_PUSH - DQN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'Dqn_Print_StdLnF'. - Dqn_Print_StdLnF(Dqn_PrintStd_Err, " OS Page Size/Alloc Granularity: %$$_I32u/%$$_I32u", result->os_page_size, result->os_alloc_granularity); - DQN_MSVC_WARNING_POP + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8("Dqn Library initialised:\n")); - #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + Dqn_f64 page_size_kib = result->os_page_size / 1024.0; + Dqn_f64 alloc_granularity_kib = result->os_alloc_granularity / 1024.0; + Dqn_Str8Builder_AppendF( + &builder, " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n", page_size_kib, alloc_granularity_kib); + + #if DQN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) if (DQN_ASAN_POISON) { - Dqn_Print_StdLnF(Dqn_PrintStd_Err, " ASAN manual poisoning%s", DQN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); - Dqn_Print_StdLnF(Dqn_PrintStd_Err, " ASAN poison guard size: %$$_I32u", DQN_ASAN_POISON_GUARD_SIZE); + Dqn_Str8Builder_AppendF( + &builder, " ASAN manual poisoning%s\n", DQN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + Dqn_Str8Builder_AppendF(&builder, " ASAN poison guard size: %u\n", DQN_ASAN_POISON_GUARD_SIZE); } #endif - #if defined(DQN_LEAK_TRACING) - Dqn_Print_StdLnF(Dqn_PrintStd_Err, " Allocation leak tracing"); + #if defined(DQN_LEAK_TRACKING) + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8(" Allocation leak tracing\n")); #endif #if !defined(DQN_NO_PROFILER) - Dqn_Print_StdLnF(Dqn_PrintStd_Err, " TSC profiler available"); + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8(" TSC profiler available\n")); + #endif + + #if defined(DQN_USE_STD_PRINTF) + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8(" Using stdio's printf functions\n")); + #else + Dqn_Str8Builder_AppendRef(&builder, DQN_STR8(" Using stb_sprintf functions\n")); #endif // TODO(doyle): Add stacktrace feature log - Dqn_Print_StdLnF(Dqn_PrintStd_Err, ""); + Dqn_Str8 info_log = Dqn_Str8Builder_Build(&builder, scratch.arena); + Dqn_Log_DebugF("%.*s", DQN_STR_FMT(info_log)); } return result; } @@ -966,7 +1096,7 @@ DQN_API void Dqn_Library_SetLogCallback(Dqn_LogProc *proc, void *user_data) DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path) { - #if defined(DQN_DEBUG_THREAD_CONTEXT) +#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"); @@ -979,7 +1109,7 @@ DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path) // NOTE: Extremely short critical section, copy the stats then do our // work on it. Dqn_ArenaStat stats[Dqn_CArray_CountI(g_dqn_library->thread_context_arena_stats)]; - int stats_size = 0; + int stats_size = 0; Dqn_TicketMutex_Begin(&g_dqn_library->thread_context_mutex); stats_size = g_dqn_library->thread_context_arena_stats_count; @@ -990,8 +1120,10 @@ DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path) Dqn_DateHMSTimeStr now = Dqn_Date_HMSLocalTimeStrNow(); fprintf(file, "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", - now.date_size, now.date, - now.hms_size, now.hms, + now.date_size, + now.date, + now.hms_size, + now.hms, g_dqn_library->thread_context_arena_stats_count); // NOTE: Write the cumulative thread arena data @@ -1000,9 +1132,9 @@ DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path) 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.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); @@ -1016,21 +1148,31 @@ DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path) // NOTE: Print individual thread arena data for (Dqn_usize index = 0; index < stats_size; index++) { - Dqn_ArenaStat const *current = stats + index; - Dqn_ArenaStatStr current_string = Dqn_Arena_StatStr(current); - fprintf(file, " [%03d] CURR %.*s\n", DQN_CAST(int)index, current_string.size, current_string.data); + Dqn_ArenaStat const *current = stats + index; + Dqn_ArenaStatStr current_string = Dqn_Arena_StatStr(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_STR_FMT(file_path)); - #else +#else (void)file_path; - #endif // #if defined(DQN_DEBUG_THREAD_CONTEXT) +#endif // #if defined(DQN_DEBUG_THREAD_CONTEXT) } +DQN_API Dqn_Arena *Dqn_Library_AllocArenaF(Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, char const *fmt, ...) +{ + DQN_ASSERT(g_dqn_library->lib_init); + va_list args; + va_start(args, fmt); + Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog; + Dqn_Arena *result = Dqn_ArenaCatalog_AllocFV(catalog, reserve, commit, arena_flags, fmt, args); + va_end(args); + return result; +} #if !defined(DQN_NO_PROFILER) -// NOTE: [$PROF] Dqn_Profiler ====================================================================== +// NOTE: [$PROF] Dqn_Profiler ////////////////////////////////////////////////////////////////////// Dqn_ProfilerZoneScope::Dqn_ProfilerZoneScope(Dqn_Str8 name, uint16_t anchor_index) { zone = Dqn_Profiler_BeginZoneWithIndex(name, anchor_index); @@ -1056,23 +1198,24 @@ Dqn_ProfilerZone Dqn_Profiler_BeginZoneWithIndex(Dqn_Str8 name, uint16_t anchor_ void Dqn_Profiler_EndZone(Dqn_ProfilerZone zone) { - uint64_t elapsed_tsc = Dqn_CPU_TSC() - zone.begin_tsc; + uint64_t elapsed_tsc = Dqn_CPU_TSC() - zone.begin_tsc; Dqn_ProfilerAnchor *anchor_buffer = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); Dqn_ProfilerAnchor *anchor = anchor_buffer + zone.anchor_index; anchor->hit_count++; - anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; anchor->tsc_exclusive += elapsed_tsc; - Dqn_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; - parent_anchor->tsc_exclusive -= elapsed_tsc; - g_dqn_library->profiler->parent_zone = zone.parent_zone; + Dqn_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + g_dqn_library->profiler->parent_zone = zone.parent_zone; } Dqn_ProfilerAnchor *Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer buffer) { - uint8_t offset = buffer == Dqn_ProfilerAnchorBuffer_Back ? 0 : 1; - uint8_t anchor_buffer = (g_dqn_library->profiler->active_anchor_buffer + offset) % DQN_ARRAY_UCOUNT(g_dqn_library->profiler->anchors); + uint8_t offset = buffer == Dqn_ProfilerAnchorBuffer_Back ? 0 : 1; + uint8_t anchor_buffer = + (g_dqn_library->profiler->active_anchor_buffer + offset) % DQN_ARRAY_UCOUNT(g_dqn_library->profiler->anchors); Dqn_ProfilerAnchor *result = g_dqn_library->profiler->anchors[anchor_buffer]; return result; } @@ -1081,27 +1224,26 @@ void Dqn_Profiler_SwapAnchorBuffer() { g_dqn_library->profiler->active_anchor_buffer++; Dqn_ProfilerAnchor *anchors = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); - DQN_MEMSET(anchors, 0, DQN_ARRAY_UCOUNT(g_dqn_library->profiler->anchors[0]) * sizeof(g_dqn_library->profiler->anchors[0][0])); + DQN_MEMSET(anchors, + 0, + DQN_ARRAY_UCOUNT(g_dqn_library->profiler->anchors[0]) * sizeof(g_dqn_library->profiler->anchors[0][0])); } void Dqn_Profiler_Dump(uint64_t tsc_per_second) { Dqn_ProfilerAnchor *anchors = Dqn_Profiler_AnchorBuffer(Dqn_ProfilerAnchorBuffer_Back); for (size_t anchor_index = 1; anchor_index < DQN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) { - Dqn_ProfilerAnchor const *anchor = anchors + anchor_index; + Dqn_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; - Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DQN_CAST(Dqn_f64)tsc_per_second; + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + Dqn_f64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DQN_CAST(Dqn_f64) tsc_per_second; if (tsc_exclusive == tsc_inclusive) { - Dqn_Print_LnF("%.*s[%u]: %.1fms", - DQN_STR_FMT(anchor->name), - anchor->hit_count, - tsc_exclusive_milliseconds); + Dqn_Print_LnF("%.*s[%u]: %.1fms", DQN_STR_FMT(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); } else { - Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DQN_CAST(Dqn_f64)tsc_per_second; + Dqn_f64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DQN_CAST(Dqn_f64) tsc_per_second; Dqn_Print_LnF("%.*s[%u]: %.1f/%.1fms", DQN_STR_FMT(anchor->name), anchor->hit_count, @@ -1111,3 +1253,91 @@ void Dqn_Profiler_Dump(uint64_t tsc_per_second) } } #endif // !defined(DQN_NO_PROFILER) + +// NOTE: [$JOBQ] Dqn_JobQueue /////////////////////////////////////////////////////////////////////// +DQN_API Dqn_JobQueueSPMC Dqn_OS_JobQueueSPMCInit() +{ + Dqn_JobQueueSPMC result = {}; + result.thread_wait_for_job_semaphore = Dqn_OS_SemaphoreInit(0 /*initial_count*/); + result.wait_for_completion_semaphore = Dqn_OS_SemaphoreInit(0 /*initial_count*/); + result.mutex = Dqn_OS_MutexInit(); + return result; +} + +DQN_API bool Dqn_OS_JobQueueSPMCAddArray(Dqn_JobQueueSPMC *job_queue, Dqn_Job *jobs, uint32_t count) +{ + if (!job_queue) + return false; + + uint32_t const pot_mask = DQN_ARRAY_UCOUNT(job_queue->jobs) - 1; + uint32_t read_index = job_queue->read_index; + uint32_t write_index = job_queue->write_index; + uint32_t size = write_index - read_index; + + if ((size + count) > DQN_ARRAY_UCOUNT(job_queue->jobs)) + return false; + + for (size_t offset = 0; offset < count; offset++) { + uint32_t wrapped_write_index = (write_index + offset) & pot_mask; + job_queue->jobs[wrapped_write_index] = jobs[offset]; + } + + Dqn_OS_MutexLock(&job_queue->mutex); + job_queue->write_index += count; + Dqn_OS_SemaphoreIncrement(&job_queue->thread_wait_for_job_semaphore, count); + Dqn_OS_MutexUnlock(&job_queue->mutex); + return true; +} + +DQN_API bool Dqn_OS_JobQueueSPMCAdd(Dqn_JobQueueSPMC *job_queue, Dqn_Job job) +{ + bool result = Dqn_OS_JobQueueSPMCAddArray(job_queue, &job, 1); + return result; +} + +DQN_API int32_t Dqn_OS_JobQueueSPMCThread(Dqn_OSThread *thread) +{ + Dqn_JobQueueSPMC *job_queue = DQN_CAST(Dqn_JobQueueSPMC *) thread->user_context; + uint32_t const pot_mask = DQN_ARRAY_UCOUNT(job_queue->jobs) - 1; + + for (;;) { + Dqn_OS_SemaphoreWait(&job_queue->thread_wait_for_job_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT); + if (job_queue->quit) + break; + + DQN_ASSERT(job_queue->read_index != job_queue->write_index); + + Dqn_OS_MutexLock(&job_queue->mutex); + uint32_t wrapped_read_index = job_queue->read_index & pot_mask; + Dqn_Job job = job_queue->jobs[wrapped_read_index]; + job_queue->read_index += 1; + Dqn_OS_MutexUnlock(&job_queue->mutex); + + job.func(job.user_context); + Dqn_Arena_Deinit(job.arena); + + Dqn_OS_MutexLock(&job_queue->mutex); + job_queue->complete_index += 1; + if (job_queue->complete_index == job_queue->write_index && job_queue->threads_waiting_for_completion) { + Dqn_OS_SemaphoreIncrement(&job_queue->wait_for_completion_semaphore, + job_queue->threads_waiting_for_completion); + job_queue->threads_waiting_for_completion = 0; + } + Dqn_OS_MutexUnlock(&job_queue->mutex); + } + + return job_queue->quit_exit_code; +} + +DQN_API void Dqn_OS_JobQueueSPMCWaitForCompletion(Dqn_JobQueueSPMC *job_queue) +{ + Dqn_OS_MutexLock(&job_queue->mutex); + if (job_queue->read_index == job_queue->write_index) { + Dqn_OS_MutexUnlock(&job_queue->mutex); + return; + } + job_queue->threads_waiting_for_completion++; + Dqn_OS_MutexUnlock(&job_queue->mutex); + + Dqn_OS_SemaphoreWait(&job_queue->wait_for_completion_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT); +} diff --git a/dqn_helpers.h b/dqn_helpers.h index acb0078..dbc9194 100644 --- a/dqn_helpers.h +++ b/dqn_helpers.h @@ -1,60 +1,35 @@ -// NOTE: [$PCGX] Dqn_PCG32 ========================================================================= -// Random number generator of the PCG family. Implementation taken from Martins -// Mmozeiko from Handmade Network. -// https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 +//////////////////////////////////////////////////////////////////////////////////////////////////// // -// NOTE: API ======================================================================================= -// @proc Dqn_PCG32_Range -// @desc Returns a value in the [low; high) interval - -// @proc Dqn_PCG32_NextF -// @desc Returns a float & double in [0; 1) interval; +// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| +// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | +// \__| \__|\________|\________|\__| \________|\__| \__| \______/ +// +// dqn_helpers.h -- Helper functions/data structures +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$PCG3] Dqn_PCG32 -- -- RNG from the PCG family +// [$JSON] Dqn_JSONBuilder -- DQN_JSON_BUILDER -- Construct json output +// [$BHEX] Dqn_Bin -- DQN_BIN -- Binary <-> hex helpers +// [$BSEA] Dqn_BinarySearch -- -- Binary search +// [$BITS] Dqn_Bit -- -- Bitset manipulation +// [$SAFE] Dqn_Safe -- -- Safe arithmetic, casts, asserts +// [$MISC] Misc -- -- Uncategorised helper functions +// [$DLIB] Dqn_Library -- -- Globally shared runtime data for this library +// [$PROF] Dqn_Profiler -- DQN_PROFILER -- Profiler that measures using a timestamp counter +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// NOTE: [$PCGX] Dqn_PCG32 ///////////////////////////////////////////////////////////////////////// struct Dqn_PCG32 { uint64_t state; }; -DQN_API uint32_t Dqn_PCG32_Next (Dqn_PCG32 *rng); -DQN_API uint64_t Dqn_PCG32_Next64 (Dqn_PCG32 *rng); -DQN_API uint32_t Dqn_PCG32_Range (Dqn_PCG32 *rng, uint32_t low, uint32_t high); -DQN_API Dqn_f32 Dqn_PCG32_NextF32(Dqn_PCG32 *rng); -DQN_API Dqn_f64 Dqn_PCG32_NextF64(Dqn_PCG32 *rng); -DQN_API void Dqn_PCG32_Seed (Dqn_PCG32 *rng, uint64_t seed); -DQN_API void Dqn_PCG32_Advance(Dqn_PCG32 *rng, uint64_t delta); - #if !defined(DQN_NO_JSON_BUILDER) -// NOTE: [$JSON] Dqn_JSONBuilder =================================================================== -// Basic helper class to construct JSON output to a string -// TODO(dqn): We need to write tests for this -// -// NOTE: API ======================================================================================= -// @proc Dqn_JSONBuilder_Build -// @desc Convert the internal JSON buffer in the builder into a string. -// @param[in] arena The allocator to use to build the string - -// @proc Dqn_JSONBuilder_KeyValue, Dqn_JSONBuilder_KeyValueF -// @desc Add a JSON key value pair untyped. The value is emitted directly -// without checking the contents of value. -// -// All other functions internally call into this function which is the main -// workhorse of the builder. - -// @proc Dqn_JSON_Builder_ObjectEnd -// @desc End a JSON object in the builder, generates internally a '}' string - -// @proc Dqn_JSON_Builder_ArrayEnd -// @desc End a JSON array in the builder, generates internally a ']' string - -// @proc Dqn_JSONBuilder_LiteralNamed -// @desc Add a named JSON key-value object whose value is directly written to -// the following '"": ' (e.g. useful for emitting the 'null' -// value) - -// @proc Dqn_JSONBuilder_U64Named, Dqn_JSONBuilder_U64, -// Dqn_JSONBuilder_I64Named, Dqn_JSONBuilder_I64, -// Dqn_JSONBuilder_F64Named, Dqn_JSONBuilder_F64, -// Dqn_JSONBuilder_BoolNamed, Dqn_JSONBuilder_Bool, -// @desc Add the named JSON data type as a key-value object. Generates -// internally the string '"": ' - +// NOTE: [$JSON] Dqn_JSONBuilder /////////////////////////////////////////////////////////////////// enum Dqn_JSONBuilderItem { Dqn_JSONBuilderItem_Empty, @@ -71,138 +46,10 @@ struct Dqn_JSONBuilder int spaces_per_indent; // The number of spaces per indent level Dqn_JSONBuilderItem last_item; }; - -#define Dqn_JSONBuilder_Object(builder) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBegin(builder), \ - Dqn_JSONBuilder_ObjectEnd(builder)) - -#define Dqn_JSONBuilder_ObjectNamed(builder, name) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBeginNamed(builder, name), \ - Dqn_JSONBuilder_ObjectEnd(builder)) - -#define Dqn_JSONBuilder_Array(builder) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBegin(builder), \ - Dqn_JSONBuilder_ArrayEnd(builder)) - -#define Dqn_JSONBuilder_ArrayNamed(builder, name) \ - DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBeginNamed(builder, name), \ - Dqn_JSONBuilder_ArrayEnd(builder)) - - -DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init (Dqn_Allocator allocator, int spaces_per_indent); -DQN_API Dqn_Str8 Dqn_JSONBuilder_Build (Dqn_JSONBuilder const *builder, Dqn_Allocator allocator); -DQN_API void Dqn_JSONBuilder_KeyValue (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); -DQN_API void Dqn_JSONBuilder_KeyValueF (Dqn_JSONBuilder *builder, Dqn_Str8 key, char const *value_fmt, ...); -DQN_API void Dqn_JSONBuilder_ObjectBeginNamed(Dqn_JSONBuilder *builder, Dqn_Str8 name); -DQN_API void Dqn_JSONBuilder_ObjectEnd (Dqn_JSONBuilder *builder); -DQN_API void Dqn_JSONBuilder_ArrayBeginNamed (Dqn_JSONBuilder *builder, Dqn_Str8 name); -DQN_API void Dqn_JSONBuilder_ArrayEnd (Dqn_JSONBuilder *builder); -DQN_API void Dqn_JSONBuilder_Str8Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); -DQN_API void Dqn_JSONBuilder_LiteralNamed (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); -DQN_API void Dqn_JSONBuilder_U64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, uint64_t value); -DQN_API void Dqn_JSONBuilder_I64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, int64_t value); -DQN_API void Dqn_JSONBuilder_F64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, double value, int decimal_places); -DQN_API void Dqn_JSONBuilder_BoolNamed (Dqn_JSONBuilder *builder, Dqn_Str8 key, bool value); - -#define Dqn_JSONBuilder_ObjectBegin(builder) Dqn_JSONBuilder_ObjectBeginNamed(builder, DQN_STRING8("")) -#define Dqn_JSONBuilder_ArrayBegin(builder) Dqn_JSONBuilder_ArrayBeginNamed(builder, DQN_STRING8("")) -#define Dqn_JSONBuilder_Str8(builder, value) Dqn_JSONBuilder_Str8Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_Literal(builder, value) Dqn_JSONBuilder_LiteralNamed(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_U64(builder, value) Dqn_JSONBuilder_U64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_I64(builder, value) Dqn_JSONBuilder_I64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_F64(builder, value) Dqn_JSONBuilder_F64Named(builder, DQN_STRING8(""), value) -#define Dqn_JSONBuilder_Bool(builder, value) Dqn_JSONBuilder_BoolNamed(builder, DQN_STRING8(""), value) #endif // !defined(DQN_NO_JSON_BUIDLER) #if !defined(DQN_NO_BIN) -// NOTE: [$BHEX] Dqn_Bin =========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Bin_U64ToHexU64Str8 -// @desc Convert a 64 bit number to a hex string -// @param[in] number Number to convert to hexadecimal representation -// @param[in] flags Bit flags from Dqn_BinHexU64Str8Flags to customise the -// output of the hexadecimal string. -// @return The hexadecimal representation of the number. This string is always -// null-terminated. - -// @proc Dqn_Bin_U64ToHex -// @copybrief Dqn_Bin_U64ToHexU64Str8 - -// @param[in] allocator The memory allocator to use for the memory of the -// hexadecimal string. -// @copyparams Dqn_Bin_U64ToHexU64Str8 - -// @proc Dqn_Bin_HexBufferToU64 -// @desc Convert a hexadecimal string a 64 bit value. -// Asserts if the hex string is too big to be converted into a 64 bit number. - -// @proc Dqn_Bin_HexToU64 -// @copydoc Dqn_Bin_HexToU64 - -// @proc Dqn_Bin_BytesToHexBuffer -// @desc Convert a binary buffer into its hex representation into dest. -// -// The dest buffer must be large enough to contain the hex representation, i.e. -// atleast (src_size * 2). -// -// @return True if the conversion into the dest buffer was successful, false -// otherwise (e.g. invalid arguments). - -// @proc Dqn_Bin_BytesToHexBufferArena -// @desc Convert a series of bytes into a string -// @return A null-terminated hex string, null pointer if allocation failed - -// @proc Dqn_Bin_BytesToHexArena -// @copydoc Dqn_Bin_BytesToHexBufferArena -// @return A hex string, the string is invalid if conversion failed. - -// @proc Dqn_Bin_HexBufferToBytes -// @desc Convert a hex string into binary at `dest`. -// -// The dest buffer must be large enough to contain the binary representation, -// i.e. atleast ceil(hex_size / 2). This function will strip whitespace, -// leading 0x/0X prefix from the string before conversion. -// -// @param[in] hex The hexadecimal string to convert -// @param[in] hex_size Size of the hex buffer. This function can handle an odd -// size hex string i.e. "fff" produces 0xfff0. -// @param[out] dest Buffer to write the bytes to -// @param[out] dest_size Maximum number of bytes to write to dest -// -// @return The number of bytes written to `dest_size`, this value will *never* -// be greater than `dest_size`. - -// @proc Dqn_Bin_HexToBytes -// @desc Str8 variant of @see Dqn_Bin_HexBufferToBytes - -// @proc Dqn_Bin_Str8HexBufferToBytesUnchecked -// @desc Unchecked variant of @see Dqn_Bin_HexBufferToBytes -// -// This function skips some string checks, it assumes the hex is a valid hex -// stream and that the arguments are valid e.g. no trimming or 0x prefix -// stripping is performed - -// @proc Dqn_Bin_Str8 -// @desc Str8 variant of @see Dqn_Bin_HexBufferToBytesUnchecked - -// @proc Dqn_Bin_HexBufferToBytesArena -// Dynamic allocating variant of @see Dqn_Bin_HexBufferToBytesUnchecked -// -// @param[in] arena The arena to allocate the bytes from -// @param[in] hex Hex string to convert into bytes -// @param[in] size Size of the hex string -// @param[out] real_size The size of the buffer returned by the function -// -// @return The byte representation of the hex string. - -// @proc Dqn_Bin_HexToBytesArena -// @copybrief Dqn_Bin_HexBufferToBytesArena -// -// @param[in] arena The arena to allocate the bytes from -// @param[in] hex Hex string to convert into bytes -// -// @return The byte representation of the hex string. - +// NOTE: [$BHEX] Dqn_Bin /////////////////////////////////////////////////////////////////////////// struct Dqn_BinHexU64Str8 { char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; @@ -214,37 +61,15 @@ enum Dqn_BinHexU64Str8Flags Dqn_BinHexU64Str8Flags_No0xPrefix = 1 << 0, /// Remove the 0x prefix from the string Dqn_BinHexU64Str8Flags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex }; - -DQN_API char const * Dqn_Bin_HexBufferTrim0x (char const *hex, Dqn_usize size, Dqn_usize *real_size); -DQN_API Dqn_Str8 Dqn_Bin_HexTrim0x (Dqn_Str8 string); - -DQN_API Dqn_BinHexU64Str8 Dqn_Bin_U64ToHexU64Str8 (uint64_t number, uint32_t flags); -DQN_API Dqn_Str8 Dqn_Bin_U64ToHex (Dqn_Allocator allocator, uint64_t number, uint32_t flags); - -DQN_API uint64_t Dqn_Bin_HexBufferToU64 (char const *hex, Dqn_usize size); -DQN_API uint64_t Dqn_Bin_HexToU64 (Dqn_Str8 hex); - -DQN_API Dqn_Str8 Dqn_Bin_BytesToHexArena (Dqn_Arena *arena, void const *src, Dqn_usize size); -DQN_API char * Dqn_Bin_BytesToHexBufferArena (Dqn_Arena *arena, void const *src, Dqn_usize size); -DQN_API bool Dqn_Bin_BytesToHexBuffer (void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size); - -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked(char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); -DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes (char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); -DQN_API char * Dqn_Bin_HexBufferToBytesArena (Dqn_Arena *arena, char const *hex, Dqn_usize hex_size, Dqn_usize *real_size); - -DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked (Dqn_Str8 hex, void *dest, Dqn_usize dest_size); -DQN_API Dqn_usize Dqn_Bin_HexToBytes (Dqn_Str8 hex, void *dest, Dqn_usize dest_size); -DQN_API Dqn_Str8 Dqn_Bin_HexToBytesArena (Dqn_Arena *arena, Dqn_Str8 hex); #endif // !defined(DQN_NO_BIN) -// NOTE: [$BSEA] Dqn_BinarySearch ================================================================== +// NOTE: [$BSEA] Dqn_BinarySearch ////////////////////////////////////////////////////////////////// template using Dqn_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); template bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); -// TODO(doyle): Implement lower/upper bound searching enum Dqn_BinarySearchType { // Index of the match. If no match is found, found is set to false and the @@ -253,14 +78,26 @@ enum Dqn_BinarySearchType // [last index + 1] of the array). Dqn_BinarySearchType_Match, - // Index of the first element in the array that is `<= find`. If no such + // Index of the first element in the array that is `element >= find`. If no such // item is found or the array is empty, then, the index is set to the array // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // Dqn_BinarySearchResult result = Dqn_BinarySearch(array, DQN_ARRAY_UCOUNT(array), 4, Dqn_BinarySearchType_LowerBound); + // printf("%zu\n", result.index); // Prints index '4' + Dqn_BinarySearchType_LowerBound, - // Index of the first element in the array that is `> find`. If no such + // Index of the first element in the array that is `element > find`. If no such // item is found or the array is empty, then, the index is set to the array // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // Dqn_BinarySearchResult result = Dqn_BinarySearch(array, DQN_ARRAY_UCOUNT(array), 4, Dqn_BinarySearchType_UpperBound); + // printf("%zu\n", result.index); // Prints index '5' + Dqn_BinarySearchType_UpperBound, }; @@ -270,287 +107,45 @@ struct Dqn_BinarySearchResult Dqn_usize index; }; -template -Dqn_BinarySearchResult Dqn_BinarySearch(T const *array, - Dqn_usize array_size, - T const &find, - Dqn_BinarySearchType type = Dqn_BinarySearchType_Match, - Dqn_BinarySearchLessThanProc less_than = Dqn_BinarySearch_DefaultLessThan); - -template -bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) -{ - bool result = lhs < rhs; - return result; -} - -template -Dqn_BinarySearchResult -Dqn_BinarySearch(T const *array, - Dqn_usize array_size, - T const &find, - Dqn_BinarySearchType type, - Dqn_BinarySearchLessThanProc less_than) -{ - Dqn_BinarySearchResult result = {}; - if (!array || array_size <= 0) - return result; - - T const *end = array + array_size; - T const *first = array; - T const *last = end; - while (first != last) { - Dqn_usize count = last - first; - T const *it = first + (count / 2); - - bool advance_first = false; - if (type == Dqn_BinarySearchType_UpperBound) - advance_first = !less_than(find, it[0]); - else - advance_first = less_than(it[0], find); - - if (advance_first) - first = it + 1; - else - last = it; - } - - switch (type) { - case Dqn_BinarySearchType_Match: { - result.found = first != end && !less_than(find, *first); - } break; - - case Dqn_BinarySearchType_LowerBound: /*FALLTHRU*/ - case Dqn_BinarySearchType_UpperBound: { - result.found = first != end; - } break; - } - - result.index = first - array; - return result; -} - -// NOTE: [$BITS] Dqn_Bit =========================================================================== -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); - -// NOTE: [$SAFE] Dqn_Safe ========================================================================== -// @proc Dqn_Safe_AddI64, Dqn_Safe_MulI64 -// @desc Add/multiply 2 I64's together, safe asserting that the operation does -// not overflow. -// @return The result of operation, INT64_MAX if it overflowed. - -// @proc Dqn_Safe_AddU64, Dqn_Safe_MulU64 -// @desc Add/multiply 2 U64's together, safe asserting that the operation does -// not overflow. -// @return The result of operation, UINT64_MAX if it overflowed. - -// @proc Dqn_Safe_SubU64, Dqn_Safe_SubU32 -// @desc Subtract 2xU64 or 2xU32's together, safe asserting that the operation -// does not overflow. -// @return The result of operation, 0 if it overflowed. - -// @proc Dqn_Safe_SaturateCastUSizeToInt, -// Dqn_Safe_SaturateCastUSizeToI8, -// Dqn_Safe_SaturateCastUSizeToI16, -// Dqn_Safe_SaturateCastUSizeToI32, -// Dqn_Safe_SaturateCastUSizeToI64 -// -// Dqn_Safe_SaturateCastU64ToUInt, -// Dqn_Safe_SaturateCastU64ToU8, -// Dqn_Safe_SaturateCastU64ToU16, -// Dqn_Safe_SaturateCastU64ToU32, -// -// Dqn_Safe_SaturateCastUSizeToU8, -// Dqn_Safe_SaturateCastUSizeToU16, -// Dqn_Safe_SaturateCastUSizeToU32, -// Dqn_Safe_SaturateCastUSizeToU64 -// -// Dqn_Safe_SaturateCastISizeToInt, -// Dqn_Safe_SaturateCastISizeToI8, -// Dqn_Safe_SaturateCastISizeToI16, -// Dqn_Safe_SaturateCastISizeToI32, -// Dqn_Safe_SaturateCastISizeToI64, -// -// int Dqn_Safe_SaturateCastISizeToUInt, -// Dqn_Safe_SaturateCastISizeToU8, -// Dqn_Safe_SaturateCastISizeToU16, -// Dqn_Safe_SaturateCastISizeToU32, -// Dqn_Safe_SaturateCastISizeToU64, -// -// Dqn_Safe_SaturateCastI64ToISize, -// Dqn_Safe_SaturateCastI64ToI8, -// Dqn_Safe_SaturateCastI64ToI16, -// Dqn_Safe_SaturateCastI64ToI32, -// -// Dqn_Safe_SaturateCastIntToI8, -// Dqn_Safe_SaturateCastIntToU8, -// Dqn_Safe_SaturateCastIntToU16, -// Dqn_Safe_SaturateCastIntToU32, -// Dqn_Safe_SaturateCastIntToU64, -// -// @desc Truncate the lhs operand to the right clamping the result to the max -// value of the desired data type. Safe asserts if clamping occurs. -// -// The following sentinel values are returned when saturated, -// USize -> Int: INT_MAX -// USize -> I8: INT8_MAX -// USize -> I16: INT16_MAX -// USize -> I32: INT32_MAX -// USize -> I64: INT64_MAX -// -// U64 -> UInt: UINT_MAX -// U64 -> U8: UINT8_MAX -// U64 -> U16: UINT16_MAX -// U64 -> U32: UINT32_MAX -// -// USize -> U8: UINT8_MAX -// USize -> U16: UINT16_MAX -// USize -> U32: UINT32_MAX -// USize -> U64: UINT64_MAX -// -// ISize -> Int: INT_MIN or INT_MAX -// ISize -> I8: INT8_MIN or INT8_MAX -// ISize -> I16: INT16_MIN or INT16_MAX -// ISize -> I32: INT32_MIN or INT32_MAX -// ISize -> I64: INT64_MIN or INT64_MAX -// -// ISize -> UInt: 0 or UINT_MAX -// ISize -> U8: 0 or UINT8_MAX -// ISize -> U16: 0 or UINT16_MAX -// ISize -> U32: 0 or UINT32_MAX -// ISize -> U64: 0 or UINT64_MAX -// -// I64 -> ISize: DQN_ISIZE_MIN or DQN_ISIZE_MAX -// I64 -> I8: INT8_MIN or INT8_MAX -// I64 -> I16: INT16_MIN or INT16_MAX -// I64 -> I32: INT32_MIN or INT32_MAX -// -// Int -> I8: INT8_MIN or INT8_MAX -// Int -> I16: INT16_MIN or INT16_MAX -// Int -> U8: 0 or UINT8_MAX -// Int -> U16: 0 or UINT16_MAX -// Int -> U32: 0 or UINT32_MAX -// Int -> U64: 0 or UINT64_MAX - -DQN_API int64_t Dqn_Safe_AddI64 (int64_t a, int64_t b); -DQN_API int64_t Dqn_Safe_MulI64 (int64_t a, int64_t b); - -DQN_API uint64_t Dqn_Safe_AddU64 (uint64_t a, uint64_t b); -DQN_API uint64_t Dqn_Safe_MulU64 (uint64_t a, uint64_t b); - -DQN_API uint64_t Dqn_Safe_SubU64 (uint64_t a, uint64_t b); -DQN_API uint32_t Dqn_Safe_SubU32 (uint32_t a, uint32_t b); - -DQN_API int Dqn_Safe_SaturateCastUSizeToInt (Dqn_usize val); -DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8 (Dqn_usize val); -DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16 (Dqn_usize val); -DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32 (Dqn_usize val); -DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64 (Dqn_usize val); - -DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt (uint64_t val); -DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8 (uint64_t val); -DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16 (uint64_t val); -DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32 (uint64_t val); - -DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8 (Dqn_usize val); -DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16 (Dqn_usize val); -DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32 (Dqn_usize val); -DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64 (Dqn_usize val); - -DQN_API int Dqn_Safe_SaturateCastISizeToInt (Dqn_isize val); -DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8 (Dqn_isize val); -DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16 (Dqn_isize val); -DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32 (Dqn_isize val); -DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64 (Dqn_isize val); - -DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val); -DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8 (Dqn_isize val); -DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16 (Dqn_isize val); -DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32 (Dqn_isize val); -DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64 (Dqn_isize val); - -DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize (int64_t val); -DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8 (int64_t val); -DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16 (int64_t val); -DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32 (int64_t val); - -DQN_API int8_t Dqn_Safe_SaturateCastIntToI8 (int val); -DQN_API int16_t Dqn_Safe_SaturateCastIntToI16 (int val); -DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8 (int val); -DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16 (int val); -DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32 (int val); -DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64 (int val); - -// NOTE: [$MISC] Misc ============================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_SNPrintFDotTruncate -// @desc Write the format string to the buffer truncating with a trailing ".." -// if there is insufficient space in the buffer followed by null-terminating -// the buffer (uses stb_sprintf underneath). -// @return The size of the string written to the buffer *not* including the -// null-terminator. -// -// @proc Dqn_U64ToStr8 -// @desc Convert a 64 bit unsigned value to its string representation. -// @param[in] val Value to convert into a string -// @param[in] separator The separator to insert every 3 digits. Set this to -// 0 if no separator is desired. - +// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// struct Dqn_U64Str8 { char data[27+1]; // NOTE(dqn): 27 is the maximum size of uint64_t including a separtor uint8_t size; }; -DQN_API int Dqn_SNPrintFDotTruncate(char *buffer, int size, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API Dqn_U64Str8 Dqn_U64ToStr8 (uint64_t val, char separator); +enum Dqn_U64ByteSizeType +{ + Dqn_U64ByteSizeType_B, + Dqn_U64ByteSizeType_KiB, + Dqn_U64ByteSizeType_MiB, + Dqn_U64ByteSizeType_GiB, + Dqn_U64ByteSizeType_TiB, + Dqn_U64ByteSizeType_Count, + Dqn_U64ByteSizeType_Auto, +}; + +struct Dqn_U64ByteSize +{ + Dqn_U64ByteSizeType type; + Dqn_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c + Dqn_f64 bytes; +}; + +enum Dqn_U64AgeUnit +{ + Dqn_U64AgeUnit_Sec = 1 << 0, + Dqn_U64AgeUnit_Min = 1 << 1, + Dqn_U64AgeUnit_Hr = 1 << 2, + Dqn_U64AgeUnit_Day = 1 << 3, + Dqn_U64AgeUnit_Week = 1 << 4, + Dqn_U64AgeUnit_Year = 1 << 5, + Dqn_U64AgeUnit_HMS = Dqn_U64AgeUnit_Sec | Dqn_U64AgeUnit_Min | Dqn_U64AgeUnit_Hr, + Dqn_U64AgeUnit_All = Dqn_U64AgeUnit_HMS | Dqn_U64AgeUnit_Day | Dqn_U64AgeUnit_Week | Dqn_U64AgeUnit_Year, +}; #if !defined(DQN_NO_PROFILER) -// NOTE: [$PROF] Dqn_Profiler ====================================================================== -// A profiler based off Casey Muratori's Computer Enhance course, Performance -// Aware Programming. This profiler measures function elapsed time using the -// CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count -// that can be converted into a duration. -// -// This profiler uses a double buffer scheme for storing profiling markers. -// After an application's typical update/frame cycle you can swap the profiler's -// buffer whereby the front buffer contains the previous frames profiling -// metrics and the back buffer will be populated with the new frame's profiling -// metrics. -#if 0 - uint64_t tsc_per_seconds = Dqn_EstimateTSCPerSecond(/*duration_ms_to_gauge_tsc_frequency*/ 100); - for (;;) { // Main update loop - enum Zone { Zone_MainLoop, Zone_Count }; - Dqn_ProfilerZone profiler_zone_main_update = Dqn_Profiler_BeginZone(Zone_MainLoop); - Dqn_ProfilerAnchor *anchors = Dqn_ProfilerAnchorBuffer(Dqn_ProfilerAnchorBuffer_Front); - for (size_t index = 0; index < Zone_Count; index++) { - Dqn_ProfilerAnchor *anchor = anchors + index; - printf("%.*s[%u] %zu cycles (%.1fms)\n", - DQN_STRING_FMT(anchor->anchor_index), - anchor->hit_count, - anchor->tsc_inclusive, - anchor->tsc_inclusive * tsc_per_seconds * 1000.f); - } - Dqn_Profiler_EndZone(&profiler_zone_main_update); - Dqn_Profiler_SwapAnchorBuffer(Zone_Count); // Should occur after all profiling zones are ended! - } -#endif - -// @proc Dqn_Profiler_AnchorBuffer -// Retrieve the requested buffer from the profiler for writing/reading -// profiling metrics. -// -// @param buffer Enum that lets you specify which buffer to grab from the -// profiler. The front buffer contains the previous frame's profiling metrics -// and the back buffer is where the profiler is current writing to. -// -// For end user intents and purposes, you likely only need to read the front -// buffer which contain the metrics that you can visualise regarding the most -// profiling metrics recorded. - +// NOTE: [$PROF] Dqn_Profiler ////////////////////////////////////////////////////////////////////// #if !defined(DQN_PROFILER_ANCHOR_BUFFER_SIZE) #define DQN_PROFILER_ANCHOR_BUFFER_SIZE 128 #endif @@ -601,81 +196,68 @@ struct Dqn_Profiler uint8_t active_anchor_buffer; uint16_t parent_zone; }; - -// NOTE: Macros ==================================================================================== -#define Dqn_Profiler_BeginZone(name) Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8(name), __COUNTER__ + 1) - -// NOTE: API ======================================================================================= -Dqn_ProfilerZone Dqn_Profiler_BeginZoneWithIndex(Dqn_Str8 name, uint16_t anchor_index); -void Dqn_Profiler_EndZone (Dqn_ProfilerZone zone); -Dqn_ProfilerAnchor *Dqn_Profiler_AnchorBuffer (Dqn_ProfilerAnchorBuffer buffer); -void Dqn_Profiler_SwapAnchorBuffer (); -void Dqn_Profiler_Dump (uint64_t tsc_per_second); #endif // !defined(DQN_NO_PROFILER) -// NOTE: [$DLIB] Dqn_Library ======================================================================= +// NOTE: [$JOBQ] Dqn_JobQueue /////////////////////////////////////////////////////////////////////// +typedef void (Dqn_JobQueueFunc)(void *user_context); +struct Dqn_Job +{ + Dqn_Arena *arena; + Dqn_JobQueueFunc *func; + void *user_context; +}; + +struct Dqn_JobQueueSPMC +{ + Dqn_OSMutex mutex; + Dqn_OSSemaphore thread_wait_for_job_semaphore; + Dqn_OSSemaphore wait_for_completion_semaphore; + uint32_t threads_waiting_for_completion; + + Dqn_Job jobs[32]; + Dqn_b32 quit; + uint32_t quit_exit_code; + uint32_t volatile read_index; + uint32_t volatile complete_index; + uint32_t volatile write_index; +}; + +// NOTE: [$DLIB] Dqn_Library /////////////////////////////////////////////////////////////////////// // 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; // True if the library has been initialised via `Dqn_Library_Init` - Dqn_TicketMutex lib_mutex; - Dqn_Str8 exe_dir; // The directory of the current executable - - Dqn_Arena arena; - Dqn_ArenaCatalog arena_catalog; - - 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 - Dqn_FsFile 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 - - // NOTE: Leak Tracing ========================================================================== - - bool allocs_are_allowed_to_leak; - bool alloc_tracking_disabled; - #if defined(DQN_LEAK_TRACING) - #if defined(DQN_NO_DSMAP) - #error "DSMap is required for allocation tracing" - #endif - - // TODO(doyle): @robust Setting some of these flags is not multi-thread safe - uint16_t stack_trace_offset_to_our_call_stack; + bool lib_init; // True if the library has been initialised via `Dqn_Library_Init` + Dqn_TicketMutex lib_mutex; + Dqn_Str8 exe_dir; // The directory of the current executable + Dqn_Arena arena; + Dqn_ChunkPool pool; // Uses 'arena' for malloc-like allocations + Dqn_ArenaCatalog arena_catalog; + bool slow_verification_checks; // Enable expensive library verification checks + // NOTE: Logging /////////////////////////////////////////////////////////////////////////////// + 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 + Dqn_OSFile 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 + // NOTE: Leak Tracing ////////////////////////////////////////////////////////////////////////// + #if defined(DQN_LEAK_TRACKING) + Dqn_DSMap alloc_table; Dqn_TicketMutex alloc_table_mutex; - Dqn_DSMap alloc_table; + Dqn_Arena alloc_table_arena; #endif - - // NOTE: OS ==================================================================================== - + // NOTE: Win32 ///////////////////////////////////////////////////////////////////////////////// #if defined(DQN_OS_WIN32) - LARGE_INTEGER win32_qpc_frequency; - Dqn_TicketMutex win32_bcrypt_rng_mutex; - void *win32_bcrypt_rng_handle; + LARGE_INTEGER win32_qpc_frequency; + Dqn_TicketMutex win32_bcrypt_rng_mutex; + void * win32_bcrypt_rng_handle; #endif - - uint32_t os_page_size; - uint32_t os_alloc_granularity; - - // NOTE: Profiler ============================================================================== - + bool win32_sym_initialised; + // NOTE: OS //////////////////////////////////////////////////////////////////////////////////// + uint32_t os_page_size; + uint32_t os_alloc_granularity; + // NOTE: Profiler ////////////////////////////////////////////////////////////////////////////// #if !defined(DQN_NO_PROFILER) Dqn_Profiler *profiler; Dqn_Profiler profiler_default_instance; @@ -688,12 +270,233 @@ enum Dqn_LibraryOnInit Dqn_LibraryOnInit_LogFeatures, }; -// NOTE: API ======================================================================================= -DQN_API Dqn_Library *Dqn_Library_Init (Dqn_LibraryOnInit on_init); -DQN_API void Dqn_Library_SetPointer (Dqn_Library *library); -#if !defined(DQN_NO_PROFILER) -DQN_API void Dqn_Library_SetProfiler (Dqn_Profiler *profiler); -#endif -DQN_API void Dqn_Library_SetLogCallback (Dqn_LogProc *proc, void *user_data); -DQN_API void Dqn_Library_DumpThreadContextArenaStat(Dqn_Str8 file_path); +// NOTE: [$PCGX] Dqn_PCG32 ///////////////////////////////////////////////////////////////////////// +DQN_API Dqn_PCG32 Dqn_PCG32_Init (uint64_t seed); +DQN_API uint32_t Dqn_PCG32_Next (Dqn_PCG32 *rng); +DQN_API uint64_t Dqn_PCG32_Next64 (Dqn_PCG32 *rng); +DQN_API uint32_t Dqn_PCG32_Range (Dqn_PCG32 *rng, uint32_t low, uint32_t high); +DQN_API Dqn_f32 Dqn_PCG32_NextF32 (Dqn_PCG32 *rng); +DQN_API Dqn_f64 Dqn_PCG32_NextF64 (Dqn_PCG32 *rng); +DQN_API void Dqn_PCG32_Advance (Dqn_PCG32 *rng, uint64_t delta); + +#if !defined(DQN_NO_JSON_BUILDER) +// NOTE: [$JSON] Dqn_JSONBuilder /////////////////////////////////////////////////////////////////// +#define Dqn_JSONBuilder_Object(builder) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBegin(builder), \ + Dqn_JSONBuilder_ObjectEnd(builder)) + +#define Dqn_JSONBuilder_ObjectNamed(builder, name) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ObjectBeginNamed(builder, name), \ + Dqn_JSONBuilder_ObjectEnd(builder)) + +#define Dqn_JSONBuilder_Array(builder) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBegin(builder), \ + Dqn_JSONBuilder_ArrayEnd(builder)) + +#define Dqn_JSONBuilder_ArrayNamed(builder, name) \ + DQN_DEFER_LOOP(Dqn_JSONBuilder_ArrayBeginNamed(builder, name), \ + Dqn_JSONBuilder_ArrayEnd(builder)) + + +DQN_API Dqn_JSONBuilder Dqn_JSONBuilder_Init (Dqn_Arena *arena, int spaces_per_indent); +DQN_API Dqn_Str8 Dqn_JSONBuilder_Build (Dqn_JSONBuilder const *builder, Dqn_Arena *arena); +DQN_API void Dqn_JSONBuilder_KeyValue (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); +DQN_API void Dqn_JSONBuilder_KeyValueF (Dqn_JSONBuilder *builder, Dqn_Str8 key, char const *value_fmt, ...); +DQN_API void Dqn_JSONBuilder_ObjectBeginNamed (Dqn_JSONBuilder *builder, Dqn_Str8 name); +DQN_API void Dqn_JSONBuilder_ObjectEnd (Dqn_JSONBuilder *builder); +DQN_API void Dqn_JSONBuilder_ArrayBeginNamed (Dqn_JSONBuilder *builder, Dqn_Str8 name); +DQN_API void Dqn_JSONBuilder_ArrayEnd (Dqn_JSONBuilder *builder); +DQN_API void Dqn_JSONBuilder_Str8Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); +DQN_API void Dqn_JSONBuilder_LiteralNamed (Dqn_JSONBuilder *builder, Dqn_Str8 key, Dqn_Str8 value); +DQN_API void Dqn_JSONBuilder_U64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, uint64_t value); +DQN_API void Dqn_JSONBuilder_I64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, int64_t value); +DQN_API void Dqn_JSONBuilder_F64Named (Dqn_JSONBuilder *builder, Dqn_Str8 key, double value, int decimal_places); +DQN_API void Dqn_JSONBuilder_BoolNamed (Dqn_JSONBuilder *builder, Dqn_Str8 key, bool value); + +#define Dqn_JSONBuilder_ObjectBegin(builder) Dqn_JSONBuilder_ObjectBeginNamed(builder, DQN_STR8("")) +#define Dqn_JSONBuilder_ArrayBegin(builder) Dqn_JSONBuilder_ArrayBeginNamed(builder, DQN_STR8("")) +#define Dqn_JSONBuilder_Str8(builder, value) Dqn_JSONBuilder_Str8Named(builder, DQN_STR8(""), value) +#define Dqn_JSONBuilder_Literal(builder, value) Dqn_JSONBuilder_LiteralNamed(builder, DQN_STR8(""), value) +#define Dqn_JSONBuilder_U64(builder, value) Dqn_JSONBuilder_U64Named(builder, DQN_STR8(""), value) +#define Dqn_JSONBuilder_I64(builder, value) Dqn_JSONBuilder_I64Named(builder, DQN_STR8(""), value) +#define Dqn_JSONBuilder_F64(builder, value) Dqn_JSONBuilder_F64Named(builder, DQN_STR8(""), value) +#define Dqn_JSONBuilder_Bool(builder, value) Dqn_JSONBuilder_BoolNamed(builder, DQN_STR8(""), value) +#endif // !defined(DQN_NO_JSON_BUILDER) +#if !defined(DQN_NO_BIN) +// NOTE: [$BHEX] Dqn_Bin /////////////////////////////////////////////////////////////////////////// +// TODO(doyle): I'm not happy with this API. Its ugly and feels complicated +// because I designed it as a C-API first (e.g. ptr + length) vs just accepting +// that Dqn_Str8/Dqn_Slice is a superior API to design for first. +DQN_API char const * Dqn_Bin_HexBufferTrim0x (char const *hex, Dqn_usize size, Dqn_usize *real_size); +DQN_API Dqn_Str8 Dqn_Bin_HexTrim0x (Dqn_Str8 string); + +DQN_API Dqn_BinHexU64Str8 Dqn_Bin_U64ToHexU64Str8 (uint64_t number, uint32_t flags); +DQN_API Dqn_Str8 Dqn_Bin_U64ToHex (Dqn_Arena *arena, uint64_t number, uint32_t flags); + +DQN_API uint64_t Dqn_Bin_HexToU64 (Dqn_Str8 hex); +DQN_API uint64_t Dqn_Bin_HexPtrToU64 (char const *hex, Dqn_usize size); + +DQN_API Dqn_Str8 Dqn_Bin_BytesToHexArena (Dqn_Arena *arena, void const *src, Dqn_usize size); +DQN_API char * Dqn_Bin_BytesToHexBufferArena (Dqn_Arena *arena, void const *src, Dqn_usize size); +DQN_API bool Dqn_Bin_BytesToHexBuffer (void const *src, Dqn_usize src_size, char *dest, Dqn_usize dest_size); + +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytesUnchecked (char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); +DQN_API Dqn_usize Dqn_Bin_HexBufferToBytes (char const *hex, Dqn_usize hex_size, void *dest, Dqn_usize dest_size); +DQN_API char * Dqn_Bin_HexBufferToBytesArena (Dqn_Arena *arena, char const *hex, Dqn_usize hex_size, Dqn_usize *real_size); + +DQN_API Dqn_usize Dqn_Bin_HexToBytesUnchecked (Dqn_Str8 hex, void *dest, Dqn_usize dest_size); +DQN_API Dqn_usize Dqn_Bin_HexToBytes (Dqn_Str8 hex, void *dest, Dqn_usize dest_size); +DQN_API Dqn_Str8 Dqn_Bin_HexToBytesArena (Dqn_Arena *arena, Dqn_Str8 hex); +#endif // !defined(DQN_NO_BIN) + +// NOTE: [$BSEA] Dqn_BinarySearch ////////////////////////////////////////////////////////////////// +template bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); +template Dqn_BinarySearchResult Dqn_BinarySearch (T const *array, + Dqn_usize array_size, + T const &find, + Dqn_BinarySearchType type = Dqn_BinarySearchType_Match, + Dqn_BinarySearchLessThanProc less_than = Dqn_BinarySearch_DefaultLessThan); + +// NOTE: [$BITS] Dqn_Bit /////////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_Bit_UnsetInplace (Dqn_usize *flags, Dqn_usize bitfield); +DQN_API void Dqn_Bit_SetInplace (Dqn_usize *flags, Dqn_usize bitfield); +DQN_API bool Dqn_Bit_IsSet (Dqn_usize bits, Dqn_usize bits_to_set); +DQN_API bool Dqn_Bit_IsNotSet (Dqn_usize bits, Dqn_usize bits_to_check); + +// NOTE: [$SAFE] Dqn_Safe ////////////////////////////////////////////////////////////////////////// +DQN_API int64_t Dqn_Safe_AddI64 (int64_t a, int64_t b); +DQN_API int64_t Dqn_Safe_MulI64 (int64_t a, int64_t b); + +DQN_API uint64_t Dqn_Safe_AddU64 (uint64_t a, uint64_t b); +DQN_API uint64_t Dqn_Safe_MulU64 (uint64_t a, uint64_t b); + +DQN_API uint64_t Dqn_Safe_SubU64 (uint64_t a, uint64_t b); +DQN_API uint32_t Dqn_Safe_SubU32 (uint32_t a, uint32_t b); + +DQN_API int Dqn_Safe_SaturateCastUSizeToInt (Dqn_usize val); +DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8 (Dqn_usize val); +DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16 (Dqn_usize val); +DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32 (Dqn_usize val); +DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64 (Dqn_usize val); + +DQN_API int Dqn_Safe_SaturateCastU64ToInt (uint64_t val); +DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt (uint64_t val); +DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8 (uint64_t val); +DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16 (uint64_t val); +DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32 (uint64_t val); + +DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8 (Dqn_usize val); +DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16 (Dqn_usize val); +DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32 (Dqn_usize val); +DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64 (Dqn_usize val); + +DQN_API int Dqn_Safe_SaturateCastISizeToInt (Dqn_isize val); +DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8 (Dqn_isize val); +DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16 (Dqn_isize val); +DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32 (Dqn_isize val); +DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64 (Dqn_isize val); + +DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt (Dqn_isize val); +DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8 (Dqn_isize val); +DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16 (Dqn_isize val); +DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32 (Dqn_isize val); +DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64 (Dqn_isize val); + +DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize (int64_t val); +DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8 (int64_t val); +DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16 (int64_t val); +DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32 (int64_t val); + +DQN_API int8_t Dqn_Safe_SaturateCastIntToI8 (int val); +DQN_API int16_t Dqn_Safe_SaturateCastIntToI16 (int val); +DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8 (int val); +DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16 (int val); +DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32 (int val); +DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64 (int val); + +// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// +DQN_API int Dqn_FmtBuffer3DotTruncate (char *buffer, int size, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_U64Str8 Dqn_U64ToStr8 (uint64_t val, char separator); +DQN_API Dqn_U64ByteSize Dqn_U64ToByteSize (uint64_t bytes, Dqn_U64ByteSizeType type); +DQN_API Dqn_Str8 Dqn_U64ToByteSizeStr8 (Dqn_Arena *arena, uint64_t bytes, Dqn_U64ByteSizeType desired_type); +DQN_API Dqn_Str8 Dqn_U64ByteSizeTypeString (Dqn_U64ByteSizeType type); +DQN_API Dqn_Str8 Dqn_U64ToAge (Dqn_Arena *arena, uint64_t age_s, Dqn_usize type); + +// NOTE: [$PROF] Dqn_Profiler ////////////////////////////////////////////////////////////////////// +#define Dqn_Profiler_BeginZone(name) Dqn_Profiler_BeginZoneWithIndex(DQN_STR8(name), __COUNTER__ + 1) +DQN_API Dqn_ProfilerZone Dqn_Profiler_BeginZoneWithIndex (Dqn_Str8 name, uint16_t anchor_index); +DQN_API void Dqn_Profiler_EndZone (Dqn_ProfilerZone zone); +DQN_API Dqn_ProfilerAnchor *Dqn_Profiler_AnchorBuffer (Dqn_ProfilerAnchorBuffer buffer); +DQN_API void Dqn_Profiler_SwapAnchorBuffer (); +DQN_API void Dqn_Profiler_Dump (uint64_t tsc_per_second); + +// NOTE: [$JOBQ] Dqn_JobQueue /////////////////////////////////////////////////////////////////////// +DQN_API Dqn_JobQueueSPMC Dqn_OS_JobQueueSPMCInit (); +DQN_API bool Dqn_OS_JobQueueSPMCAddArray (Dqn_JobQueueSPMC *job_queue, Dqn_Job *jobs, uint32_t count); +DQN_API bool Dqn_OS_JobQueueSPMCAdd (Dqn_JobQueueSPMC *job_queue, Dqn_Job job); +DQN_API void Dqn_OS_JobQueueSPMCWaitForCompletion (Dqn_JobQueueSPMC *job_queue); +DQN_API int32_t Dqn_OS_JobQueueSPMCThread (Dqn_OSThread *thread); + +// NOTE: Dqn_Library /////////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Library * Dqn_Library_Init (Dqn_LibraryOnInit on_init); +DQN_API void Dqn_Library_SetPointer (Dqn_Library *library); +#if !defined(DQN_NO_PROFILER) +DQN_API void Dqn_Library_SetProfiler (Dqn_Profiler *profiler); +#endif +DQN_API void Dqn_Library_SetLogCallback (Dqn_LogProc *proc, void *user_data); +DQN_API void Dqn_Library_DumpThreadContextArenaStat (Dqn_Str8 file_path); +DQN_API Dqn_Arena * Dqn_Library_AllocArenaF (Dqn_usize reserve, Dqn_usize commit, uint8_t arena_flags, char const *fmt, ...); + +// NOTE: [$BSEA] Dqn_BinarySearch ////////////////////////////////////////////////////////////////// +template +bool Dqn_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) +{ + bool result = lhs < rhs; + return result; +} + +template +Dqn_BinarySearchResult Dqn_BinarySearch(T const *array, + Dqn_usize array_size, + T const &find, + Dqn_BinarySearchType type, + Dqn_BinarySearchLessThanProc less_than) +{ + Dqn_BinarySearchResult result = {}; + if (!array || array_size <= 0) + return result; + + T const *end = array + array_size; + T const *first = array; + T const *last = end; + while (first != last) { + Dqn_usize count = last - first; + T const *it = first + (count / 2); + + bool advance_first = false; + if (type == Dqn_BinarySearchType_UpperBound) + advance_first = !less_than(find, it[0]); + else + advance_first = less_than(it[0], find); + + if (advance_first) + first = it + 1; + else + last = it; + } + + switch (type) { + case Dqn_BinarySearchType_Match: { + result.found = first != end && !less_than(find, *first); + } break; + + case Dqn_BinarySearchType_LowerBound: /*FALLTHRU*/ + case Dqn_BinarySearchType_UpperBound: { + result.found = first != end; + } break; + } + + result.index = first - array; + return result; +} diff --git a/dqn_math.cpp b/dqn_math.cpp index 9451115..fe2d475 100644 --- a/dqn_math.cpp +++ b/dqn_math.cpp @@ -1,5 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$\ $$$$$$$$\ $$\ $$\ +// $$$\ $$$ |$$ __$$\\__$$ __|$$ | $$ | +// $$$$\ $$$$ |$$ / $$ | $$ | $$ | $$ | +// $$\$$\$$ $$ |$$$$$$$$ | $$ | $$$$$$$$ | +// $$ \$$$ $$ |$$ __$$ | $$ | $$ __$$ | +// $$ |\$ /$$ |$$ | $$ | $$ | $$ | $$ | +// $$ | \_/ $$ |$$ | $$ | $$ | $$ | $$ | +// \__| \__|\__| \__| \__| \__| \__| +// +// dqn_math.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + #if !defined(DQN_NO_V2) -// NOTE: [$VEC2] Vector2 =========================================================================== +// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// // NOTE: Dqn_V2I DQN_API bool operator!=(Dqn_V2I lhs, Dqn_V2I rhs) { @@ -139,6 +154,24 @@ DQN_API Dqn_V2I &operator+=(Dqn_V2I &lhs, Dqn_V2I rhs) return lhs; } +DQN_API Dqn_V2I Dqn_V2I_Min(Dqn_V2I a, Dqn_V2I b) +{ + Dqn_V2I result = Dqn_V2I_InitNx2(DQN_MIN(a.x, b.x), DQN_MIN(a.y, b.y)); + return result; +} + +DQN_API Dqn_V2I Dqn_V2I_Max(Dqn_V2I a, Dqn_V2I b) +{ + Dqn_V2I result = Dqn_V2I_InitNx2(DQN_MAX(a.x, b.x), DQN_MAX(a.y, b.y)); + return result; +} + +DQN_API Dqn_V2I Dqn_V2I_Abs(Dqn_V2I a) +{ + Dqn_V2I result = Dqn_V2I_InitNx2(DQN_ABS(a.x), DQN_ABS(a.y)); + return result; +} + // NOTE: Dqn_V2U16 DQN_API bool operator!=(Dqn_V2U16 lhs, Dqn_V2U16 rhs) { @@ -309,42 +342,75 @@ DQN_API bool operator>(Dqn_V2 lhs, Dqn_V2 rhs) return result; } +// NOTE: Dqn_V2 operator- ////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_V2 operator-(Dqn_V2 lhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(-lhs.x, -lhs.y); + return result; +} + DQN_API Dqn_V2 operator-(Dqn_V2 lhs, Dqn_V2 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x - rhs.x, lhs.y - rhs.y); return result; } +DQN_API Dqn_V2 operator-(Dqn_V2 lhs, Dqn_V2I rhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + DQN_API Dqn_V2 operator-(Dqn_V2 lhs, Dqn_f32 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x - rhs, lhs.y - rhs); return result; } -DQN_API Dqn_V2 operator-(Dqn_V2 lhs) +DQN_API Dqn_V2 operator-(Dqn_V2 lhs, int32_t rhs) { - Dqn_V2 result = Dqn_V2_InitNx2(-lhs.x, -lhs.y); + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x - rhs, lhs.y - rhs); return result; } +// NOTE: Dqn_V2 operator+ ////////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 operator+(Dqn_V2 lhs, Dqn_V2 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x + rhs.x, lhs.y + rhs.y); return result; } +DQN_API Dqn_V2 operator+(Dqn_V2 lhs, Dqn_V2I rhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + DQN_API Dqn_V2 operator+(Dqn_V2 lhs, Dqn_f32 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x + rhs, lhs.y + rhs); return result; } +DQN_API Dqn_V2 operator+(Dqn_V2 lhs, int32_t rhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x + rhs, lhs.y + rhs); + return result; +} + +// NOTE: Dqn_V2 operator* ////////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 operator*(Dqn_V2 lhs, Dqn_V2 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x * rhs.x, lhs.y * rhs.y); return result; } +DQN_API Dqn_V2 operator*(Dqn_V2 lhs, Dqn_V2I rhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + DQN_API Dqn_V2 operator*(Dqn_V2 lhs, Dqn_f32 rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x * rhs, lhs.y * rhs); @@ -357,7 +423,14 @@ DQN_API Dqn_V2 operator*(Dqn_V2 lhs, int32_t rhs) return result; } -DQN_API Dqn_V2 operator/(Dqn_V2 lhs, Dqn_V2 rhs) +// NOTE: Dqn_V2 operator/ ////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_V2 operator/(Dqn_V2 lhs, Dqn_V2 rhs) +{ + Dqn_V2 result = Dqn_V2_InitNx2(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DQN_API Dqn_V2 operator/(Dqn_V2 lhs, Dqn_V2I rhs) { Dqn_V2 result = Dqn_V2_InitNx2(lhs.x / rhs.x, lhs.y / rhs.y); return result; @@ -375,12 +448,19 @@ DQN_API Dqn_V2 operator/(Dqn_V2 lhs, int32_t rhs) return result; } +// NOTE: Dqn_V2 operator*/ ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 &operator*=(Dqn_V2 &lhs, Dqn_V2 rhs) { lhs = lhs * rhs; return lhs; } +DQN_API Dqn_V2 &operator*=(Dqn_V2 &lhs, Dqn_V2I rhs) +{ + lhs = lhs * rhs; + return lhs; +} + DQN_API Dqn_V2 &operator*=(Dqn_V2 &lhs, Dqn_f32 rhs) { lhs = lhs * rhs; @@ -393,12 +473,19 @@ DQN_API Dqn_V2 &operator*=(Dqn_V2 &lhs, int32_t rhs) return lhs; } +// NOTE: Dqn_V2 operator// ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 &operator/=(Dqn_V2 &lhs, Dqn_V2 rhs) { lhs = lhs / rhs; return lhs; } +DQN_API Dqn_V2 &operator/=(Dqn_V2 &lhs, Dqn_V2I rhs) +{ + lhs = lhs / rhs; + return lhs; +} + DQN_API Dqn_V2 &operator/=(Dqn_V2 &lhs, Dqn_f32 rhs) { lhs = lhs / rhs; @@ -411,30 +498,56 @@ DQN_API Dqn_V2 &operator/=(Dqn_V2 &lhs, int32_t rhs) return lhs; } +// NOTE: Dqn_V2 operator-/ ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 &operator-=(Dqn_V2 &lhs, Dqn_V2 rhs) { lhs = lhs - rhs; return lhs; } +DQN_API Dqn_V2 &operator-=(Dqn_V2 &lhs, Dqn_V2I rhs) +{ + lhs = lhs - rhs; + return lhs; +} + DQN_API Dqn_V2 &operator-=(Dqn_V2 &lhs, Dqn_f32 rhs) { lhs = lhs - rhs; return lhs; } +DQN_API Dqn_V2 &operator-=(Dqn_V2 &lhs, int32_t rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +// NOTE: Dqn_V2 operator+/ ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 &operator+=(Dqn_V2 &lhs, Dqn_V2 rhs) { lhs = lhs + rhs; return lhs; } +DQN_API Dqn_V2 &operator+=(Dqn_V2 &lhs, Dqn_V2I rhs) +{ + lhs = lhs + rhs; + return lhs; +} + DQN_API Dqn_V2 &operator+=(Dqn_V2 &lhs, Dqn_f32 rhs) { lhs = lhs + rhs; return lhs; } +DQN_API Dqn_V2 &operator+=(Dqn_V2 &lhs, int32_t rhs) +{ + lhs = lhs + rhs; + return lhs; +} + DQN_API Dqn_V2 Dqn_V2_Min(Dqn_V2 a, Dqn_V2 b) { Dqn_V2 result = Dqn_V2_InitNx2(DQN_MIN(a.x, b.x), DQN_MIN(a.y, b.y)); @@ -455,7 +568,7 @@ DQN_API Dqn_V2 Dqn_V2_Abs(Dqn_V2 a) DQN_API Dqn_f32 Dqn_V2_Dot(Dqn_V2 a, Dqn_V2 b) { - // NOTE: Scalar projection of B onto A ========================================================= + // NOTE: Scalar projection of B onto A ///////////////////////////////////////////////////////// // // Scalar projection calculates the signed distance between `b` and `a` // where `a` is a unit vector then, the dot product calculates the projection @@ -488,7 +601,7 @@ DQN_API Dqn_f32 Dqn_V2_Dot(Dqn_V2 a, Dqn_V2 b) // 2 objects. One of the vectors must be normalised (e.g. turned into a unit // vector). // - // NOTE: Vector projection ===================================================================== + // NOTE: Vector projection ///////////////////////////////////////////////////////////////////// // // Vector projection calculates the exact X,Y coordinates of where `b` meets // `a` when it was projected. This is calculated by multipying the @@ -578,7 +691,7 @@ DQN_API Dqn_f32 Dqn_V2_Area(Dqn_V2 a) #endif // !defined(DQN_NO_V2) #if !defined(DQN_NO_V3) -// NOTE: [$VEC3] Vector3 =========================================================================== +// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// DQN_API bool operator!=(Dqn_V3 lhs, Dqn_V3 rhs) { bool result = !(lhs == rhs); @@ -739,7 +852,7 @@ DQN_API Dqn_V3 Dqn_V3_Normalise(Dqn_V3 a) #endif // !defined(DQN_NO_V3) #if !defined(DQN_NO_V4) -// NOTE: [$VEC4] Vector4 =========================================================================== +// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// DQN_API bool operator!=(Dqn_V4 lhs, Dqn_V4 rhs) { bool result = !(lhs == rhs); @@ -856,7 +969,7 @@ DQN_API Dqn_f32 Dqn_V4Dot(Dqn_V4 a, Dqn_V4 b) #endif // !defined(DQN_NO_V4) #if !defined(DQN_NO_M4) -// NOTE: [$MAT4] Dqn_M4 ============================================================================ +// NOTE: [$MAT4] Dqn_M4 //////////////////////////////////////////////////////////////////////////// DQN_API Dqn_M4 Dqn_M4_Identity() { Dqn_M4 result = @@ -924,7 +1037,7 @@ DQN_API Dqn_M4 Dqn_M4_Translate(Dqn_V3 xyz) DQN_API Dqn_M4 Dqn_M4_Transpose(Dqn_M4 mat) { - Dqn_M4 result; + 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]; @@ -1106,7 +1219,7 @@ DQN_API Dqn_FStr8<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat) #endif #endif // !defined(DQN_M4) -// NOTE: [$M2x3] Dqn_M2x3 ========================================================================== +// NOTE: [$M2x3] Dqn_M2x3 ////////////////////////////////////////////////////////////////////////// DQN_API bool operator==(Dqn_M2x3 const &lhs, Dqn_M2x3 const &rhs) { bool result = DQN_MEMCMP(lhs.e, rhs.e, sizeof(lhs.e[0]) * DQN_ARRAY_UCOUNT(lhs.e)) == 0; @@ -1178,7 +1291,7 @@ DQN_API Dqn_M2x3 Dqn_M2x3_Mul(Dqn_M2x3 m1, Dqn_M2x3 m2) return result; } -DQN_API Dqn_V2 Dqn_M2x3_MulV2(Dqn_M2x3 m1, Dqn_V2 v2) +DQN_API Dqn_V2 Dqn_M2x3_Mul2F32(Dqn_M2x3 m1, Dqn_f32 x, Dqn_f32 y) { // NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3) // != row count (2). We pretend we have a V3 with `z` set to `1`. @@ -1188,14 +1301,20 @@ DQN_API Dqn_V2 Dqn_M2x3_MulV2(Dqn_M2x3 m1, Dqn_V2 v2) // | 1 | Dqn_V2 result = {{ - m1.e[0]*v2.x + m1.e[1]*v2.y + m1.e[2], // a*x + b*y + c*1 - m1.e[3]*v2.x + m1.e[4]*v2.y + m1.e[5], // d*x + e*y + f*1 + m1.e[0]*x + m1.e[1]*y + m1.e[2], // a*x + b*y + c*1 + m1.e[3]*x + m1.e[4]*y + m1.e[5], // d*x + e*y + f*1 }}; return result; } +DQN_API Dqn_V2 Dqn_M2x3_MulV2(Dqn_M2x3 m1, Dqn_V2 v2) +{ + Dqn_V2 result = Dqn_M2x3_Mul2F32(m1, v2.x, v2.y); + return result; +} + #if !defined(DQN_NO_RECT) -// NOTE: [$RECT] Dqn_Rect ========================================================================== +// NOTE: [$RECT] Dqn_Rect ////////////////////////////////////////////////////////////////////////// DQN_API bool operator==(const Dqn_Rect& lhs, const Dqn_Rect& rhs) { bool result = (lhs.pos == rhs.pos) && (lhs.size == rhs.size); @@ -1399,7 +1518,7 @@ DQN_API Dqn_V2 Dqn_Rect_BottomRight(Dqn_Rect rect) } #endif // !defined(DQN_NO_RECT) -// NOTE: [$MATH] Raycast =========================================================================== +// NOTE: [$MATH] Raycast /////////////////////////////////////////////////////////////////////////// DQN_API Dqn_RaycastLineIntersectV2Result Dqn_Raycast_LineIntersectV2(Dqn_V2 origin_a, Dqn_V2 dir_a, Dqn_V2 origin_b, Dqn_V2 dir_b) { @@ -1432,7 +1551,7 @@ DQN_API Dqn_RaycastLineIntersectV2Result Dqn_Raycast_LineIntersectV2(Dqn_V2 orig return result; } -// NOTE: [$MATH] Other ============================================================================= +// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// DQN_API Dqn_V2 Dqn_Lerp_V2(Dqn_V2 a, Dqn_f32 t, Dqn_V2 b) { Dqn_V2 result = {}; diff --git a/dqn_math.h b/dqn_math.h index 61a5a95..0978dcf 100644 --- a/dqn_math.h +++ b/dqn_math.h @@ -1,8 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$\ $$$$$$$$\ $$\ $$\ +// $$$\ $$$ |$$ __$$\\__$$ __|$$ | $$ | +// $$$$\ $$$$ |$$ / $$ | $$ | $$ | $$ | +// $$\$$\$$ $$ |$$$$$$$$ | $$ | $$$$$$$$ | +// $$ \$$$ $$ |$$ __$$ | $$ | $$ __$$ | +// $$ |\$ /$$ |$$ | $$ | $$ | $$ | $$ | +// $$ | \_/ $$ |$$ | $$ | $$ | $$ | $$ | +// \__| \__|\__| \__| \__| \__| \__| +// +// dqn_math.h -- Basic math functions +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$VEC2] Dqn_V2, V2i -- DQN_V2 +// [$VEC3] Dqn_V3, V3i -- DQN_V3 +// [$VEC4] Dqn_V4, V4i -- DQN_V4 +// [$MAT4] Dqn_M4 -- DQN_M4 +// [$M2x3] Dqn_M2x3 -- +// [$RECT] Dqn_Rect -- DQN_RECT +// [$MATH] Other -- +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + DQN_MSVC_WARNING_PUSH DQN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union - #if !defined(DQN_NO_V2) -// NOTE: [$VEC2] Vector2 =========================================================================== +// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// union Dqn_V2I { struct { int32_t x, y; }; @@ -10,34 +34,6 @@ union Dqn_V2I int32_t data[2]; }; -#define Dqn_V2I_InitNx1(x) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(x)}} -#define Dqn_V2I_InitNx2(x, y) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(y)}} -#define Dqn_V2I_InitV2(xy) DQN_LITERAL(Dqn_V2I){{(int32_t)(xy).x, (int32_t)(xy).y}} - -DQN_API bool operator!=(Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API bool operator==(Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API bool operator>=(Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API bool operator<=(Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API bool operator< (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API bool operator> (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I operator- (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I operator- (Dqn_V2I lhs); -DQN_API Dqn_V2I operator+ (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I operator* (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I operator* (Dqn_V2I lhs, Dqn_f32 rhs); -DQN_API Dqn_V2I operator* (Dqn_V2I lhs, int32_t rhs); -DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, Dqn_f32 rhs); -DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, int32_t rhs); -DQN_API Dqn_V2I &operator*=(Dqn_V2I& lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I &operator*=(Dqn_V2I& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2I &operator*=(Dqn_V2I& lhs, int32_t rhs); -DQN_API Dqn_V2I &operator/=(Dqn_V2I& lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I &operator/=(Dqn_V2I& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2I &operator/=(Dqn_V2I& lhs, int32_t rhs); -DQN_API Dqn_V2I &operator-=(Dqn_V2I& lhs, Dqn_V2I rhs); -DQN_API Dqn_V2I &operator+=(Dqn_V2I& lhs, Dqn_V2I rhs); - union Dqn_V2U16 { struct { uint16_t x, y; }; @@ -45,90 +41,16 @@ union Dqn_V2U16 uint16_t data[2]; }; -#define Dqn_V2U16_InitNx1(x) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(x)}} -#define Dqn_V2U16_InitNx2(x, y) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(y)}} - -DQN_API bool operator!=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API bool operator==(Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API bool operator>=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API bool operator<=(Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API bool operator< (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API bool operator> (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 operator- (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 operator+ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, int32_t rhs); -DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, int32_t rhs); -DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2U16 &operator*=(Dqn_V2U16& lhs, int32_t rhs); -DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2U16 &operator/=(Dqn_V2U16& lhs, int32_t rhs); -DQN_API Dqn_V2U16 &operator-=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); -DQN_API Dqn_V2U16 &operator+=(Dqn_V2U16& lhs, Dqn_V2U16 rhs); - union Dqn_V2 { struct { Dqn_f32 x, y; }; struct { Dqn_f32 w, h; }; Dqn_f32 data[2]; }; - -#define Dqn_V2_Zero DQN_LITERAL(Dqn_V2){{(Dqn_f32)(0), (Dqn_f32)(0)}} -#define Dqn_V2_One DQN_LITERAL(Dqn_V2){{(Dqn_f32)(1), (Dqn_f32)(1)}} -#define Dqn_V2_InitNx1(x) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(x)}} -#define Dqn_V2_InitNx2(x, y) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(y)}} -#define Dqn_V2_InitV2I(xy) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(xy).x, (Dqn_f32)(xy).y}} - -DQN_API bool operator!=(Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API bool operator==(Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API bool operator>=(Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API bool operator<=(Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API bool operator< (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API bool operator> (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 operator- (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 operator- (Dqn_V2 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 operator- (Dqn_V2 lhs); -DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 operator* (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 operator* (Dqn_V2 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 operator* (Dqn_V2 lhs, int32_t rhs); -DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, int32_t rhs); -DQN_API Dqn_V2 &operator*=(Dqn_V2& lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 &operator*=(Dqn_V2& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 &operator*=(Dqn_V2& lhs, int32_t rhs); -DQN_API Dqn_V2 &operator/=(Dqn_V2& lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 &operator/=(Dqn_V2& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 &operator/=(Dqn_V2& lhs, int32_t rhs); -DQN_API Dqn_V2 &operator-=(Dqn_V2& lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 &operator-=(Dqn_V2& lhs, Dqn_f32 rhs); -DQN_API Dqn_V2 &operator+=(Dqn_V2& lhs, Dqn_V2 rhs); -DQN_API Dqn_V2 &operator+=(Dqn_V2& lhs, Dqn_f32 rhs); - -DQN_API Dqn_V2I Dqn_V2_ToV2I (Dqn_V2 a); -DQN_API Dqn_V2 Dqn_V2_Min (Dqn_V2 a, Dqn_V2 b); -DQN_API Dqn_V2 Dqn_V2_Max (Dqn_V2 a, Dqn_V2 b); -DQN_API Dqn_V2 Dqn_V2_Abs (Dqn_V2 a); -DQN_API Dqn_f32 Dqn_V2_Dot (Dqn_V2 a, Dqn_V2 b); -DQN_API Dqn_f32 Dqn_V2_LengthSq_V2x2(Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_f32 Dqn_V2_Length_V2x2 (Dqn_V2 lhs, Dqn_V2 rhs); -DQN_API Dqn_f32 Dqn_V2_LengthSq (Dqn_V2 lhs); -DQN_API Dqn_f32 Dqn_V2_Length (Dqn_V2 lhs); -DQN_API Dqn_V2 Dqn_V2_Normalise (Dqn_V2 a); -DQN_API Dqn_V2 Dqn_V2_Perpendicular(Dqn_V2 a); -DQN_API Dqn_V2 Dqn_V2_Reflect (Dqn_V2 in, Dqn_V2 surface); -DQN_API Dqn_f32 Dqn_V2_Area (Dqn_V2 a); #endif // !defined(DQN_NO_V2) #if !defined(DQN_NO_V3) -// NOTE: [$VEC3] Vector3 =========================================================================== +// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// union Dqn_V3 { struct { Dqn_f32 x, y, z; }; @@ -136,41 +58,10 @@ union Dqn_V3 Dqn_f32 data[3]; }; -#define Dqn_V3_InitNx1(x) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} -#define Dqn_V3_InitNx3(x, y, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z)}} -#define Dqn_V3_InitV2x1_Nx1(xy, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(xy.x), (Dqn_f32)(xy.y), (Dqn_f32)(z)}} - -DQN_API bool operator!=(Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API bool operator==(Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API bool operator>=(Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API bool operator<=(Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API bool operator< (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API bool operator> (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 operator- (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 operator- (Dqn_V3 lhs); -DQN_API Dqn_V3 operator+ (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 operator* (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 operator* (Dqn_V3 lhs, Dqn_f32 rhs); -DQN_API Dqn_V3 operator* (Dqn_V3 lhs, int32_t rhs); -DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, Dqn_f32 rhs); -DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, int32_t rhs); -DQN_API Dqn_V3 &operator*=(Dqn_V3 &lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 &operator*=(Dqn_V3 &lhs, Dqn_f32 rhs); -DQN_API Dqn_V3 &operator*=(Dqn_V3 &lhs, int32_t rhs); -DQN_API Dqn_V3 &operator/=(Dqn_V3 &lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 &operator/=(Dqn_V3 &lhs, Dqn_f32 rhs); -DQN_API Dqn_V3 &operator/=(Dqn_V3 &lhs, int32_t rhs); -DQN_API Dqn_V3 &operator-=(Dqn_V3 &lhs, Dqn_V3 rhs); -DQN_API Dqn_V3 &operator+=(Dqn_V3 &lhs, Dqn_V3 rhs); - -DQN_API Dqn_f32 Dqn_V3_LengthSq(Dqn_V3 a); -DQN_API Dqn_f32 Dqn_V3_Length(Dqn_V3 a); -DQN_API Dqn_V3 Dqn_V3_Normalise(Dqn_V3 a); #endif // !defined(DQN_NO_V3) #if !defined(DQN_NO_V4) -// NOTE: [$VEC4] Vector4 =========================================================================== +// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// union Dqn_V4 { struct { Dqn_f32 x, y, z, w; }; @@ -181,85 +72,29 @@ union Dqn_V4 #endif Dqn_f32 data[4]; }; - -#define Dqn_V4_InitNx1(x) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} -#define Dqn_V4_InitNx4(x, y, z, w) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z), (Dqn_f32)(w)}} -#define Dqn_V4_Init_V3x1_Nx1(xyz, w) DQN_LITERAL(Dqn_V4){{xyz.x, xyz.y, xyz.z, w}} - -DQN_API bool operator!=(Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API bool operator==(Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API bool operator>=(Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API bool operator<=(Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API bool operator< (Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API bool operator> (Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 operator- (Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 operator- (Dqn_V4 lhs); -DQN_API Dqn_V4 operator+ (Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 operator* (Dqn_V4 lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 operator* (Dqn_V4 lhs, Dqn_f32 rhs); -DQN_API Dqn_V4 operator* (Dqn_V4 lhs, int32_t rhs); -DQN_API Dqn_V4 operator/ (Dqn_V4 lhs, Dqn_f32 rhs); -DQN_API Dqn_V4 &operator*=(Dqn_V4 &lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 &operator*=(Dqn_V4 &lhs, Dqn_f32 rhs); -DQN_API Dqn_V4 &operator*=(Dqn_V4 &lhs, int32_t rhs); -DQN_API Dqn_V4 &operator-=(Dqn_V4 &lhs, Dqn_V4 rhs); -DQN_API Dqn_V4 &operator+=(Dqn_V4 &lhs, Dqn_V4 rhs); #endif // !defined(DQN_NO_V4) +DQN_MSVC_WARNING_POP #if !defined(DQN_NO_M4) -// NOTE: [$MAT4] Dqn_M4 ============================================================================ -// NOTE: Column major matrix +// NOTE: [$MAT4] Dqn_M4 //////////////////////////////////////////////////////////////////////////// struct Dqn_M4 { - Dqn_f32 columns[4][4]; + Dqn_f32 columns[4][4]; // Column major matrix }; - -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_FSTR8) -DQN_API Dqn_FStr8<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat); -#endif #endif // !defined(DQN_M4) +// NOTE: [$M2x3] Dqn_M2x3 ////////////////////////////////////////////////////////////////////////// union Dqn_M2x3 { Dqn_f32 e[6]; Dqn_f32 row[2][3]; }; -DQN_API bool operator==(Dqn_M2x3 const &lhs, Dqn_M2x3 const &rhs); -DQN_API bool operator!=(Dqn_M2x3 const &lhs, Dqn_M2x3 const &rhs); -DQN_API Dqn_M2x3 Dqn_M2x3_Identity (); -DQN_API Dqn_M2x3 Dqn_M2x3_Translate(Dqn_V2 offset); -DQN_API Dqn_M2x3 Dqn_M2x3_Scale (Dqn_V2 scale); -DQN_API Dqn_M2x3 Dqn_M2x3_Rotate (Dqn_f32 radians); -DQN_API Dqn_M2x3 Dqn_M2x3_Mul (Dqn_M2x3 m1, Dqn_M2x3 m2); -DQN_API Dqn_V2 Dqn_M2x3_MulV2 (Dqn_M2x3 m1, Dqn_V2 v2); - -// NOTE: [$RECT] Dqn_Rect ========================================================================== +// NOTE: [$RECT] Dqn_Rect ////////////////////////////////////////////////////////////////////////// #if !defined(DQN_NO_RECT) #if defined(DQN_NO_V2) #error "Rectangles requires V2, DQN_NO_V2 must not be defined" #endif - struct Dqn_Rect { Dqn_V2 pos, size; @@ -270,47 +105,12 @@ struct Dqn_RectMinMax Dqn_V2 min, max; }; -#define Dqn_Rect_InitV2x2(pos, size) DQN_LITERAL(Dqn_Rect){(pos), (size)} -#define Dqn_Rect_InitNx4(pos_x, pos_y, size_w, size_h) DQN_LITERAL(Dqn_Rect){DQN_LITERAL(Dqn_V2){{pos_x, pos_y}}, DQN_LITERAL(Dqn_V2){{size_w, size_h}}} - -DQN_API bool operator== (const Dqn_Rect& lhs, const Dqn_Rect& rhs); -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_Rect Dqn_Rect_Expand (Dqn_Rect a, Dqn_f32 amount); -DQN_API Dqn_Rect Dqn_Rect_ExpandV2 (Dqn_Rect a, Dqn_V2 amount); -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_RectMinMax Dqn_Rect_MinMax (Dqn_Rect a); -DQN_API Dqn_f32 Dqn_Rect_Area (Dqn_Rect a); -DQN_API Dqn_V2 Dqn_Rect_InterpolatedPoint(Dqn_Rect rect, Dqn_V2 t01); -DQN_API Dqn_V2 Dqn_Rect_TopLeft (Dqn_Rect rect); -DQN_API Dqn_V2 Dqn_Rect_TopRight (Dqn_Rect rect); -DQN_API Dqn_V2 Dqn_Rect_BottomLeft (Dqn_Rect rect); -DQN_API Dqn_V2 Dqn_Rect_BottomRight (Dqn_Rect rect); - enum Dqn_RectCutClip { Dqn_RectCutClip_No, Dqn_RectCutClip_Yes, }; -DQN_API Dqn_Rect Dqn_Rect_CutLeftClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); -DQN_API Dqn_Rect Dqn_Rect_CutRightClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); -DQN_API Dqn_Rect Dqn_Rect_CutTopClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); -DQN_API Dqn_Rect Dqn_Rect_CutBottomClip(Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); - -#define Dqn_Rect_CutLeft(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_Yes) -#define Dqn_Rect_CutRight(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_Yes) -#define Dqn_Rect_CutTop(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_Yes) -#define Dqn_Rect_CutBottom(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_Yes) - -#define Dqn_Rect_CutLeftNoClip(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_No) -#define Dqn_Rect_CutRightNoClip(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_No) -#define Dqn_Rect_CutTopNoClip(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_No) -#define Dqn_Rect_CutBottomNoClip(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_No) - enum Dqn_RectCutSide { Dqn_RectCutSide_Left, @@ -324,27 +124,10 @@ struct Dqn_RectCut Dqn_Rect* rect; Dqn_RectCutSide side; }; - -#define Dqn_RectCut_Init(rect, side) DQN_LITERAL(Dqn_RectCut){rect, side} -#define Dqn_RectCut_Left(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Left} -#define Dqn_RectCut_Right(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Right} -#define Dqn_RectCut_Top(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Top} -#define Dqn_RectCut_Bottom(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Bottom} - -DQN_API Dqn_Rect Dqn_RectCut_Cut(Dqn_RectCut rect_cut, Dqn_V2 size, Dqn_RectCutClip clip); #endif // !defined(DQN_NO_RECT) -// NOTE: [$MATH] Raycast =========================================================================== -// +// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// // NOTE: API -// @proc Dqn_Raycast_LineIntersectV2 -// @desc Calculate the intersection point of 2 rays returning a `t` value -// which is how much along the direction of the 'ray' did the intersection -// occur. -// -// The arguments passed in do not need to be normalised for the function to -// work. - struct Dqn_RaycastLineIntersectV2Result { bool hit; // True if there was an intersection, false if the lines are parallel @@ -352,9 +135,274 @@ struct Dqn_RaycastLineIntersectV2Result Dqn_f32 t_b; // Distance along `dir_b` that the intersection occurred, e.g. `origin_b + (dir_b * t_b)` }; -DQN_API Dqn_RaycastLineIntersectV2Result Dqn_Raycast_LineIntersectV2(Dqn_V2 origin_a, Dqn_V2 dir_a, Dqn_V2 origin_b, Dqn_V2 dir_b); +#if !defined(DQN_NO_V2) +// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// +#define Dqn_V2I_Zero DQN_LITERAL(Dqn_V2I){{(int32_t)(0), (int32_t)(0)}} +#define Dqn_V2I_One DQN_LITERAL(Dqn_V2I){{(int32_t)(1), (int32_t)(1)}} +#define Dqn_V2I_InitNx1(x) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(x)}} +#define Dqn_V2I_InitNx2(x, y) DQN_LITERAL(Dqn_V2I){{(int32_t)(x), (int32_t)(y)}} +#define Dqn_V2I_InitV2(xy) DQN_LITERAL(Dqn_V2I){{(int32_t)(xy).x, (int32_t)(xy).y}} -// NOTE: [$MATH] Other ============================================================================= -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); -DQN_MSVC_WARNING_POP +DQN_API bool operator!= (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API bool operator== (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API bool operator>= (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API bool operator<= (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API bool operator< (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API bool operator> (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I operator- (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I operator- (Dqn_V2I lhs); +DQN_API Dqn_V2I operator+ (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I operator* (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I operator* (Dqn_V2I lhs, Dqn_f32 rhs); +DQN_API Dqn_V2I operator* (Dqn_V2I lhs, int32_t rhs); +DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, Dqn_f32 rhs); +DQN_API Dqn_V2I operator/ (Dqn_V2I lhs, int32_t rhs); +DQN_API Dqn_V2I & operator*= (Dqn_V2I& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I & operator*= (Dqn_V2I& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2I & operator*= (Dqn_V2I& lhs, int32_t rhs); +DQN_API Dqn_V2I & operator/= (Dqn_V2I& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I & operator/= (Dqn_V2I& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2I & operator/= (Dqn_V2I& lhs, int32_t rhs); +DQN_API Dqn_V2I & operator-= (Dqn_V2I& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2I & operator+= (Dqn_V2I& lhs, Dqn_V2I rhs); + +DQN_API Dqn_V2I Dqn_V2I_Min (Dqn_V2I a, Dqn_V2I b); +DQN_API Dqn_V2I Dqn_V2I_Max (Dqn_V2I a, Dqn_V2I b); +DQN_API Dqn_V2I Dqn_V2I_Abs (Dqn_V2I a); + +#define Dqn_V2U16_Zero DQN_LITERAL(Dqn_V2U16){{(uint16_t)(0), (uint16_t)(0)}} +#define Dqn_V2U16_One DQN_LITERAL(Dqn_V2U16){{(uint16_t)(1), (uint16_t)(1)}} +#define Dqn_V2U16_InitNx1(x) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(x)}} +#define Dqn_V2U16_InitNx2(x, y) DQN_LITERAL(Dqn_V2U16){{(uint16_t)(x), (uint16_t)(y)}} + +DQN_API bool operator!= (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator== (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator>= (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator<= (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator< (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API bool operator> (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator- (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator+ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 operator* (Dqn_V2U16 lhs, int32_t rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 operator/ (Dqn_V2U16 lhs, int32_t rhs); +DQN_API Dqn_V2U16 & operator*= (Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 & operator*= (Dqn_V2U16& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 & operator*= (Dqn_V2U16& lhs, int32_t rhs); +DQN_API Dqn_V2U16 & operator/= (Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 & operator/= (Dqn_V2U16& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2U16 & operator/= (Dqn_V2U16& lhs, int32_t rhs); +DQN_API Dqn_V2U16 & operator-= (Dqn_V2U16& lhs, Dqn_V2U16 rhs); +DQN_API Dqn_V2U16 & operator+= (Dqn_V2U16& lhs, Dqn_V2U16 rhs); + +#define Dqn_V2_Zero DQN_LITERAL(Dqn_V2){{(Dqn_f32)(0), (Dqn_f32)(0)}} +#define Dqn_V2_One DQN_LITERAL(Dqn_V2){{(Dqn_f32)(1), (Dqn_f32)(1)}} +#define Dqn_V2_InitNx1(x) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V2_InitNx2(x, y) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(x), (Dqn_f32)(y)}} +#define Dqn_V2_InitV2I(xy) DQN_LITERAL(Dqn_V2){{(Dqn_f32)(xy).x, (Dqn_f32)(xy).y}} + +DQN_API bool operator!= (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API bool operator== (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API bool operator>= (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API bool operator<= (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API bool operator< (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API bool operator> (Dqn_V2 lhs, Dqn_V2 rhs); + +DQN_API Dqn_V2 operator- (Dqn_V2 lhs); +DQN_API Dqn_V2 operator- (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 operator- (Dqn_V2 lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 operator- (Dqn_V2 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 operator- (Dqn_V2 lhs, int32_t rhs); + +DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 operator+ (Dqn_V2 lhs, int32_t rhs); + +DQN_API Dqn_V2 operator* (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 operator* (Dqn_V2 lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 operator* (Dqn_V2 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 operator* (Dqn_V2 lhs, int32_t rhs); + +DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 operator/ (Dqn_V2 lhs, int32_t rhs); + +DQN_API Dqn_V2 & operator*= (Dqn_V2& lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 & operator*= (Dqn_V2& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 & operator*= (Dqn_V2& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 & operator*= (Dqn_V2& lhs, int32_t rhs); + +DQN_API Dqn_V2 & operator/= (Dqn_V2& lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 & operator/= (Dqn_V2& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 & operator/= (Dqn_V2& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 & operator/= (Dqn_V2& lhs, int32_t rhs); + +DQN_API Dqn_V2 & operator-= (Dqn_V2& lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 & operator-= (Dqn_V2& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 & operator-= (Dqn_V2& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 & operator-= (Dqn_V2& lhs, int32_t rhs); + +DQN_API Dqn_V2 & operator+= (Dqn_V2& lhs, Dqn_V2 rhs); +DQN_API Dqn_V2 & operator+= (Dqn_V2& lhs, Dqn_V2I rhs); +DQN_API Dqn_V2 & operator+= (Dqn_V2& lhs, Dqn_f32 rhs); +DQN_API Dqn_V2 & operator+= (Dqn_V2& lhs, int32_t rhs); + +DQN_API Dqn_V2 Dqn_V2_Min (Dqn_V2 a, Dqn_V2 b); +DQN_API Dqn_V2 Dqn_V2_Max (Dqn_V2 a, Dqn_V2 b); +DQN_API Dqn_V2 Dqn_V2_Abs (Dqn_V2 a); +DQN_API Dqn_f32 Dqn_V2_Dot (Dqn_V2 a, Dqn_V2 b); +DQN_API Dqn_f32 Dqn_V2_LengthSq_V2x2 (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_f32 Dqn_V2_Length_V2x2 (Dqn_V2 lhs, Dqn_V2 rhs); +DQN_API Dqn_f32 Dqn_V2_LengthSq (Dqn_V2 lhs); +DQN_API Dqn_f32 Dqn_V2_Length (Dqn_V2 lhs); +DQN_API Dqn_V2 Dqn_V2_Normalise (Dqn_V2 a); +DQN_API Dqn_V2 Dqn_V2_Perpendicular (Dqn_V2 a); +DQN_API Dqn_V2 Dqn_V2_Reflect (Dqn_V2 in, Dqn_V2 surface); +DQN_API Dqn_f32 Dqn_V2_Area (Dqn_V2 a); +#endif // !defined(DQN_NO_V2) +#if !defined(DQN_NO_V3) +// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// +#define Dqn_V3_InitNx1(x) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V3_InitNx3(x, y, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z)}} +#define Dqn_V3_InitV2x1_Nx1(xy, z) DQN_LITERAL(Dqn_V3){{(Dqn_f32)(xy.x), (Dqn_f32)(xy.y), (Dqn_f32)(z)}} + +DQN_API bool operator!= (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API bool operator== (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API bool operator>= (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API bool operator<= (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API bool operator< (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API bool operator> (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 operator- (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 operator- (Dqn_V3 lhs); +DQN_API Dqn_V3 operator+ (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 operator* (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 operator* (Dqn_V3 lhs, Dqn_f32 rhs); +DQN_API Dqn_V3 operator* (Dqn_V3 lhs, int32_t rhs); +DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, Dqn_f32 rhs); +DQN_API Dqn_V3 operator/ (Dqn_V3 lhs, int32_t rhs); +DQN_API Dqn_V3 & operator*= (Dqn_V3 &lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 & operator*= (Dqn_V3 &lhs, Dqn_f32 rhs); +DQN_API Dqn_V3 & operator*= (Dqn_V3 &lhs, int32_t rhs); +DQN_API Dqn_V3 & operator/= (Dqn_V3 &lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 & operator/= (Dqn_V3 &lhs, Dqn_f32 rhs); +DQN_API Dqn_V3 & operator/= (Dqn_V3 &lhs, int32_t rhs); +DQN_API Dqn_V3 & operator-= (Dqn_V3 &lhs, Dqn_V3 rhs); +DQN_API Dqn_V3 & operator+= (Dqn_V3 &lhs, Dqn_V3 rhs); +DQN_API Dqn_f32 Dqn_V3_LengthSq (Dqn_V3 a); +DQN_API Dqn_f32 Dqn_V3_Length (Dqn_V3 a); +DQN_API Dqn_V3 Dqn_V3_Normalise (Dqn_V3 a); +#endif // !defined(DQN_NO_V3) +#if !defined(DQN_NO_V4) +// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// +#define Dqn_V4_InitNx1(x) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x), (Dqn_f32)(x)}} +#define Dqn_V4_InitNx4(x, y, z, w) DQN_LITERAL(Dqn_V4){{(Dqn_f32)(x), (Dqn_f32)(y), (Dqn_f32)(z), (Dqn_f32)(w)}} +#define Dqn_V4_Init_V3x1_Nx1(xyz, w) DQN_LITERAL(Dqn_V4){{xyz.x, xyz.y, xyz.z, w}} +DQN_API bool operator!= (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API bool operator== (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API bool operator>= (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API bool operator<= (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API bool operator< (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API bool operator> (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 operator- (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 operator- (Dqn_V4 lhs); +DQN_API Dqn_V4 operator+ (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 operator* (Dqn_V4 lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 operator* (Dqn_V4 lhs, Dqn_f32 rhs); +DQN_API Dqn_V4 operator* (Dqn_V4 lhs, int32_t rhs); +DQN_API Dqn_V4 operator/ (Dqn_V4 lhs, Dqn_f32 rhs); +DQN_API Dqn_V4 & operator*= (Dqn_V4 &lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 & operator*= (Dqn_V4 &lhs, Dqn_f32 rhs); +DQN_API Dqn_V4 & operator*= (Dqn_V4 &lhs, int32_t rhs); +DQN_API Dqn_V4 & operator-= (Dqn_V4 &lhs, Dqn_V4 rhs); +DQN_API Dqn_V4 & operator+= (Dqn_V4 &lhs, Dqn_V4 rhs); +#endif // !defined(DQN_NO_V4) +#if !defined(DQN_NO_M4) +// NOTE: [$MAT4] Dqn_M4 //////////////////////////////////////////////////////////////////////////// +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_FSTR8) +DQN_API Dqn_FStr8<256> Dqn_M4_ColumnMajorString (Dqn_M4 mat); +#endif +#endif // !defined(DQN_NO_M4) +// NOTE: [$M2x3] Dqn_M2x3 ////////////////////////////////////////////////////////////////////////// +DQN_API bool operator== (Dqn_M2x3 const &lhs, Dqn_M2x3 const &rhs); +DQN_API bool operator!= (Dqn_M2x3 const &lhs, Dqn_M2x3 const &rhs); +DQN_API Dqn_M2x3 Dqn_M2x3_Identity (); +DQN_API Dqn_M2x3 Dqn_M2x3_Translate (Dqn_V2 offset); +DQN_API Dqn_M2x3 Dqn_M2x3_Scale (Dqn_V2 scale); +DQN_API Dqn_M2x3 Dqn_M2x3_Rotate (Dqn_f32 radians); +DQN_API Dqn_M2x3 Dqn_M2x3_Mul (Dqn_M2x3 m1, Dqn_M2x3 m2); +DQN_API Dqn_V2 Dqn_M2x3_Mul2F32 (Dqn_M2x3 m1, Dqn_f32 x, Dqn_f32 y); +DQN_API Dqn_V2 Dqn_M2x3_MulV2 (Dqn_M2x3 m1, Dqn_V2 v2); + +#if !defined(DQN_NO_RECT) +// NOTE: [$RECT] Dqn_Rect ////////////////////////////////////////////////////////////////////////// +#define Dqn_Rect_InitV2x2(pos, size) DQN_LITERAL(Dqn_Rect){(pos), (size)} +#define Dqn_Rect_InitNx4(x, y, w, h) DQN_LITERAL(Dqn_Rect){DQN_LITERAL(Dqn_V2){{x, y}}, DQN_LITERAL(Dqn_V2){{w, h}}} + +DQN_API bool operator== (const Dqn_Rect& lhs, const Dqn_Rect& rhs); +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_Rect Dqn_Rect_Expand (Dqn_Rect a, Dqn_f32 amount); +DQN_API Dqn_Rect Dqn_Rect_ExpandV2 (Dqn_Rect a, Dqn_V2 amount); +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_RectMinMax Dqn_Rect_MinMax (Dqn_Rect a); +DQN_API Dqn_f32 Dqn_Rect_Area (Dqn_Rect a); +DQN_API Dqn_V2 Dqn_Rect_InterpolatedPoint (Dqn_Rect rect, Dqn_V2 t01); +DQN_API Dqn_V2 Dqn_Rect_TopLeft (Dqn_Rect rect); +DQN_API Dqn_V2 Dqn_Rect_TopRight (Dqn_Rect rect); +DQN_API Dqn_V2 Dqn_Rect_BottomLeft (Dqn_Rect rect); +DQN_API Dqn_V2 Dqn_Rect_BottomRight (Dqn_Rect rect); + +DQN_API Dqn_Rect Dqn_Rect_CutLeftClip (Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutRightClip (Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutTopClip (Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); +DQN_API Dqn_Rect Dqn_Rect_CutBottomClip (Dqn_Rect *rect, Dqn_f32 amount, Dqn_RectCutClip clip); + +#define Dqn_Rect_CutLeft(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutRight(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutTop(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_Yes) +#define Dqn_Rect_CutBottom(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_Yes) + +#define Dqn_Rect_CutLeftNoClip(rect, amount) Dqn_Rect_CutLeftClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutRightNoClip(rect, amount) Dqn_Rect_CutRightClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutTopNoClip(rect, amount) Dqn_Rect_CutTopClip(rect, amount, Dqn_RectCutClip_No) +#define Dqn_Rect_CutBottomNoClip(rect, amount) Dqn_Rect_CutBottomClip(rect, amount, Dqn_RectCutClip_No) + +DQN_API Dqn_Rect Dqn_RectCut_Cut (Dqn_RectCut rect_cut, Dqn_V2 size, Dqn_RectCutClip clip); +#define Dqn_RectCut_Init(rect, side) DQN_LITERAL(Dqn_RectCut){rect, side} +#define Dqn_RectCut_Left(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Left} +#define Dqn_RectCut_Right(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Right} +#define Dqn_RectCut_Top(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Top} +#define Dqn_RectCut_Bottom(rect) DQN_LITERAL(Dqn_RectCut){rect, Dqn_RectCutSide_Bottom} +#endif // !defined(DQN_NO_RECT) +// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_RaycastLineIntersectV2Result Dqn_Raycast_LineIntersectV2(Dqn_V2 origin_a, Dqn_V2 dir_a, Dqn_V2 origin_b, Dqn_V2 dir_b); +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); diff --git a/dqn_memory.cpp b/dqn_memory.cpp deleted file mode 100644 index 92d4848..0000000 --- a/dqn_memory.cpp +++ /dev/null @@ -1,608 +0,0 @@ -#if !defined(DQN_PLATFORM_EMSCRIPTEN) -// NOTE: [$VMEM] Dqn_VMem ========================================================================== -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 - - Dqn_Debug_TrackAlloc(result, size, (page_flags & Dqn_VMemPage_AllocRecordLeakPermitted)); - return result; -} - -DQN_API bool Dqn_VMem_Commit(void *ptr, Dqn_usize size, uint32_t page_flags) -{ - bool result = false; - if (!ptr || size == 0) - return false; - - unsigned long os_page_flags = Dqn_VMem_ConvertPageToOSFlags_(page_flags); - #if defined(DQN_OS_WIN32) - result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; - #elif defined(DQN_OS_UNIX) - 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) - - // NOTE: This is a decommit call, which is explicitly saying to free the - // pages but not the VADs, you would use VMem_Release to release everything. - DQN_MSVC_WARNING_PUSH - DQN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. - VirtualFree(ptr, size, MEM_DECOMMIT); - DQN_MSVC_WARNING_POP - - #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_Debug_TrackDealloc(ptr); -} - -DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags) -{ - if (!ptr || size == 0) - return 0; - - static Dqn_Str8 const ALIGNMENT_ERROR_MSG = - DQN_STR8("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, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); - DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%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) { - #if defined(DQN_NO_WIN) - DQN_ASSERTF(result, "VirtualProtect failed"); - #else - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - DQN_ASSERTF(result, "VirtualProtect failed (%u): %.*s", error.code, DQN_STR_FMT(error.msg)); - #endif - } - #else - int result = mprotect(result->memory, result->size, os_page_flags); - DQN_ASSERTF(result == 0, "mprotect failed (%d)", errno); - #endif - - return result; -} -#endif // !defined(DQN_PLATFORM_EMSCRIPTEN) - -// NOTE: [$MEMF] Dqn_MemAPI ================================================================== -#if !defined(DQN_PLATFORM_EMSCRIPTEN) -DQN_API Dqn_MemAPI Dqn_MemAPI_InitOSVirtual() -{ - Dqn_MemAPI result = {}; - result.reserve = Dqn_VMem_Reserve; - result.commit = Dqn_VMem_Commit; - result.release = Dqn_VMem_Release; - return result; -} -#endif // !defined(DQN_PLATFORM_EMSCRIPTEN) - -void *Dqn_MemAPI_CRTReserve(Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags) -{ - (void)page_flags; - (void)commit; - void *result = calloc(1, size); - return result; -} - -bool Dqn_MemAPI_CRTCommit(void *ptr, Dqn_usize size, uint32_t page_flags) -{ - (void)ptr; (void)size; (void)page_flags; - return true; -} - -void Dqn_MemAPI_CRTRelease(void *ptr, Dqn_usize size) -{ - (void)size; - free(ptr); -} - -DQN_API Dqn_MemAPI Dqn_MemAPI_InitCRT() -{ - Dqn_MemAPI result = {}; - result.reserve = Dqn_MemAPI_CRTReserve; - result.commit = Dqn_MemAPI_CRTCommit; - result.release = Dqn_MemAPI_CRTRelease; - return result; -} - -// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== -DQN_API Dqn_MemBlockSizeRequiredResult Dqn_MemBlock_SizeRequired(Dqn_MemBlock const *block, Dqn_usize size, uint8_t alignment, uint32_t flags) -{ - DQN_ASSERT(alignment > 0 && Dqn_IsPowerOfTwo(alignment)); - - Dqn_MemBlockSizeRequiredResult result = {}; - result.alloc_size = size; - Dqn_MemBlockFlag block_flags = DQN_CAST(Dqn_MemBlockFlag)((block ? block->flags : 0) | flags); - uint8_t ptr_alignment = alignment; - - if (DQN_ASAN_POISON) { - // NOTE: Guard a page after with poisoned memory. The first allocation - // is always guarded with poison-ed memory to prevent read/writes behind - // the block of memory. - if ((block_flags & Dqn_MemBlockFlag_AllocsAreContiguous) == 0) { - result.alloc_size = Dqn_AlignUpPowerOfTwo(size + DQN_ASAN_POISON_GUARD_SIZE, DQN_ASAN_POISON_ALIGNMENT); - } - ptr_alignment = DQN_MAX(alignment, DQN_ASAN_POISON_ALIGNMENT); - } - - if (block) { - uintptr_t address = DQN_CAST(uintptr_t)block->data + block->used; - uintptr_t next_address = Dqn_AlignUpPowerOfTwo(address, ptr_alignment); - result.data_offset = next_address - DQN_CAST(uintptr_t)block->data; - Dqn_usize new_used = result.data_offset + result.alloc_size; - result.block_size = new_used - block->used; - } else { - result.block_size = result.alloc_size + (ptr_alignment - 1); - } - - return result; -} - -DQN_API Dqn_usize Dqn_MemBlock_MetadataSize() -{ - Dqn_usize init_poison_page = DQN_ASAN_POISON ? DQN_ASAN_POISON_GUARD_SIZE : 0; - Dqn_usize alignment = DQN_ASAN_POISON ? DQN_ASAN_POISON_ALIGNMENT : alignof(Dqn_MemBlock); - Dqn_usize result = Dqn_AlignUpPowerOfTwo(sizeof(Dqn_MemBlock), alignment) + init_poison_page; - return result; -} - -DQN_API Dqn_MemBlock *Dqn_MemBlock_InitMemAPI(Dqn_usize reserve, Dqn_usize commit, uint32_t flags, Dqn_MemAPI mem_api) -{ - DQN_ASSERTF(g_dqn_library->os_page_size, "Library needs to be initialised by calling Dqn_Library_Init()"); - DQN_ASSERTF(Dqn_IsPowerOfTwo(g_dqn_library->os_page_size), "Invalid page size"); - DQN_ASSERTF((flags & ~Dqn_MemBlockFlag_All) == 0, "Invalid flag combination, must adhere to Dqn_MemBlockFlags"); - - if (reserve == 0) - return nullptr; - - Dqn_usize metadata_size = Dqn_MemBlock_MetadataSize(); - Dqn_usize reserve_aligned = Dqn_AlignUpPowerOfTwo(metadata_size + reserve, g_dqn_library->os_page_size); - Dqn_usize commit_aligned = Dqn_AlignUpPowerOfTwo(metadata_size + commit, g_dqn_library->os_page_size); - commit_aligned = DQN_MIN(commit_aligned, reserve_aligned); - - // NOTE: Avoid 1 syscall by committing on reserve if amounts are equal - Dqn_VMemCommit commit_on_reserve = commit_aligned == reserve_aligned ? Dqn_VMemCommit_Yes : Dqn_VMemCommit_No; - auto *result = DQN_CAST(Dqn_MemBlock *)mem_api.reserve(reserve_aligned, commit_on_reserve, Dqn_VMemPage_ReadWrite); - if (result) { - // NOTE: Commit pages if we did not commit on the initial range. - if (!commit_on_reserve) - mem_api.commit(result, commit_aligned, Dqn_VMemPage_ReadWrite); - - result->mem_api = mem_api; - result->data = DQN_CAST(uint8_t *)result + metadata_size; - result->size = reserve_aligned - metadata_size; - result->commit = commit_aligned - metadata_size; - result->flags = DQN_CAST(uint8_t)flags; - - // NOTE: Poison (guard page + commit). We do *not* poison the entire - // block, only the commit pages. Since we may reserve large amounts of - // space vs commit we'd waste time marking those pages as poisoned as - // reads or writes outside of committed pages will page fault. - if (DQN_ASAN_POISON) { - DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->data, DQN_ASAN_POISON_ALIGNMENT)); - DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->size, DQN_ASAN_POISON_ALIGNMENT)); - void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)result + sizeof(Dqn_MemBlock), DQN_ASAN_POISON_ALIGNMENT); - Dqn_usize bytes_to_poison = DQN_ASAN_POISON_GUARD_SIZE + result->commit; - Dqn_ASAN_PoisonMemoryRegion(poison_ptr, bytes_to_poison); - } - } - return result; -} - -DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uint32_t flags, Dqn_MemAPI mem_api) -{ - Dqn_MemBlock *result = Dqn_MemBlock_InitMemAPI(reserve, commit, flags, mem_api); - return result; -} - -DQN_API void *Dqn_MemBlock_Alloc(Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem) -{ - DQN_ASSERT(zero_mem == Dqn_ZeroMem_Yes || zero_mem == Dqn_ZeroMem_No); - - void *result = nullptr; - if (!block) - return result; - - Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(block, size, alignment, Dqn_MemBlockFlag_Nil); - Dqn_usize new_used = size_required.data_offset + size_required.alloc_size; - if (new_used > block->size) - return result; - - result = DQN_CAST(char *)block->data + size_required.data_offset; - block->used = new_used; - block->used_hwm = DQN_MAX(block->used_hwm, new_used); - DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result, alignment)); - - if (DQN_ASAN_POISON) - Dqn_ASAN_UnpoisonMemoryRegion(result, size); - - if (zero_mem == Dqn_ZeroMem_Yes) { - Dqn_usize reused_bytes = DQN_MIN(block->commit - size_required.data_offset, size); - DQN_MEMSET(result, DQN_MEMSET_BYTE, reused_bytes); - } - - if (block->commit < block->used) { - Dqn_usize commit_size = Dqn_AlignUpPowerOfTwo(block->used - block->commit, g_dqn_library->os_page_size); - void *commit_ptr = (void *)Dqn_AlignUpPowerOfTwo((char *)block->data + block->commit, g_dqn_library->os_page_size); - block->commit += commit_size; - block->mem_api.commit(commit_ptr, commit_size, Dqn_VMemPage_ReadWrite); - DQN_ASSERT(block->commit <= block->size); - - if (DQN_ASAN_POISON) { // NOTE: Poison newly committed pages that aren't being used. - void *poison_ptr = DQN_CAST(char *)block->data + block->used; - Dqn_usize bytes_to_poison = block->commit - block->used; - Dqn_ASAN_PoisonMemoryRegion(poison_ptr, bytes_to_poison); - } - } - - return result; -} - -DQN_API void Dqn_MemBlock_Free(Dqn_MemBlock *block) -{ - if (!block) - return; - Dqn_usize release_size = block->size + Dqn_MemBlock_MetadataSize(); - if (DQN_ASAN_POISON) - Dqn_ASAN_UnpoisonMemoryRegion(block, release_size); - block->mem_api.release(block, release_size); -} - -DQN_API void Dqn_MemBlock_Pop(Dqn_MemBlock *block, Dqn_usize size) -{ - if (!block) - return; - Dqn_usize size_adjusted = DQN_MIN(size, block->used); - Dqn_usize to = block->used - size_adjusted; - Dqn_MemBlock_PopTo(block, to); -} - -DQN_API void Dqn_MemBlock_PopTo(Dqn_MemBlock *block, Dqn_usize to) -{ - if (!block || to >= block->used) - return; - - if (DQN_ASAN_POISON) { - void *poison_ptr = DQN_CAST(char *)block->data + to; - void *end_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo((DQN_CAST(uintptr_t)block->data + block->used), DQN_ASAN_POISON_ALIGNMENT); - uintptr_t bytes_to_poison = DQN_CAST(uintptr_t)end_ptr - DQN_CAST(uintptr_t)poison_ptr; - Dqn_ASAN_PoisonMemoryRegion(poison_ptr, bytes_to_poison); - } - block->used = to; -} - -// NOTE: [$AREN] Dqn_Arena ========================================================================= -DQN_FILE_SCOPE void *Dqn_Arena_AllocatorAlloc(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_Alloc(arena, size, align, zero_mem); - return result; -} - -DQN_FILE_SCOPE void Dqn_Arena_AllocatorDealloc(void *, size_t, void *) -{ - // NOTE: No-op, arenas batch allocate and batch deallocate. Call free on the - // underlying arena, since we can't free individual pointers. -} - -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; -} - -DQN_API Dqn_MemBlock *Dqn_Arena_Grow(Dqn_Arena *arena, Dqn_usize reserve, Dqn_usize commit, uint8_t flags) -{ - if (!arena) - return nullptr; - - uint8_t mem_block_flags = flags; - if (arena->allocs_are_allowed_to_leak) - mem_block_flags |= Dqn_MemBlockFlag_AllocRecordLeakPermitted; - - if (!arena->mem_api.reserve) { - #if defined(DQN_PLATFORM_EMSCRIPTEN) - arena->mem_api = Dqn_MemAPI_InitCRT(); - #else - arena->mem_api = Dqn_MemAPI_InitOSVirtual(); - #endif - } - - Dqn_MemBlock *result = Dqn_MemBlock_Init(reserve, commit, mem_block_flags, arena->mem_api); - if (result) { - if (!arena->head) - arena->head = result; - - if (arena->tail) - arena->tail->next = result; - - if (!arena->curr) - arena->curr = result; - - result->prev = arena->tail; - arena->tail = result; - arena->blocks += 1; - } - return result; -} - -DQN_API void *Dqn_Arena_Alloc(Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem) -{ - DQN_ASSERT(Dqn_IsPowerOfTwo(align)); - - void *result = nullptr; - if (!arena || size == 0 || align == 0) - return result; - - for (;;) { - while (arena->curr && (arena->curr->flags & Dqn_MemBlockFlag_ArenaPrivate)) - arena->curr = arena->curr->next; - - if (!arena->curr) { - Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(nullptr, size, align, Dqn_MemBlockFlag_Nil); - Dqn_usize block_size = size_required.block_size; - if (!Dqn_Arena_Grow(arena, block_size, block_size, Dqn_MemBlockFlag_Nil)) - return result; - DQN_ASSERT(arena->curr); - } - - result = Dqn_MemBlock_Alloc(arena->curr, size, align, zero_mem); - if (result) - break; - - arena->curr = arena->curr->next; - } - - return result; -} - -DQN_API void *Dqn_Arena_Copy(Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) -{ - void *result = Dqn_Arena_Alloc(arena, size, align, Dqn_ZeroMem_No); - DQN_MEMCPY(result, src, size); - return result; -} - -DQN_API void *Dqn_Arena_CopyZ(Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t align) -{ - void *result = Dqn_Arena_Alloc(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_Arena *arena) -{ - if (!arena) - return; - - for (Dqn_MemBlock *block = arena->head; block; ) { - Dqn_MemBlock *next = block->next; - Dqn_MemBlock_Free(block); - block = next; - } - - arena->curr = arena->head = arena->tail = nullptr; - arena->blocks = 0; -} - -DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena) -{ - Dqn_ArenaTempMemory result = {}; - if (arena) { - 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.blocks = arena->blocks; - } - return result; -} - -DQN_API void Dqn_Arena_EndTempMemory(Dqn_ArenaTempMemory temp_memory, bool cancel) -{ - if (cancel || !temp_memory.arena) - return; - - // NOTE: The arena has been freed or invalidly manipulated (e.g. freed) - // since the temp memory started as the head cannot change once it is - // captured in the temp memory. - Dqn_Arena *arena = temp_memory.arena; - if (arena->head != temp_memory.head) - return; - - // NOTE: Revert the current block to the temp_memory's current block - arena->blocks = temp_memory.blocks; - arena->head = temp_memory.head; - arena->curr = temp_memory.curr; - Dqn_MemBlock_PopTo(arena->curr, temp_memory.curr_used); - - // NOTE: Free the tail blocks until we reach the temp_memory's tail block - while (arena->tail != temp_memory.tail) { - Dqn_MemBlock *tail = arena->tail; - arena->tail = tail->prev; - Dqn_MemBlock_Free(tail); - } - - // NOTE: Chop the restored tail link - if (arena->tail) - arena->tail->next = nullptr; - - // NOTE: Reset the usage of all the blocks between the tail and current block's - for (Dqn_MemBlock *block = arena->tail; block && (block != arena->curr); block = block->prev) - Dqn_MemBlock_PopTo(block, 0); -} - -Dqn_ArenaTempMemoryScope::Dqn_ArenaTempMemoryScope(Dqn_Arena *arena) -{ - temp_memory = Dqn_Arena_BeginTempMemory(arena); -} - -Dqn_ArenaTempMemoryScope::~Dqn_ArenaTempMemoryScope() -{ - Dqn_Arena_EndTempMemory(temp_memory, cancel); -} - -DQN_API Dqn_ArenaInfo Dqn_Arena_Info(Dqn_Arena const *arena) -{ - Dqn_ArenaInfo result = {}; - if (!arena) - return result; - - for (Dqn_MemBlock const *block = arena->head; block; block = block->next) { - result.capacity += block->size; - result.used += block->used; - result.used_hwm += block->used_hwm; - result.commit += block->commit; - result.wasted += block->next ? (block->size - block->used) : 0; - } - return result; -} - -// NOTE: [$ACAT] Dqn_ArenaCatalog ================================================================== -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, DQN_FMT_ATTRIB char const *fmt, va_list args) -{ - Dqn_Arena *result = Dqn_ArenaCatalog_Alloc(catalog, byte_size, commit); - result->label = Dqn_Str8_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, DQN_FMT_ATTRIB 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; -} - diff --git a/dqn_memory.h b/dqn_memory.h deleted file mode 100644 index 3e8354f..0000000 --- a/dqn_memory.h +++ /dev/null @@ -1,342 +0,0 @@ -// NOTE: [$VMEM] Dqn_VMem ========================================================================== -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. - // This flag must only be used in Dqn_VMem_Protect - Dqn_VMemPage_Guard = 1 << 3, - - // If leak tracing is enabled, this flag will allow the allocation recorded - // from the reserve call to be leaked, e.g. not printed when leaks are - // dumped to the console. - Dqn_VMemPage_AllocRecordLeakPermitted = 1 << 2, -}; - -#if !defined(DQN_PLATFORM_EMSCRIPTEN) -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); -#endif - -// NOTE: [$MEMF] Dqn_MemAPI ================================================================== -// Interface for specifying the routines for memory allocation for Dqn_MemBlock - -typedef void *(Dqn_MemReserveFunc)(Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags); -typedef bool (Dqn_MemCommitFunc) (void *ptr, Dqn_usize size, uint32_t page_flags); -typedef void (Dqn_MemReleaseFunc)(void *ptr, Dqn_usize size); - -struct Dqn_MemAPI -{ - Dqn_MemReserveFunc *reserve; - Dqn_MemCommitFunc *commit; - Dqn_MemReleaseFunc *release; -}; - -#if !defined(DQN_PLATFORM_EMSCRIPTEN) -DQN_API Dqn_MemAPI Dqn_MemAPI_InitOSVirtual(); -#endif -DQN_API Dqn_MemAPI Dqn_MemAPI_InitCRT(); - -// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== -// Encapsulates allocation of objects from a raw block of memory by bumping a -// a pointer in the block. Some examples include our memory arenas are -// implemented as light wrappers over chained memory blocks and our arrays -// backed by virtual memory take memory blocks. -// -// One pattern we take advantage of under this design is that our virtual arrays -// can ask an arena for a memory block and sub-allocate its contiguous items -// from it. Since the arena created the memory block, the array's lifetime is -// bound to the arena which could also be managing a bunch of other allocations -// with the same lifetime. -// -// This provides an advantage over creating a specific arena for that array that -// is configured not to grow or chain (in order to adhere to the contiguous -// layout requirement) thus limiting the arena to that 1 specific usecase. -// -// NOTE: API ======================================================================================= -// @proc Dqn_MemBlockSizeRequiredResult -// @desc Calculate various size metrics about how many bytes it'd take to -// allocate a pointer from the given block. The size of the allocation is -// treated as one object and the padding and page-guards are applied -// accordingly to the one object -// -// If you are trying to determine how many bytes are required for `N` distinct -// objects then you must multiple the result of this function by `N` to -// account for the per-item page-guard paddding. -// -// @param `block` Pass in the block you wish to allocate from to calculate -// size metrics for. You may pass in `null` to calculate how many bytes are -// needed to `Dqn_MemBlock_Init` a fresh block capable of allocating the size -// requested. -// -// @param `flags` The `Dqn_MemBlockFlag`s to apply in the calculation. Various -// features may influence the sizes required for allocating the requested -// amount of bytes. If `block` is passed in, the flags will be OR'ed together -// to determine the flags to account for. - -enum Dqn_MemBlockFlag -{ - Dqn_MemBlockFlag_Nil = 0, - Dqn_MemBlockFlag_ArenaPrivate = 1 << 0, - - // Enforce that adjacent allocations from this block are contiguous in - // memory (as long as the alignment used between allocations are - /// consistent). - // - // This flag is currently only used when ASAN memory poison-ing is enabled - // via `DQN_ASAN_POISON`. In this mode all allocations are sandwiched with a - // page's worth of poison-ed memory breaking the contiguous requirement of - // arrays. Passing this flag will stop the block from padding pointers with - // poison. - Dqn_MemBlockFlag_AllocsAreContiguous = 1 << 1, - - // If leak tracing is enabled, this flag will allow the allocation recorded - // from the reserve call to be leaked, e.g. not printed when leaks are - // dumped to the console. - Dqn_MemBlockFlag_AllocRecordLeakPermitted = 1 << 2, - Dqn_MemBlockFlag_All = Dqn_MemBlockFlag_ArenaPrivate | - Dqn_MemBlockFlag_AllocsAreContiguous | - Dqn_MemBlockFlag_AllocRecordLeakPermitted, -}; - -struct Dqn_MemBlock -{ - Dqn_MemAPI mem_api; - void *data; - Dqn_usize used; - Dqn_usize used_hwm; - Dqn_usize size; - Dqn_usize commit; - Dqn_MemBlock *next; - Dqn_MemBlock *prev; - uint8_t flags; -}; - -struct Dqn_MemBlockSizeRequiredResult -{ - // Offset from the block's data pointer that the allocation will start at - // If `block` was null then this is always set to 0. - Dqn_usize data_offset; - - // How many bytes will be allocated for the amount requested by the user. - // This is usually the same as the number requested except when ASAN - // poison-ing is enabled. In that case, the pointer will be padded at the - // end with a page's worth of poison-ed memory. - Dqn_usize alloc_size; - - // How many bytes of space is needed in a block for allocating the requested - // pointer. This may differ from the allocation size depending on additional - // alignment requirements *and* whether or not ASAN poison-ing is required. - Dqn_usize block_size; -}; - -DQN_API Dqn_usize Dqn_MemBlock_MetadataSize (uint8_t flags); -DQN_API Dqn_MemBlockSizeRequiredResult Dqn_MemBlock_SizeRequired (Dqn_MemBlock const *block, Dqn_usize size, uint8_t alignment, uint32_t flags); -DQN_API Dqn_MemBlock * Dqn_MemBlock_InitMemAPI(Dqn_usize reserve, Dqn_usize commit, uint32_t flags, Dqn_MemAPI mem_functions); -DQN_API Dqn_MemBlock * Dqn_MemBlock_Init (Dqn_usize reserve, Dqn_usize commit, uint32_t flags); -DQN_API void * Dqn_MemBlock_Alloc (Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem); -DQN_API void Dqn_MemBlock_Free (Dqn_MemBlock *block); -DQN_API void Dqn_MemBlock_Pop (Dqn_MemBlock *block, Dqn_usize size); -DQN_API void Dqn_MemBlock_PopTo (Dqn_MemBlock *block, Dqn_usize to); - -#define Dqn_MemBlock_New(block, Type, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type), alignof(Type), zero_mem) -#define Dqn_MemBlock_NewArray(block, Type, count, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type) * count, alignof(Type), zero_mem) - -// NOTE: [$AREN] Dqn_Arena ========================================================================= -// 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_Alloc, Dqn_Arena_New, Dqn_Arena_NewArray, -// Dqn_Arena_NewArrayWithBlock, -// @desc Alloc byte/objects -// `Alloc` 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 Alloc 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. -struct Dqn_ArenaInfo -{ - Dqn_usize capacity; // Total allocating capacity of the arena in bytes - Dqn_usize used; // Total amount of bytes used in the arena - Dqn_usize commit; // Total amount of bytes committed in the arena - Dqn_usize wasted; // Orphaned space in blocks due to allocations requiring more space than available in the active block - Dqn_usize used_hwm; // High-water mark for 'used' -}; - -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 used_hwm;// 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 -{ - Dqn_MemAPI mem_api; - bool allocs_are_allowed_to_leak; - Dqn_Str8 label; // Optional label to describe the arena - Dqn_MemBlock *head; // Active block the arena is allocating from - Dqn_MemBlock *curr; // Active block the arena is allocating from - Dqn_MemBlock *tail; // Last block in the linked list of blocks - uint64_t blocks; -}; - -struct Dqn_ArenaTempMemory -{ - Dqn_Arena *arena; // Arena the scope is for - Dqn_MemBlock *head; // Head block of the arena at the beginning of the scope - Dqn_MemBlock *curr; // Current block of the arena at the beginning of the scope - Dqn_MemBlock *tail; // Tail block of the arena at the beginning of the scope - Dqn_usize blocks; - Dqn_usize curr_used; // Current used amount of the current block -}; - -// Automatically begin and end a temporary memory scope on object construction -// and destruction respectively. -#define Dqn_Arena_TempMemoryScope(arena) Dqn_ArenaTempMemoryScope DQN_UNIQUE_NAME(temp_memory_) = Dqn_ArenaTempMemoryScope(arena) -struct Dqn_ArenaTempMemoryScope -{ - Dqn_ArenaTempMemoryScope(Dqn_Arena *arena); - ~Dqn_ArenaTempMemoryScope(); - Dqn_ArenaTempMemory temp_memory; - bool cancel = false; -}; - -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_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type), alignof(Type), zero_mem) -#define Dqn_Arena_NewCopy(arena, Type, src) (Type *)Dqn_Arena_Copy(arena, (src), sizeof(*src), alignof(Type)) -#define Dqn_Arena_NewCopyZ(arena, Type, src) (Type *)Dqn_Arena_Copy(arena, (src), sizeof(*src), alignof(Type)) -#define Dqn_Arena_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type) * (count), alignof(Type), zero_mem) -#define Dqn_Arena_NewArrayCopy(arena, Type, src, count) (Type *)Dqn_Arena_Copy(arena, (src), sizeof(*src) * (count), alignof(Type)) -#define Dqn_Arena_NewArrayCopyZ(arena, Type, src, count) (Type *)Dqn_Arena_CopyZ(arena, (src), sizeof(*src) * (count), alignof(Type)) - -DQN_API Dqn_Allocator Dqn_Arena_Allocator (Dqn_Arena *arena); -DQN_API Dqn_MemBlock * Dqn_Arena_Grow (Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags); -DQN_API void * Dqn_Arena_Alloc (Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem); -DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); -DQN_API void * Dqn_Arena_CopyZ (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment); -DQN_API void Dqn_Arena_Free (Dqn_Arena *arena, Dqn_ZeroMem zero_mem); - -// NOTE: Temp Memory =============================================================================== -DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena); -DQN_API void Dqn_Arena_EndTempMemory (Dqn_ArenaTempMemory temp_memory, bool cancel); - -// NOTE: Arena Info =============================================================================== -DQN_API Dqn_ArenaInfo Dqn_Arena_Info (Dqn_Arena const *arena); - -// NOTE: [$ACAT] Dqn_ArenaCatalog ================================================================== -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, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF (Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, DQN_FMT_ATTRIB char const *fmt, ...); diff --git a/dqn_os.cpp b/dqn_os.cpp index d81f636..ac1a173 100644 --- a/dqn_os.cpp +++ b/dqn_os.cpp @@ -1,136 +1,442 @@ -DQN_API void Dqn_OS_Exit(uint32_t exit_code) +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ +// $$ / $$ |$$ / \__| +// $$ | $$ |\$$$$$$\ +// $$ | $$ | \____$$\ +// $$ | $$ |$$\ $$ | +// $$$$$$ |\$$$$$$ | +// \______/ \______/ +// +// dqn_os.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSDateTimeStr8 Dqn_OS_DateLocalTimeStr8(Dqn_OSDateTime time, char date_separator, char hms_separator) { - #if defined(DQN_OS_WIN32) - ExitProcess(exit_code); - #else - exit(exit_code); - #endif -} + Dqn_OSDateTimeStr8 result = {}; + result.hms_size = DQN_CAST(uint8_t) DQN_SNPRINTF(result.hms, + DQN_ARRAY_ICOUNT(result.hms), + "%02hhu%c%02hhu%c%02hhu", + time.hour, + hms_separator, + time.minutes, + hms_separator, + time.seconds); -// NOTE: [$EXEC] Dqn_OSExec ======================================================================== -DQN_API Dqn_OSExecResult Dqn_OS_ExecWait(Dqn_OSExecAsyncHandle handle) -{ - Dqn_OSExecResult result = {}; - if (!handle.process || handle.os_error_code) { - result.os_error_code = handle.os_error_code; - return result; - } + result.date_size = DQN_CAST(uint8_t) DQN_SNPRINTF(result.date, + DQN_ARRAY_ICOUNT(result.date), + "%hu%c%02hhu%c%02hhu", + time.year, + date_separator, + time.month, + date_separator, + time.day); - #if defined(DQN_OS_WIN32) - DWORD exec_result = WaitForSingleObject(handle.process, INFINITE); - if (exec_result == WAIT_FAILED) { - result.os_error_code = GetLastError(); - return result; - } - - DWORD exit_status; - if (!GetExitCodeProcess(handle.process, &exit_status)) { - result.os_error_code = GetLastError(); - return result; - } - - result.exit_code = exit_status; - CloseHandle(handle.process); - #elif defined(DQN_PLATFORM_EMSCRIPTEN) - DQN_ASSERTF(false, "Unsupported operation"); - #else - for (;;) { - int status = 0; - if (waitpid(DQN_CAST(pid_t)handle.process, &status, 0) < 0) { - result.os_error_code = errno; - break; - } - - if (WIFEXITED(status)) { - result.exit_code = WEXITSTATUS(status); - break; - } - - if (WIFSIGNALLED(status)) { - result.os_error_code = WTERMSIG(status); - break; - } - } - #endif + DQN_ASSERT(result.hms_size < DQN_ARRAY_UCOUNT(result.hms)); + DQN_ASSERT(result.date_size < DQN_ARRAY_UCOUNT(result.date)); return result; } -DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync(Dqn_Str8 cmd, Dqn_Str8 working_dir) +DQN_API Dqn_OSDateTimeStr8 Dqn_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) { - Dqn_OSExecAsyncHandle result = {}; - if (cmd.size == 0) - return result; + Dqn_OSDateTime time = Dqn_OS_DateLocalTimeNow(); + Dqn_OSDateTimeStr8 result = Dqn_OS_DateLocalTimeStr8(time, date_separator, hms_separator); + return result; +} - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 cmd16 = Dqn_Win_Str8ToStr16(scratch.arena, cmd); - Dqn_Str16 working_dir16 = Dqn_Win_Str8ToStr16(scratch.arena, working_dir); - - PROCESS_INFORMATION proc_info = {}; - STARTUPINFOW startup_info = {}; - startup_info.cb = sizeof(STARTUPINFOW); - startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); - startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startup_info.dwFlags |= STARTF_USESTDHANDLES; - BOOL create_result = CreateProcessW(nullptr, cmd16.data, nullptr, nullptr, true, 0, nullptr, working_dir16.data, &startup_info, &proc_info); - if (!create_result) { - result.os_error_code = GetLastError(); +DQN_API Dqn_Str8 Dqn_OS_EXEDir(Dqn_Arena *arena) +{ + Dqn_Str8 result = {}; + if (!arena) return result; + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str8 exe_path = Dqn_OS_EXEPath(scratch.arena); + Dqn_Str8 separators[] = {DQN_STR8("/"), DQN_STR8("\\")}; + Dqn_Str8BinarySplitResult split = Dqn_Str8_BinarySplitReverseArray(exe_path, separators, DQN_ARRAY_UCOUNT(separators)); + result = Dqn_Str8_Copy(arena, split.lhs); + return result; +} + +DQN_API Dqn_f64 Dqn_OS_PerfCounterS(uint64_t begin, uint64_t end) +{ + uint64_t frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)frequency; + return result; +} + +DQN_API Dqn_f64 Dqn_OS_PerfCounterMs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + Dqn_f64 result = (ticks * 1'000) / DQN_CAST(Dqn_f64)frequency; + return result; +} + +DQN_API Dqn_f64 Dqn_OS_PerfCounterUs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + Dqn_f64 result = (ticks * 1'000'000) / DQN_CAST(Dqn_f64)frequency; + return result; +} + +DQN_API Dqn_f64 Dqn_OS_PerfCounterNs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + Dqn_f64 result = (ticks * 1'000'000'000) / DQN_CAST(Dqn_f64)frequency; + return result; +} + + +DQN_API Dqn_OSTimer Dqn_OS_TimerBegin() +{ + Dqn_OSTimer result = {}; + result.start = Dqn_OS_PerfCounterNow(); + return result; +} + +DQN_API void Dqn_OS_TimerEnd(Dqn_OSTimer *timer) +{ + timer->end = Dqn_OS_PerfCounterNow(); +} + +DQN_API Dqn_f64 Dqn_OS_TimerS(Dqn_OSTimer timer) +{ + Dqn_f64 result = Dqn_OS_PerfCounterS(timer.start, timer.end); + return result; +} + +DQN_API Dqn_f64 Dqn_OS_TimerMs(Dqn_OSTimer timer) +{ + Dqn_f64 result = Dqn_OS_PerfCounterMs(timer.start, timer.end); + return result; +} + +DQN_API Dqn_f64 Dqn_OS_TimerUs(Dqn_OSTimer timer) +{ + Dqn_f64 result = Dqn_OS_PerfCounterUs(timer.start, timer.end); + return result; +} + +DQN_API Dqn_f64 Dqn_OS_TimerNs(Dqn_OSTimer timer) +{ + Dqn_f64 result = Dqn_OS_PerfCounterNs(timer.start, timer.end); + return result; +} + +DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) +{ + uint64_t os_frequency = Dqn_OS_PerfCounterFrequency(); + uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; + uint64_t tsc_begin = Dqn_CPU_TSC(); + uint64_t result = 0; + if (tsc_begin) { + uint64_t os_elapsed = 0; + for (uint64_t os_begin = Dqn_OS_PerfCounterNow(); os_elapsed < os_target_elapsed; ) + os_elapsed = Dqn_OS_PerfCounterNow() - os_begin; + uint64_t tsc_end = Dqn_CPU_TSC(); + uint64_t tsc_elapsed = tsc_end - tsc_begin; + result = tsc_elapsed / os_elapsed * os_frequency; + } + return result; +} + +#if !defined(DQN_NO_OS_FILE_API) +// NOTE: [$FILE] Dqn_OSPathInfo/File /////////////////////////////////////////////////////////////// +DQN_API bool Dqn_OS_WriteFile(Dqn_OSFile *file, Dqn_Str8 buffer) +{ + bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size); + return result; +} + +DQN_API bool Dqn_OS_WriteFileFV(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + bool result = false; + if (!file || !fmt) + return result; + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); + result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size); + return result; +} + +DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = Dqn_OS_WriteFileFV(file, fmt, args); + va_end(args); + return result; +} + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_OS_WriteAll(Dqn_Str8 path, Dqn_Str8 buffer) +{ + Dqn_OSFile file = Dqn_OS_OpenFile(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write); + bool result = Dqn_OS_WriteFile(&file, buffer); + Dqn_OS_CloseFile(&file); + return result; +} + +DQN_API bool Dqn_OS_WriteAllFV(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); + bool result = Dqn_OS_WriteAll(file_path, buffer); + return result; +} + +DQN_API bool Dqn_OS_WriteAllF(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = Dqn_OS_WriteAllFV(file_path, fmt, args); + va_end(args); + return result; +} + +DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 tmp_path = Dqn_Str8_InitF(scratch.arena, "%.*s.tmp", DQN_STR_FMT(path)); + if (!Dqn_OS_WriteAll(tmp_path, buffer)) { + Dqn_Log_ErrorF("Failed to write to temporary file [path=%.*s]", DQN_STR_FMT(tmp_path)); + return false; } - CloseHandle(proc_info.hThread); - result.process = proc_info.hProcess; - #else - DQN_ASSERTF(false, "Unsupported operation"); - // TODO: This API will need to switch to an array of strings for unix - #if 0 - pid_t child_pid = fork(); - if (child_pid < 0) { - result.os_error_code = errno; - return result; + if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/)) { + Dqn_Log_ErrorF("Failed to overwrite file at '%.*s' with temporary file '%.*s' to complete the safe-write", + DQN_STR_FMT(tmp_path), + DQN_STR_FMT(path)); + return false; } - if (child_pid == 0) { - if (working_dir.size) { - if (chdir(working_dir.data) == -1) { - result.os_error_code = errno; - return result; + if (!Dqn_OS_PathDelete(tmp_path)) { + Dqn_Log_ErrorF("Failed to delete the temporary file at '%.*s' to clean-up the safe-write", DQN_STR_FMT(tmp_path)); + return false; + } + + return true; +} + +DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args); + bool result = Dqn_OS_WriteAllSafe(path, buffer); + return result; +} + +DQN_API bool Dqn_OS_WriteAllSafeF(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = Dqn_OS_WriteAllSafeFV(path, fmt, args); + return result; +} +#endif // !defined(DQN_NO_OS_FILE_API) + +// NOTE: [$PATH] Dqn_OSPath //////////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_OS_PathAddRef(Dqn_Arena *arena, Dqn_OSPath *fs_path, Dqn_Str8 path) +{ + if (!arena || !fs_path || !Dqn_Str8_HasData(path)) + return false; + + if (path.size <= 0) + return true; + + Dqn_Str8 const delimiter_array[] = { + DQN_STR8("\\"), + DQN_STR8("/") + }; + + if (fs_path->links_size == 0) { + fs_path->has_prefix_path_separator = (path.data[0] == '/'); + } + + for (;;) { + Dqn_Str8BinarySplitResult delimiter = Dqn_Str8_BinarySplitArray(path, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array)); + for (; delimiter.lhs.data; delimiter = Dqn_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array))) { + if (delimiter.lhs.size <= 0) + continue; + + Dqn_OSPathLink *link = Dqn_Arena_New(arena, Dqn_OSPathLink, Dqn_ZeroMem_Yes); + if (!link) + return false; + + link->string = delimiter.lhs; + link->prev = fs_path->tail; + if (fs_path->tail) { + fs_path->tail->next = link; + } else { + fs_path->head = link; + } + fs_path->tail = link; + fs_path->links_size += 1; + fs_path->string_size += delimiter.lhs.size; + } + + if (!delimiter.lhs.data) + break; + } + + return true; +} + +DQN_API bool Dqn_OS_PathAdd(Dqn_Arena *arena, Dqn_OSPath *fs_path, Dqn_Str8 path) +{ + Dqn_Str8 copy = Dqn_Str8_Copy(arena, path); + bool result = Dqn_Str8_HasData(copy) ? true : Dqn_OS_PathAddRef(arena, fs_path, copy); + return result; +} + +DQN_API bool Dqn_OS_PathAddF(Dqn_Arena *arena, Dqn_OSPath *fs_path, DQN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_Str8 path = Dqn_Str8_InitFV(arena, fmt, args); + va_end(args); + bool result = Dqn_OS_PathAddRef(arena, fs_path, path); + return result; +} + +DQN_API bool Dqn_OS_PathPop(Dqn_OSPath *fs_path) +{ + if (!fs_path) + return false; + + if (fs_path->tail) { + DQN_ASSERT(fs_path->head); + fs_path->links_size -= 1; + fs_path->string_size -= fs_path->tail->string.size; + fs_path->tail = fs_path->tail->prev; + if (fs_path->tail) { + fs_path->tail->next = nullptr; + } else { + fs_path->head = nullptr; + } + } else { + DQN_ASSERT(!fs_path->head); + } + + return true; +} + +DQN_API Dqn_Str8 Dqn_OS_PathConvertTo(Dqn_Arena *arena, Dqn_Str8 path, Dqn_Str8 path_separator) +{ + Dqn_OSPath fs_path = {}; + Dqn_OS_PathAddRef(arena, &fs_path, path); + Dqn_Str8 result = Dqn_OS_PathBuildWithSeparator(arena, &fs_path, path_separator); + return result; +} + +DQN_API Dqn_Str8 Dqn_OS_PathConvertToF(Dqn_Arena *arena, Dqn_Str8 path_separator, DQN_FMT_ATTRIB char const *fmt, ...) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + va_list args; + va_start(args, fmt); + Dqn_Str8 path = Dqn_Str8_InitFV(scratch.arena, fmt, args); + va_end(args); + Dqn_Str8 result = Dqn_OS_PathConvertTo(arena, path, path_separator); + return result; +} + +DQN_API Dqn_Str8 Dqn_OS_PathConvert(Dqn_Arena *arena, Dqn_Str8 path) +{ + Dqn_Str8 result = Dqn_OS_PathConvertTo(arena, path, Dqn_OSPathSeperatorString); + return result; +} + +DQN_API Dqn_Str8 Dqn_OS_PathConvertF(Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + va_list args; + va_start(args, fmt); + Dqn_Str8 path = Dqn_Str8_InitFV(scratch.arena, fmt, args); + va_end(args); + Dqn_Str8 result = Dqn_OS_PathConvert(arena, path); + return result; +} + +DQN_API Dqn_Str8 Dqn_OS_PathBuildWithSeparator(Dqn_Arena *arena, Dqn_OSPath const *fs_path, Dqn_Str8 path_separator) +{ + Dqn_Str8 result = {}; + if (!fs_path || fs_path->links_size <= 0) + return result; + + // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' + Dqn_usize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); + result = Dqn_Str8_Alloc(arena, string_size, Dqn_ZeroMem_No); + if (result.data) { + char *dest = result.data; + if (fs_path->has_prefix_path_separator) { + DQN_MEMCPY(dest, path_separator.data, path_separator.size); + dest += path_separator.size; + } + + for (Dqn_OSPathLink *link = fs_path->head; link; link = link->next) { + Dqn_Str8 string = link->string; + DQN_MEMCPY(dest, string.data, string.size); + dest += string.size; + + if (link != fs_path->tail) { + DQN_MEMCPY(dest, path_separator.data, path_separator.size); + dest += path_separator.size; } } - - if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { - result.os_error_code = errno; - return result; - } - DQN_INVALID_CODE_PATH; } - result.process = DQN_CAST(void *)child_pid; - #endif - #endif + result.data[string_size] = 0; return result; } -DQN_API Dqn_OSExecResult Dqn_OS_Exec(Dqn_Str8 cmd, Dqn_Str8 working_dir) + +// NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSExecResult Dqn_OS_Exec(Dqn_Slice cmd_line, Dqn_Str8 working_dir) { - Dqn_OSExecAsyncHandle async_handle = Dqn_OS_ExecAsync(cmd, working_dir); + Dqn_OSExecAsyncHandle async_handle = Dqn_OS_ExecAsync(cmd_line, working_dir); Dqn_OSExecResult result = Dqn_OS_ExecWait(async_handle); return result; } -DQN_API void Dqn_OS_ExecOrAbort(Dqn_Str8 cmd, Dqn_Str8 working_dir) +DQN_API void Dqn_OS_ExecOrAbort(Dqn_Slice cmd_line, Dqn_Str8 working_dir) { - Dqn_OSExecResult result = Dqn_OS_Exec(cmd, working_dir); + Dqn_OSExecResult result = Dqn_OS_Exec(cmd_line, working_dir); if (result.os_error_code || result.exit_code) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 cmd_combined = Dqn_Slice_Str8Render(scratch.arena, cmd_line, DQN_STR8(" ") /*separator*/); if (result.os_error_code) { - Dqn_Log_ErrorF("OS failed to execute the requested command returning the error code %u. The command was\n\n%.*s", result.os_error_code, DQN_STR_FMT(cmd)); + Dqn_Log_ErrorF("OS failed to execute the requested command returning the error code %u. The command was\n\n%.*s", result.os_error_code, DQN_STR_FMT(cmd_combined)); Dqn_OS_Exit(result.os_error_code); } if (result.exit_code) { - Dqn_Log_ErrorF("OS executed command and returned a non-zero status: %u. The command was\n\n%.*s", result.exit_code, DQN_STR_FMT(cmd)); + Dqn_Log_ErrorF("OS executed command and returned a non-zero status: %u. The command was\n\n%.*s", result.exit_code, DQN_STR_FMT(cmd_combined)); Dqn_OS_Exit(result.exit_code); } } } + +// NOTE: [$HTTP] Dqn_OSHttp //////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_OS_HttpRequestWait(Dqn_OSHttpResponse *response) +{ + if (response && Dqn_OS_SemaphoreHasData(&response->on_complete_semaphore)) + Dqn_OS_SemaphoreWait(&response->on_complete_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT); +} + +DQN_API Dqn_OSHttpResponse Dqn_OS_HttpRequest(Dqn_Arena *arena, Dqn_Str8 host, Dqn_Str8 path, Dqn_OSHttpRequestSecure secure, Dqn_Str8 method, Dqn_Str8 body, Dqn_Str8 headers) +{ + // TODO(doyle): Revise the memory allocation and its lifetime + Dqn_OSHttpResponse result = {}; + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + result.scratch_arena = scratch.arena; + + Dqn_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers); + Dqn_OS_HttpRequestWait(&result); + return result; +} diff --git a/dqn_os.h b/dqn_os.h index c0e0455..a039572 100644 --- a/dqn_os.h +++ b/dqn_os.h @@ -1,7 +1,179 @@ -// NOTE: [$EXEC] Dqn_OSExec ======================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ +// $$ / $$ |$$ / \__| +// $$ | $$ |\$$$$$$\ +// $$ | $$ | \____$$\ +// $$ | $$ |$$\ $$ | +// $$$$$$ |\$$$$$$ | +// \______/ \______/ +// +// dqn_os.h -- Common APIs/services provided by the operating system/platform layer +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$OMEM] Dqn_OSMem -- -- Memory allocation (typically virtual memory if supported) +// [$DATE] Dqn_OSDate -- -- Date time APIs +// [$FILE] Dqn_OSPathInfo/File -- -- File path info/reading/writing +// [$PATH] Dqn_OSPath -- -- Construct native OS paths helpers +// [$EXEC] Dqn_OSExec -- -- Execute programs programatically +// [$SEMA] Dqn_OSSemaphore -- DQN_SEMAPHORE -- +// [$MUTX] Dqn_OSMutex -- -- +// [$THRD] Dqn_OSThread -- DQN_THREAD -- +// [$HTTP] Dqn_OSHttp -- -- +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$OMEM] Dqn_OSMem ////////////////////////////////////////////////////////////////////////// +enum Dqn_OSMemCommit +{ + Dqn_OSMemCommit_No, + Dqn_OSMemCommit_Yes, +}; + +enum Dqn_OSMemPage +{ + // Exception on read/write with a page. This flag overrides the read/write + // access. + Dqn_OSMemPage_NoAccess = 1 << 0, + + Dqn_OSMemPage_Read = 1 << 1, // Only read permitted on the page. + + // Only write permitted on the page. On Windows this is not supported and + // will be promoted to read+write permissions. + Dqn_OSMemPage_Write = 1 << 2, + + Dqn_OSMemPage_ReadWrite = Dqn_OSMemPage_Read | Dqn_OSMemPage_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_OSMemPage_NoAccess. + // This flag must only be used in Dqn_OSMem_Protect + Dqn_OSMemPage_Guard = 1 << 3, + + // If leak tracing is enabled, this flag will allow the allocation recorded + // from the reserve call to be leaked, e.g. not printed when leaks are + // dumped to the console. + Dqn_OSMemPage_AllocRecordLeakPermitted = 1 << 4, + + // If leak tracing is enabled this flag will prevent any allocation record + // from being created in the allocation table at all. If this flag is + // enabled, 'OSMemPage_AllocRecordLeakPermitted' has no effect since the + // record will never be created. + Dqn_OSMemPage_NoAllocRecordEntry = 1 << 5, + + // [INTERNAL] Do not use. All flags together do not constitute a correct + // configuration of pages. + Dqn_OSMemPage_All = Dqn_OSMemPage_NoAccess | + Dqn_OSMemPage_ReadWrite | + Dqn_OSMemPage_Guard | + Dqn_OSMemPage_AllocRecordLeakPermitted | + Dqn_OSMemPage_NoAllocRecordEntry, +}; + +// NOTE: [$DATE] Dqn_OSDate //////////////////////////////////////////////////////////////////////// +struct Dqn_OSDateTimeStr8 +{ + 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_OSDateTime +{ + uint8_t day; + uint8_t month; + uint16_t year; + uint8_t hour; + uint8_t minutes; + uint8_t seconds; +}; + +struct Dqn_OSTimer /// Record time between two time-points using the OS's performance counter. +{ + uint64_t start; + uint64_t end; +}; + +#if !defined(DQN_NO_OS_FILE_API) +// NOTE: [$FSYS] Dqn_OSFile //////////////////////////////////////////////////////////////////////// +enum Dqn_OSPathInfoType +{ + Dqn_OSPathInfoType_Unknown, + Dqn_OSPathInfoType_Directory, + Dqn_OSPathInfoType_File, +}; + +struct Dqn_OSPathInfo +{ + bool exists; + Dqn_OSPathInfoType 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: R/W Stream API //////////////////////////////////////////////////////////////////////////// +struct Dqn_OSFile +{ + void *handle; + char error[512]; + uint16_t error_size; +}; + +enum Dqn_OSFileOpen +{ + Dqn_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open + Dqn_OSFileOpen_OpenIfExist, // Open file at path only if it exists + Dqn_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist +}; + +enum Dqn_OSFileAccess +{ + Dqn_OSFileAccess_Read = 1 << 0, + Dqn_OSFileAccess_Write = 1 << 1, + Dqn_OSFileAccess_Execute = 1 << 2, + Dqn_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode + Dqn_OSFileAccess_ReadWrite = Dqn_OSFileAccess_Read | Dqn_OSFileAccess_Write, + Dqn_OSFileAccess_All = Dqn_OSFileAccess_ReadWrite | Dqn_OSFileAccess_Execute, +}; +#endif // DQN_NO_OS_FILE_API + +// NOTE: Dqn_OSPath //////////////////////////////////////////////////////////////////////////////// +#if !defined(Dqn_OSPathSeperator) + #if defined(DQN_OS_WIN32) + #define Dqn_OSPathSeperator "\\" + #else + #define Dqn_OSPathSeperator "/" + #endif + #define Dqn_OSPathSeperatorString DQN_STR8(Dqn_OSPathSeperator) +#endif + +struct Dqn_OSPathLink +{ + Dqn_Str8 string; + Dqn_OSPathLink *next; + Dqn_OSPathLink *prev; +}; + +struct Dqn_OSPath +{ + bool has_prefix_path_separator; + Dqn_OSPathLink *head; + Dqn_OSPathLink *tail; + Dqn_usize string_size; + uint16_t links_size; +}; + +// NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// struct Dqn_OSExecAsyncHandle { uint32_t os_error_code; + uint32_t exit_code; void *process; }; @@ -11,8 +183,200 @@ struct Dqn_OSExecResult uint32_t exit_code; }; -DQN_API void Dqn_OS_Exit (uint32_t exit_code); -DQN_API Dqn_OSExecResult Dqn_OS_ExecWait (Dqn_OSExecAsyncHandle handle); -DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync (Dqn_Str8 cmd, Dqn_Str8 working_dir); -DQN_API Dqn_OSExecResult Dqn_OS_Exec (Dqn_Str8 cmd, Dqn_Str8 working_dir); -DQN_API void Dqn_OS_ExecOrAbort(Dqn_Str8 cmd, Dqn_Str8 working_dir); +#if !defined(DQN_NO_SEMAPHORE) +// NOTE: [$SEMA] Dqn_OSSemaphore /////////////////////////////////////////////////////////////////// +uint32_t const DQN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; + +struct Dqn_OSSemaphore +{ + #if defined(DQN_OS_WIN32) && !defined(DQN_OS_WIN32_USE_PTHREADS) + void *win32_handle; + #else + sem_t posix_handle; + bool posix_init; + #endif +}; + +enum Dqn_OSSemaphoreWaitResult +{ + Dqn_OSSemaphoreWaitResult_Failed, + Dqn_OSSemaphoreWaitResult_Success, + Dqn_OSSemaphoreWaitResult_Timeout, +}; +#endif // !defined(DQN_NO_SEMAPHORE) + +// NOTE: [$MUTX] Dqn_OSMutex /////////////////////////////////////////////////////////////////////// +struct Dqn_OSMutex +{ + #if defined(DQN_OS_WIN32) && !defined(DQN_OS_WIN32_USE_PTHREADS) + char win32_handle[48]; + #else + pthread_mutex_t posix_handle; + pthread_mutexattr_t posix_attribs; + #endif +}; + +// NOTE: [$THRD] Dqn_OSThread ///////////////////////////////////////////////////////////////////// +#if !defined(DQN_NO_THREAD) && !defined(DQN_NO_SEMAPHORE) +typedef int32_t (Dqn_OSThreadFunc)(struct Dqn_OSThread*); + +struct Dqn_OSThread +{ + void *handle; + uint64_t thread_id; + void *user_context; + Dqn_OSThreadFunc *func; + Dqn_OSSemaphore init_semaphore; +}; +#endif // !defined(DQN_NO_THREAD) + +// NOTE: [$HTTP] Dqn_OSHttp //////////////////////////////////////////////////////////////////////// +enum Dqn_OSHttpRequestSecure +{ + Dqn_OSHttpRequestSecure_No, + Dqn_OSHttpRequestSecure_Yes, +}; + +struct Dqn_OSHttpResponse +{ + // NOTE: Response data + uint32_t error_code; + Dqn_Str8 error_msg; + uint16_t http_status; + Dqn_Str8 body; + Dqn_b32 done; + + // NOTE: Book-keeping + Dqn_Arena *arena; // Allocates memory for the response + + // NOTE: Async book-keeping + // Synchronous HTTP response uses the TLS scratch arena whereas async + // calls use their own dedicated arena. + Dqn_Arena tmp_arena; + Dqn_Arena *scratch_arena; + Dqn_Str8Builder builder; + Dqn_OSSemaphore on_complete_semaphore; + + #if defined(DQN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_t *em_handle; + #elif defined(DQN_OS_WIN32) + HINTERNET win32_request_session; + HINTERNET win32_request_connection; + HINTERNET win32_request_handle; + #endif +}; + +// NOTE: [$OMEM] Memory ////////////////////////////////////////////////////////////////////////// +DQN_API void * Dqn_OS_MemReserve (Dqn_usize size, Dqn_OSMemCommit commit, uint32_t page_flags); +DQN_API bool Dqn_OS_MemCommit (void *ptr, Dqn_usize size, uint32_t page_flags); +DQN_API void Dqn_OS_MemDecommit(void *ptr, Dqn_usize size); +DQN_API void Dqn_OS_MemRelease (void *ptr, Dqn_usize size); +DQN_API int Dqn_OS_MemProtect (void *ptr, Dqn_usize size, uint32_t page_flags); + +// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow (); +DQN_API Dqn_OSDateTimeStr8 Dqn_OS_DateLocalTimeStr8Now(char date_separator = '-', char hms_separator = ':'); +DQN_API Dqn_OSDateTimeStr8 Dqn_OS_DateLocalTimeStr8 (Dqn_OSDateTime time, char date_separator = '-', char hms_separator = ':'); +DQN_API uint64_t Dqn_OS_DateUnixTime (); + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_OS_SecureRNGBytes (void *buffer, uint32_t size); +DQN_API Dqn_Str8 Dqn_OS_EXEPath (Dqn_Arena *arena); +DQN_API Dqn_Str8 Dqn_OS_EXEDir (Dqn_Arena *arena); +DQN_API void Dqn_OS_SleepMs (Dqn_uint milliseconds); + +// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +DQN_API uint64_t Dqn_OS_PerfCounterNow (); +DQN_API uint64_t Dqn_OS_PerfCounterFrequency(); +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_PerfCounterUs (uint64_t begin, uint64_t end); +DQN_API Dqn_f64 Dqn_OS_PerfCounterNs (uint64_t begin, 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_TimerUs (Dqn_OSTimer timer); +DQN_API Dqn_f64 Dqn_OS_TimerNs (Dqn_OSTimer timer); +DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency); + +#if !defined(DQN_NO_OS_FILE_API) +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo (Dqn_Str8 path); +DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path); +DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path); +DQN_API bool Dqn_OS_FileCopy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); +DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); +DQN_API bool Dqn_OS_DirExists (Dqn_Str8 path); +DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path); + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSFile Dqn_OS_OpenFile (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access); +DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size); +DQN_API bool Dqn_OS_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer); +DQN_API bool Dqn_OS_WriteFileFV (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_OS_CloseFile (Dqn_OSFile *file); + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena); +DQN_API bool Dqn_OS_WriteAll (Dqn_Str8 path, Dqn_Str8 buffer); +DQN_API bool Dqn_OS_WriteAllFV (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteAllF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API bool Dqn_OS_WriteAllSafe (Dqn_Str8 path, Dqn_Str8 buffer); +DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_WriteAllSafeF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...); +#endif // !defined(DQN_NO_OS_FILE_API) + +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_OS_PathAddRef (Dqn_Arena *arena, Dqn_OSPath *fs_path, Dqn_Str8 path); +DQN_API bool Dqn_OS_PathAdd (Dqn_Arena *arena, Dqn_OSPath *fs_path, Dqn_Str8 path); +DQN_API bool Dqn_OS_PathAddF (Dqn_Arena *arena, Dqn_OSPath *fs_path, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API bool Dqn_OS_PathPop (Dqn_OSPath *fs_path); +DQN_API Dqn_Str8 Dqn_OS_PathBuildWithSeparator(Dqn_Arena *arena, Dqn_OSPath const *fs_path, Dqn_Str8 path_separator); +DQN_API Dqn_Str8 Dqn_OS_PathConvertTo (Dqn_Arena *arena, Dqn_Str8 path, Dqn_Str8 path_separtor); +DQN_API Dqn_Str8 Dqn_OS_PathConvertToF (Dqn_Arena *arena, Dqn_Str8 path_separator, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_Str8 Dqn_OS_PathConvert (Dqn_Arena *arena, Dqn_Str8 path); +DQN_API Dqn_Str8 Dqn_OS_PathConvertF (Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...); +#define Dqn_OS_PathBuildFwdSlash(allocator, fs_path) Dqn_OS_PathBuildWithSeparator(allocator, fs_path, DQN_STR8("/")) +#define Dqn_OS_PathBuildBackSlash(allocator, fs_path) Dqn_OS_PathBuildWithSeparator(allocator, fs_path, DQN_STR8("\\")) +#if defined(DQN_OS_WIN32) + #define Dqn_OS_PathBuild(allocator, fs_path) Dqn_OS_PathBuildBackSlash(allocator, fs_path) +#else + #define Dqn_OS_PathBuild(allocator, fs_path) Dqn_OS_PathBuildFwdSlash(allocator, fs_path) +#endif + +// NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_OS_Exit (uint32_t exit_code); +DQN_API Dqn_OSExecResult Dqn_OS_ExecWait (Dqn_OSExecAsyncHandle handle); +DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync (Dqn_Slice cmd_line, Dqn_Str8 working_dir); +DQN_API Dqn_OSExecResult Dqn_OS_Exec (Dqn_Slice cmd_line, Dqn_Str8 working_dir); +DQN_API void Dqn_OS_ExecOrAbort(Dqn_Slice cmd_line, Dqn_Str8 working_dir); + +// NOTE: [$SEMA] Dqn_OSSemaphore /////////////////////////////////////////////////////////////////// +#if !defined(DQN_NO_SEMAPHORE) +DQN_API Dqn_OSSemaphore Dqn_OS_SemaphoreInit (uint32_t initial_count); +DQN_API bool Dqn_OS_SemaphoreIsValid (Dqn_OSSemaphore *semaphore); +DQN_API void Dqn_OS_SemaphoreDeinit (Dqn_OSSemaphore *semaphore); +DQN_API void Dqn_OS_SemaphoreIncrement(Dqn_OSSemaphore *semaphore, uint32_t amount); +DQN_API Dqn_OSSemaphoreWaitResult Dqn_OS_SemaphoreWait (Dqn_OSSemaphore *semaphore, uint32_t timeout_ms); +#endif // !defined(DQN_NO_SEMAPHORE) + +// NOTE: [$MUTX] Dqn_OSMutex /////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSMutex Dqn_OS_MutexInit (uint32_t initial_count, uint32_t max_count); +DQN_API void Dqn_OS_MutexDeinit(Dqn_OSMutex *mutex); +DQN_API void Dqn_OS_MutexLock (Dqn_OSMutex mutex); +DQN_API void Dqn_OS_MutexUnlock(Dqn_OSMutex mutex); + +// NOTE: [$THRD] Dqn_OSThread ///////////////////////////////////////////////////////////////////// +#if !defined(DQN_NO_THREAD) && !defined(DQN_NO_SEMAPHORE) +DQN_API bool Dqn_OS_ThreadInit (Dqn_OSThread *thread, Dqn_OSThreadFunc *func, void *user_context); +DQN_API void Dqn_OS_ThreadDeinit(Dqn_OSThread thread); +DQN_API uint32_t Dqn_OS_ThreadID (); +#endif // !defined(DQN_NO_THREAD) + +// NOTE: [$HTTP] Dqn_OSHttp //////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_OS_HttpRequestAsync(Dqn_OSHttpResponse *response, Dqn_Arena *arena, Dqn_Str8 host, Dqn_Str8 path, Dqn_OSHttpRequestSecure secure, Dqn_Str8 method, Dqn_Str8 body, Dqn_Str8 headers); +DQN_API void Dqn_OS_HttpRequestWait (Dqn_OSHttpResponse *response); +DQN_API void Dqn_OS_HttpRequestFree (Dqn_OSHttpResponse *response); +DQN_API Dqn_OSHttpResponse Dqn_OS_HttpRequest (Dqn_Arena *arena, Dqn_Str8 host, Dqn_Str8 path, Dqn_OSHttpRequestSecure secure, Dqn_Str8 method, Dqn_Str8 body, Dqn_Str8 headers); diff --git a/dqn_os_posix.cpp b/dqn_os_posix.cpp new file mode 100644 index 0000000..d41b4da --- /dev/null +++ b/dqn_os_posix.cpp @@ -0,0 +1,926 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ \_$$ _|$$ | $$ | +// $$ / $$ |$$ / \__| $$ | $$ |$$ / $$ |$$ / \__| $$ | \$$\ $$ | +// $$ | $$ |\$$$$$$\ $$$$$$$ |$$ | $$ |\$$$$$$\ $$ | \$$$$ / +// $$ | $$ | \____$$\ $$ ____/ $$ | $$ | \____$$\ $$ | $$ $$< +// $$ | $$ |$$\ $$ | $$ | $$ | $$ |$$\ $$ | $$ | $$ /\$$\ +// $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ | +// \______/ \______/ \__| \______/ \______/ \______|\__| \__| +// +// dqn_os_posix.cpp -- Posix implementation of the OS layer +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$VMEM] Dqn_OSMem ////////////////////////////////////////////////////////////////////////// +static uint32_t Dqn_OS_MemConvertPageToOSFlags_(uint32_t protect) +{ + DQN_ASSERT((protect & ~Dqn_OSMemPage_All) == 0); + DQN_ASSERT(protect != 0); + uint32_t result = 0; + + if (protect & (Dqn_OSMemPage_NoAccess | Dqn_OSMemPage_Guard)) { + result = PROT_NONE; + } else { + if (protect & Dqn_OSMemPage_Read) + result = PROT_READ; + if (protect & Dqn_OSMemPage_Write) + result = PROT_WRITE; + } + return result; +} + +DQN_API void *Dqn_OS_MemReserve(Dqn_usize size, Dqn_OSMemCommit commit, uint32_t page_flags) +{ + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + + if (commit == Dqn_OSMemCommit_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; + return result; +} + +DQN_API bool Dqn_OS_MemCommit(void *ptr, Dqn_usize size, uint32_t page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + result = mprotect(ptr, size, os_page_flags) == 0; + return result; +} + +DQN_API void Dqn_OS_MemDecommit(void *ptr, Dqn_usize size) +{ + mprotect(ptr, size, PROT_NONE); + madvise(ptr, size, MADV_FREE); +} + +DQN_API void Dqn_OS_MemRelease(void *ptr, Dqn_usize size) +{ + munmap(ptr, size); +} + +DQN_API int Dqn_OS_MemProtect(void *ptr, Dqn_usize size, uint32_t page_flags) +{ + if (!ptr || size == 0) + return 0; + + static Dqn_Str8 const ALIGNMENT_ERROR_MSG = + DQN_STR8("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, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + int result = mprotect(ptr, size, os_page_flags); + DQN_ASSERTF(result == 0, "mprotect failed (%d)", errno); + return result; +} + +// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow() +{ + Dqn_OSDateTime result = {}; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + // NOTE: localtime_r is used because it is thread safe + // See: https://linux.die.net/man/3/localtime + // According to POSIX.1-2004, localtime() is required to behave as though + // tzset(3) was called, while localtime_r() does not have this requirement. + // For portable code tzset(3) should be called before localtime_r(). + for (static bool once = true; once; once = false) + tzset(); + + struct tm time = {}; + localtime_r(&ts.tv_sec, &time); + + result.hour = time.tm_hour; + result.minutes = time.tm_min; + result.seconds = time.tm_sec; + + result.day = DQN_CAST(uint8_t)time.tm_mday; + result.month = DQN_CAST(uint8_t)time.tm_mon + 1; + result.year = 1900 + DQN_CAST(int16_t)time.tm_year; + return result; +} + +DQN_API uint64_t Dqn_OS_DateUnixTime() +{ + uint64_t result = time(nullptr); + return result; +} + +DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size) +{ + if (!buffer || size < 0) + return false; + + if (size == 0) + return true; + + DQN_ASSERTF(size <= 32, + "We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix guarantees 32 " + "bytes can always be fulfilled by this system at a time"); + // TODO(doyle): https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c + // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html + uint32_t read_bytes = 0; + do { + read_bytes = getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes + } while (read_bytes != size || errno == EAGAIN); + return true; +} + +DQN_API Dqn_Str8 Dqn_OS_EXEPath(Dqn_Arena *arena) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + int required_size_wo_null_terminator = 0; + for (int try_size = 128;; try_size *= 2) { + auto scoped_arena = Dqn_ArenaTempMemScope(arena); + char *try_buf = Dqn_Arena_NewArray(arena, char, try_size, Dqn_ZeroMem_No); + int bytes_written = readlink("/proc/self/exe", try_buf, try_size); + if (bytes_written == -1) { + // Failed, we're unable to determine the executable directory + break; + } else if (bytes_written == try_size) { + // Try again, if returned size was equal- we may of prematurely + // truncated according to the man pages + continue; + } else { + // readlink will give us the path to the executable. Once we + // determine the correct buffer size required to get the full file + // path, we do some post-processing on said string and extract just + // the directory. + + // TODO(dqn): It'd be nice if there's some way of keeping this + // try_buf around, memcopy the byte and trash the try_buf from the + // arena. Instead we just get the size and redo the call one last + // time after this "calculate" step. + DQN_ASSERTF(bytes_written < try_size, "bytes_written can never be greater than the try size, function writes at most try_size"); + required_size_wo_null_terminator = bytes_written; + break; + } + } + + if (required_size_wo_null_terminator) { + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(arena); + char *exe_path = Dqn_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, Dqn_ZeroMem_No); + exe_path[required_size_wo_null_terminator] = 0; + + int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator); + if (bytes_written == -1) { + // Note that if read-link fails again can be because there's + // a potential race condition here, our exe or directory could have + // been deleted since the last call, so we need to be careful. + Dqn_Arena_TempMemEnd(temp_mem); + } else { + result = Dqn_Str8_Init(exe_path, required_size_wo_null_terminator); + } + } + return result; +} + +DQN_API uint64_t Dqn_OS_PerfCounterFrequency() +{ + // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which + // increments at nanosecond granularity. + uint64_t result = 1'000'000'000; + return result; +} + +DQN_API uint64_t Dqn_OS_PerfCounterNow() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + uint64_t result = DQN_CAST(uint64_t) ts.tv_sec * 1'000'000'000 + DQN_CAST(uint64_t) ts.tv_nsec; + return result; +} + +#if !defined(DQN_NO_OS_FILE_API) +DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path) +{ + Dqn_OSPathInfo result = {}; + if (!Dqn_Str8_HasData(path)) + return result; + + struct stat file_stat; + if (lstat(path.data, &file_stat) != -1) { + result.exists = true; + result.size = file_stat.st_size; + result.last_access_time_in_s = file_stat.st_atime; + result.last_write_time_in_s = file_stat.st_mtime; + // TODO(dqn): Seems linux does not support creation time via stat. We + // shoddily deal with this. + result.create_time_in_s = DQN_MIN(result.last_access_time_in_s, result.last_write_time_in_s); + } + return result; +} + +DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); + return result; +} + +DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +{ + bool result = false; + #if defined(DQN_PLATFORM_EMSCRIPTEN) + DQN_ASSERTF(false, "Unsupported on Emscripten because of their VFS model"); + #else + int src_fd = open(src.data, O_RDONLY); + int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); + + if (src_fd != -1 && dest_fd != -1) { + struct stat stat_existing; + fstat(src_fd, &stat_existing); + ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); + result = (bytes_written == stat_existing.st_size); + } + + if (src_fd != -1) + close(src_fd); + + if (dest_fd != -1) + close(dest_fd); + #endif + return result; +} + +DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +{ + // See: https://github.com/gingerBill/gb/blob/master/gb.h + bool result = false; + bool file_moved = true; + if (link(src.data, dest.data) == -1) { + // NOTE: Link can fail if we're trying to link across different volumes + // so we fall back to a binary directory. + file_moved |= Dqn_OS_FileCopy(src, dest, overwrite); + } + + if (file_moved) + result = (unlink(src.data) != -1); // Remove original file + return result; +} + +DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISDIR(stat_result.st_mode); + return result; +} + +DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + bool result = true; + + // TODO(doyle): Implement this without using the path indexes, it's not + // necessary. See Windows implementation. + Dqn_usize path_indexes_size = 0; + uint16_t path_indexes[64] = {}; + + Dqn_Str8 copy = Dqn_Str8_Copy(scratch.arena, path); + for (Dqn_usize index = copy.size - 1; index < copy.size; index--) { + bool first_char = index == (copy.size - 1); + char ch = copy.data[index]; + if (ch == '/' || first_char) { + char temp = copy.data[index]; + + if (!first_char) + copy.data[index] = 0; // Temporarily null terminate it + + bool is_file = Dqn_OS_FileExists(copy); + + if (!first_char) + copy.data[index] = temp; // Undo null termination + + if (is_file) { + // NOTE: There's something that exists in at this path, but + // it's not a directory. This request to make a directory is + // invalid. + return false; + } else { + if (Dqn_OS_DirExists(copy)) { + // NOTE: We found a directory, we can stop here and start + // building up all the directories that didn't exist up to + // this point. + break; + } else { + // NOTE: There's nothing that exists at this path, we can + // create a directory here + path_indexes[path_indexes_size++] = DQN_CAST(uint16_t)index; + } + } + } + } + + for (Dqn_usize index = path_indexes_size - 1; result && index < path_indexes_size; index--) { + uint16_t path_index = path_indexes[index]; + char temp = copy.data[path_index]; + + if (index != 0) copy.data[path_index] = 0; + result |= mkdir(copy.data, 0774) == 0; + if (index != 0) copy.data[path_index] = temp; + } + return result; +} + +DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) +{ + bool result = false; + if (Dqn_Str8_HasData(path)) + result = remove(path.data) == 0; + return result; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access) +{ + Dqn_OSFile result = {}; + if (!Dqn_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~Dqn_OSFileAccess_All) || ((access & Dqn_OSFileAccess_All) == 0)) { + DQN_INVALID_CODE_PATH; + return result; + } + + if (access & Dqn_OSFileAccess_Execute) { + result.error_size = DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate( + result.error, + DQN_ARRAY_UCOUNT(result.error), + "Open file failed: execute access not supported for \"%.*s\"", + DQN_STR_FMT(path)); + DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen + return result; + } + + // NOTE: fopen interface is not as expressive as the Win32 + // We will fopen the file beforehand to setup the state/check for validity + // before closing and reopening it if valid with the correct request access + // permissions. + { + FILE *handle = nullptr; + switch (open_mode) { + case Dqn_OSFileOpen_CreateAlways: handle = fopen(path.data, "w"); break; + case Dqn_OSFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break; + case Dqn_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; + default: DQN_INVALID_CODE_PATH; break; + } + if (!handle) { + result.error_size = DQN_CAST(uint16_t)Dqn_SNPrintFDotTruncate( + result.error, + DQN_ARRAY_UCOUNT(result.error), + "Open file failed: Could not open file in requested mode %d for \"%.*s\"", + open_mode, + DQN_STR_FMT(path)); + return result; + } + fclose(handle); + } + + char const *fopen_mode = nullptr; + if (access & Dqn_OSFileAccess_AppendOnly) { + fopen_mode = "a+"; + } else if (access & Dqn_OSFileAccess_Write) { + fopen_mode = "w+"; + } else if (access & Dqn_OSFileAccess_Read) { + fopen_mode = "r+"; + } + + FILE *handle = fopen(path.data, fopen_mode); + if (!handle) { + result.error_size = DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate( + result.error, + DQN_ARRAY_UCOUNT(result.error), + "Open file failed: Could not open file in fopen mode \"%s\" for \"%.*s\"", + fopen_mode, + DQN_STR_FMT(path)); + return result; + } + result.handle = handle; + return result; +} + +DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size) +{ + if (!file || !file->handle || !buffer || size <= 0 || file->error_size) + return false; + bool result = fwrite(buffer, DQN_CAST(Dqn_usize)size, 1 /*count*/, DQN_CAST(FILE *)file->handle) == 1 /*count*/; + return result; +} + +DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) +{ + if (!file || !file->handle || file->error_size) + return; + fclose(DQN_CAST(FILE *)file->handle); + *file = {}; +} + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); + FILE *file_handle = fopen(path.data, "rb"); + if (!file_handle) { + Dqn_Log_ErrorF("Failed to open file '%.*s' using fopen", DQN_STR_FMT(path)); + return result; + } + DQN_DEFER { fclose(file_handle); }; + + fseek(file_handle, 0, SEEK_END); + Dqn_usize file_size = ftell(file_handle); + + if (DQN_CAST(long)(file_size) == -1L) { + Dqn_Log_ErrorF("Failed to determine '%.*s' file size using ftell", DQN_STR_FMT(path)); + return result; + } + + rewind(file_handle); + Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, file_size, Dqn_ZeroMem_No); + if (!buffer.data) { + Dqn_Log_ErrorF("Failed to allocate %zu bytes to read file '%.*s'", file_size + 1, DQN_STR_FMT(path)); + return result; + } + + if (fread(buffer.data, file_size, 1, file_handle) != 1) { + Dqn_Log_ErrorF("Failed to read %zu bytes into buffer from '%.*s'", file_size, DQN_STR_FMT(path)); + return result; + } + + buffer.data[file_size] = 0; + result = buffer; + temp_mem.mem = {}; + return result; +} +#endif // !defined(DQN_NO_OS_FILE_API) + +// NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_OS_Exit(uint32_t exit_code) +{ + exit(exit_code); +} + +DQN_API Dqn_OSExecResult Dqn_OS_ExecWait(Dqn_OSExecAsyncHandle handle) +{ + Dqn_OSExecResult result = {}; + if (!handle.process || handle.os_error_code) { + result.os_error_code = handle.os_error_code; + return result; + } + + if (handle.exit_code) { + result.exit_code = handle.exit_code; + return result; + } + + #if defined(DQN_PLATFORM_EMSCRIPTEN) + DQN_ASSERTF(false, "Unsupported operation"); + #else + static_assert(sizeof(pid_t) <= sizeof(handle.process), "We store the PID opaquely in a register sized pointer"); + pid_t process = {}; + DQN_MEMCPY(&process, &handle.process, sizeof(process)); + for (;;) { + int status = 0; + if (waitpid(process, &status, 0) < 0) { + result.os_error_code = errno; + break; + } + + if (WIFEXITED(status)) { + result.exit_code = WEXITSTATUS(status); + break; + } + + if (WIFSIGNALED(status)) { + result.os_error_code = WTERMSIG(status); + break; + } + } + #endif + return result; +} + +DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync(Dqn_Slice cmd_line, Dqn_Str8 working_dir) +{ + Dqn_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + // TODO: This API will need to switch to an array of strings for unix + pid_t child_pid = fork(); + if (child_pid < 0) { + result.os_error_code = errno; + return result; + } + + if (child_pid == 0) { + + // NOTE: Convert the command into something suitable for execvp + char **argv = Dqn_Arena_NewArray(scratch.arena, char*, cmd_line.size + 1 /*null*/, Dqn_ZeroMem_Yes); + if (!argv) { + result.exit_code = -1; + return result; + } + + for (Dqn_usize arg_index = 0; arg_index < cmd_line.size; arg_index++) { + Dqn_Str8 arg = cmd_line.data[arg_index]; + argv[arg_index] = Dqn_Str8_Copy(scratch.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated + } + + // NOTE: Change the working directory if there is one + char *prev_working_dir = nullptr; + DQN_DEFER { + if (!prev_working_dir) + return; + if (result.os_error_code == 0) + chdir(prev_working_dir); + free(prev_working_dir); + }; + + if (working_dir.size) { + prev_working_dir = get_current_dir_name(); + if (chdir(working_dir.data) == -1) { + result.os_error_code = errno; + return result; + } + } + + // NOTE: Execute the command. We reuse argv because the first arg, the + // binary to execute is guaranteed to be null-terminated. + if (execvp(argv[0], argv) < 0) { + result.os_error_code = errno; + return result; + } + } + + DQN_MEMCPY(&result.process, &child_pid, sizeof(child_pid)); + return result; +} + +#if !defined(DQN_NO_SEMAPHORE) +// NOTE: [$SEMA] Dqn_OSSemaphore /////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSSemaphore Dqn_OS_SemaphoreInit(uint32_t initial_count) +{ + Dqn_OSSemaphore result = {}; + int pshared = 0; // Share the semaphore across all threads in the process + if (sem_init(&result.posix_handle, pshared, initial_count) == 0) + result.posix_init = true; + return result; +} + +DQN_API bool Dqn_OS_SemaphoreIsValid(Dqn_OSSemaphore *semaphore) +{ + bool result = false; + if (semaphore) { + result = semaphore->posix_init; + } + return result; +} + +DQN_API void Dqn_OS_SemaphoreDeinit(Dqn_OSSemaphore *semaphore) +{ + if (!Dqn_OS_SemaphoreIsValid(semaphore)) + return; + // TODO(doyle): Error handling? + if (semaphore->posix_init) + sem_destroy(&semaphore->posix_handle); + *semaphore = {}; +} + +// NOTE: These functions don't need semaphore to be passed by pointer, **BUT** +// the POSIX implementation disallows copies of sem_t. In particular: +// +// Source: The Open Group Base Specifications Issue 7, 2018 edition +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 +// +// 2.9.9 Synchronization Object Copies and Alternative Mappings +// +// For barriers, condition variables, mutexes, and read-write locks, [TSH] +// [Option Start] if the process-shared attribute is set to +// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the +// address used to initialize it can be used for performing synchronization. The +// effect of referring to another mapping of the same object when locking, +// unlocking, or destroying the object is undefined. [...] The effect of +// referring to a copy of the object when locking, unlocking, or destroying it +// is undefined. + +DQN_API void Dqn_OS_SemaphoreIncrement(Dqn_OSSemaphore *semaphore, uint32_t amount) +{ + if (!Dqn_OS_SemaphoreIsValid(semaphore)) + return; + + #if defined(DQN_OS_WIN32) + sem_post_multiple(&semaphore->posix_handle, amount); // mingw extension + #else + DQN_FOR_UINDEX(index, amount) + sem_post(&semaphore->posix_handle); + #endif // !defined(DQN_OS_WIN32) +} + +DQN_API Dqn_OSSemaphoreWaitResult Dqn_OS_SemaphoreWait(Dqn_OSSemaphore *semaphore, uint32_t timeout_ms) +{ + Dqn_OSSemaphoreWaitResult result = {}; + if (!Dqn_OS_SemaphoreIsValid(semaphore)) + return result; + + if (timeout_ms == DQN_OS_SEMAPHORE_INFINITE_TIMEOUT) { + int wait_result = 0; + do { + wait_result = sem_wait(&semaphore->posix_handle); + } while (wait_result == -1 && errno == EINTR); + + if (wait_result == 0) + result = Dqn_OSSemaphoreWaitResult_Success; + } else { + struct timespec abs_timeout = {}; + abs_timeout.tv_sec = timeout_ms / 1000; + abs_timeout.tv_nsec = (timeout_ms % 1000) * 1'000'000; + if (sem_timedwait(&semaphore->posix_handle, &abs_timeout) == 0) { + result = Dqn_OSSemaphoreWaitResult_Success; + } else { + if (errno == ETIMEDOUT) + result = Dqn_OSSemaphoreWaitResult_Timeout; + } + } + return result; +} +#endif // !defined(DQN_NO_SEMAPHORE) + +#if !defined(DQN_NO_THREAD) +// NOTE: [$MUTX] Dqn_OSMutex /////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSMutex Dqn_OS_MutexInit() +{ + Dqn_OSMutex result = {}; + if (pthread_mutexattr_init(&result.posix_attribs) != 0) + return result; + if (pthread_mutex_init(&result.posix_handle, &result.posix_attribs) != 0) + return result; + return result; +} + +DQN_API void Dqn_OS_MutexDeinit(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutexattr_destroy(&mutex->posix_attribs); + pthread_mutex_destroy(&mutex->posix_handle); +} + +DQN_API void Dqn_OS_MutexLock(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutex_lock(&mutex->posix_handle); +} + +DQN_API void Dqn_OS_MutexUnlock(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutex_unlock(&mutex->posix_handle); +} + +// NOTE: [$THRD] Dqn_OSThread ///////////////////////////////////////////////////////////////////// +static void *Dqn_OS_ThreadFunc_(void *user_context) +{ + Dqn_OSThread *thread = DQN_CAST(Dqn_OSThread *)user_context; + Dqn_OS_SemaphoreWait(&thread->init_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT); + thread->func(thread); + return nullptr; +} + +DQN_API bool Dqn_OS_ThreadInit(Dqn_OSThread *thread, Dqn_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = Dqn_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and + // the ID are different things. For pthreads then we just duplicate the + // thread ID to both variables + pthread_t p_thread = {}; + static_assert(sizeof(p_thread) <= sizeof(thread->handle), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + static_assert(sizeof(p_thread) <= sizeof(thread->thread_id), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + + pthread_attr_t attribs = {}; + pthread_attr_init(&attribs); + result = pthread_create(&p_thread, &attribs, Dqn_OS_ThreadFunc_, thread) == 0; + pthread_attr_destroy(&attribs); + + if (result) { + DQN_MEMCPY(&thread->handle, &p_thread, sizeof(p_thread)); + DQN_MEMCPY(&thread->thread_id, &p_thread, sizeof(p_thread)); + } + + if (result) { + Dqn_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + Dqn_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DQN_API void Dqn_OS_ThreadDeinit(Dqn_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + pthread_t thread_id = {}; + DQN_MEMCPY(&thread_id, &thread->thread_id, sizeof(thread_id)); + + void *return_val = nullptr; + pthread_join(thread_id, &return_val); + thread->handle = {}; + thread->thread_id = {}; +} + +DQN_API uint32_t Dqn_OS_ThreadID() +{ + pid_t result = gettid(); + DQN_ASSERT(gettid() >= 0); + return DQN_CAST(uint32_t)result; +} +#endif // !defined(DQN_NO_THREAD) + +// NOTE: [$HTTP] Dqn_OSHttp //////////////////////////////////////////////////////////////////////// +#if 0 // TODO(doyle): Implement websockets for Windows and Emscripten +static EM_BOOL EMWebSocketOnOpenCallback(int type, const EmscriptenWebSocketOpenEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + // EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(event->socket, R"({"jsonrpc":"2.0","id":1,"method": "eth_subscribe","params":["newHeads"]})"); + // if (result) + // Dqn_Log_InfoF("Failed to emscripten_websocket_send_utf8_text(): %d\n", result); + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnMsgCallback(int type, const EmscriptenWebSocketMessageEvent *event __attribute__((nonnull)), void *user_context) +{ + (void)type; + (void)user_context; + (void)event; + if (event->isText) { + Dqn_Log_InfoF("Received: %.*s", event->numBytes, event->data); + } else { + Dqn_Log_InfoF("Received: %d bytes", event->numBytes); + } + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnErrorCallback(int type, const EmscriptenWebSocketErrorEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnCloseCallback(int type, const EmscriptenWebSocketCloseEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} +#endif + +#if defined(DQN_PLATFORM_EMSCRIPTEN) +static void Dqn_OS_HttpRequestEMFetchOnSuccessCallback(emscripten_fetch_t *fetch) +{ + Dqn_OSHttpResponse *response = DQN_CAST(Dqn_OSHttpResponse *)fetch->userData; + if (!DQN_CHECK(response)) + return; + + response->http_status = DQN_CAST(uint32_t)fetch->status; + response->body = Dqn_Str8_Alloc(response->arena, fetch->numBytes, Dqn_ZeroMem_No); + if (response->body.data) + DQN_MEMCPY(response->body.data, fetch->data, fetch->numBytes); + + Dqn_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + Dqn_Atomic_AddU32(&response->done, 1); +} + +static void Dqn_OS_HttpRequestEMFetchOnErrorCallback(emscripten_fetch_t *fetch) +{ + Dqn_OSHttpResponse *response = DQN_CAST(Dqn_OSHttpResponse *)fetch->userData; + if (!DQN_CHECK(response)) + return; + + response->http_status = DQN_CAST(uint32_t)fetch->status; + response->body = Dqn_Str8_Alloc(response->arena, fetch->numBytes, Dqn_ZeroMem_No); + if (response->body.size) + DQN_MEMCPY(response->body.data, fetch->data, fetch->numBytes); + + Dqn_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + Dqn_Atomic_AddU32(&response->done, 1); +} +#endif + +DQN_API void Dqn_OS_HttpRequestAsync(Dqn_OSHttpResponse *response, + Dqn_Arena *arena, + Dqn_Str8 host, + Dqn_Str8 path, + Dqn_OSHttpRequestSecure secure, + Dqn_Str8 method, + Dqn_Str8 body, + Dqn_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = response->scratch_arena ? response->scratch_arena : &response->tmp_arena; + + Dqn_Arena *scratch_arena = response->scratch_arena; + Dqn_Scratch scratch_ = Dqn_Scratch_Get(arena); + if (!scratch_arena) + scratch_arena = scratch_.arena; + + #if defined(DQN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_attr_t fetch_attribs = {}; + emscripten_fetch_attr_init(&fetch_attribs); + + if (method.size >= sizeof(fetch_attribs.requestMethod)) { + response->error_msg = Dqn_Str8_InitF(arena, + "Request method in EM has a size limit of 31 characters, method was '%.*s' which is %zu characters long", + DQN_STR_FMT(method), + method.size); + DQN_CHECKF(method.size < sizeof(fetch_attribs.requestMethod), "%.*s", DQN_STR_FMT(response->error_msg)); + response->error_code = DQN_CAST(uint32_t)-1; + Dqn_Atomic_AddU32(&response->done, 1); + return; + } + + DQN_MEMCPY(fetch_attribs.requestMethod, method.data, method.size); + + fetch_attribs.requestData = body.data; + fetch_attribs.requestDataSize = body.size; + fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + fetch_attribs.onsuccess = Dqn_OS_HttpRequestEMFetchOnSuccessCallback; + fetch_attribs.onerror = Dqn_OS_HttpRequestEMFetchOnErrorCallback; + fetch_attribs.userData = response; + + Dqn_Str8 url = Dqn_Str8_InitF(scratch_arena, "%.*s%.*s", DQN_STR_FMT(host), DQN_STR_FMT(path)); + Dqn_Log_InfoF("Initiating HTTP '%s' request to '%.*s' with payload '%.*s'", fetch_attribs.requestMethod, DQN_STR_FMT(url), DQN_STR_FMT(body)); + response->on_complete_semaphore = Dqn_OS_SemaphoreInit(0); + response->em_handle = emscripten_fetch(&fetch_attribs, url.data); + #else // #elif defined(DQN_OS_WIN32) + DQN_INVALID_CODE_PATHF("Unimplemented function"); + #endif +} + +DQN_API void Dqn_OS_HttpRequestFree(Dqn_OSHttpResponse *response) +{ + // NOTE: Cleanup + #if defined(DQN_PLATFORM_EMSCRIPTEN) + if (response->em_handle) { + emscripten_fetch_close(response->em_handle); + response->em_handle = nullptr; + } + #endif // #elif defined(DQN_OS_WIN32) + + Dqn_Arena_Deinit(&response->tmp_arena); + if (Dqn_OS_SemaphoreIsValid(&response->on_complete_semaphore)) + Dqn_OS_SemaphoreDeinit(&response->on_complete_semaphore); + *response = {}; +} diff --git a/dqn_os_win32.cpp b/dqn_os_win32.cpp new file mode 100644 index 0000000..2b5c914 --- /dev/null +++ b/dqn_os_win32.cpp @@ -0,0 +1,1295 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| +// +// dqn_os_win32.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$VMEM] Dqn_OSMem ////////////////////////////////////////////////////////////////////////// +static uint32_t Dqn_OS_MemConvertPageToOSFlags_(uint32_t protect) +{ + DQN_ASSERT((protect & ~Dqn_OSMemPage_All) == 0); + DQN_ASSERT(protect != 0); + uint32_t result = 0; + + if (protect & Dqn_OSMemPage_NoAccess) { + result = PAGE_NOACCESS; + } else { + if (protect & Dqn_OSMemPage_ReadWrite) { + result = PAGE_READWRITE; + } else if (protect & Dqn_OSMemPage_Read) { + result = PAGE_READONLY; + } else if (protect & Dqn_OSMemPage_Write) { + Dqn_Log_WarningF("Windows does not support write-only pages, granting read+write access"); + result = PAGE_READWRITE; + } + } + + if (protect & Dqn_OSMemPage_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"); + return result; +} + +DQN_API void *Dqn_OS_MemReserve(Dqn_usize size, Dqn_OSMemCommit commit, uint32_t page_flags) +{ + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long flags = MEM_RESERVE | (commit == Dqn_OSMemCommit_Yes ? MEM_COMMIT : 0); + void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); + return result; +} + +DQN_API bool Dqn_OS_MemCommit(void *ptr, Dqn_usize size, uint32_t page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; + return result; +} + +DQN_API void Dqn_OS_MemDecommit(void *ptr, Dqn_usize size) +{ + // NOTE: This is a decommit call, which is explicitly saying to free the + // pages but not the address space, you would use OS_MemRelease to release + // everything. + DQN_MSVC_WARNING_PUSH + DQN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. + VirtualFree(ptr, size, MEM_DECOMMIT); + DQN_MSVC_WARNING_POP +} + +DQN_API void Dqn_OS_MemRelease(void *ptr, Dqn_usize size) +{ + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +} + +DQN_API int Dqn_OS_MemProtect(void *ptr, Dqn_usize size, uint32_t page_flags) +{ + if (!ptr || size == 0) + return 0; + + static Dqn_Str8 const ALIGNMENT_ERROR_MSG = + DQN_STR8("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, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long prev_flags = 0; + int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); + + (void)prev_flags; + if (result == 0) + DQN_ASSERTF(result, "VirtualProtect failed"); + return result; +} + +// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow() +{ + SYSTEMTIME sys_time; + GetLocalTime(&sys_time); + + Dqn_OSDateTime result = {}; + result.hour = DQN_CAST(uint8_t) sys_time.wHour; + result.minutes = DQN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DQN_CAST(uint8_t) sys_time.wSecond; + result.day = DQN_CAST(uint8_t) sys_time.wDay; + result.month = DQN_CAST(uint8_t) sys_time.wMonth; + result.year = DQN_CAST(int16_t) sys_time.wYear; + return result; +} + +DQN_API uint64_t Dqn_OS_DateUnixTime() +{ + const uint64_t UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks" + const uint64_t TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds + + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + uint64_t result = (date_time.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND; + return result; +} + +DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size) +{ + if (!buffer || size < 0) + return false; + + if (size == 0) + return true; + + bool init = true; + Dqn_TicketMutex_Begin(&g_dqn_library->win32_bcrypt_rng_mutex); + if (!g_dqn_library->win32_bcrypt_rng_handle) + { + wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&g_dqn_library->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (!g_dqn_library->win32_bcrypt_rng_handle || init_status != 0) + { + Dqn_Log_ErrorF("Failed to initialise random number generator, error: %d", init_status); + init = false; + } + } + Dqn_TicketMutex_End(&g_dqn_library->win32_bcrypt_rng_mutex); + + if (!init) + return false; + + long gen_status = BCryptGenRandom(g_dqn_library->win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/); + if (gen_status != 0) + { + Dqn_Log_ErrorF("Failed to generate random bytes: %d", gen_status); + return false; + } + + return true; +} + +DQN_API Dqn_Str8 Dqn_OS_EXEPath(Dqn_Arena *arena) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str16 exe_dir16 = Dqn_Win_EXEPathW(scratch.arena); + result = Dqn_Win_Str16ToStr8(arena, exe_dir16); + return result; +} + +DQN_API void Dqn_OS_SleepMs(Dqn_uint milliseconds) +{ + Sleep(milliseconds); +} + +DQN_API uint64_t Dqn_OS_PerfCounterFrequency() +{ + uint64_t result = g_dqn_library->win32_qpc_frequency.QuadPart; + DQN_ASSERTF(result, "Initialise the library with Dqn_Library_Init() to get a valid QPC frequency value"); + return result; +} + +DQN_API uint64_t Dqn_OS_PerfCounterNow() +{ + LARGE_INTEGER integer = {}; + QueryPerformanceCounter(&integer); + uint64_t result = integer.QuadPart; + return result; +} + +#if !defined(DQN_NO_OS_FILE_API) +static uint64_t Dqn_Win_FileTimeToSeconds_(FILETIME const *time) +{ + ULARGE_INTEGER time_large_int = {}; + time_large_int.u.LowPart = time->dwLowDateTime; + time_large_int.u.HighPart = time->dwHighDateTime; + uint64_t result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; + return result; +} + +DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path) +{ + Dqn_OSPathInfo result = {}; + if (!Dqn_Str8_HasData(path)) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + return result; + + result.exists = true; + result.create_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftCreationTime); + result.last_access_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftLastAccessTime); + result.last_write_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftLastWriteTime); + + LARGE_INTEGER large_int = {}; + large_int.u.HighPart = DQN_CAST(int32_t)attrib_data.nFileSizeHigh; + large_int.u.LowPart = attrib_data.nFileSizeLow; + result.size = (uint64_t)large_int.QuadPart; + + if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + result.type = Dqn_OSPathInfoType_Directory; + else + result.type = Dqn_OSPathInfoType_File; + } + + return result; +} + +DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + } + return result; +} + +DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +{ + bool result = false; + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src); + Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest); + + int fail_if_exists = overwrite == false; + result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; + + if (!result) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Failed to copy the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", + DQN_STR_FMT(src), + DQN_STR_FMT(dest), + DQN_STR_FMT(error.msg)); + } + return result; +} + +DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +{ + bool result = false; + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src); + Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest); + + unsigned long flags = MOVEFILE_COPY_ALLOWED; + if (overwrite) { + flags |= MOVEFILE_REPLACE_EXISTING; + } + + result = MoveFileExW(src16.data, dest16.data, flags) != 0; + if (!result) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Failed to move the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", + DQN_STR_FMT(src), + DQN_STR_FMT(dest), + DQN_STR_FMT(error.msg)); + } + return result; +} + + +DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + } + + return result; +} + +DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) +{ + bool result = true; + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + + // NOTE: Go back from the end of the string to all the directories in the + // string, and try to create them. Since Win32 API cannot create + // intermediate directories that don't exist in a path we need to go back + // and record all the directories until we encounter one that exists. + // + // From that point onwards go forwards and make all the directories + // inbetween by null-terminating the string temporarily, creating the + // directory and so forth until we reach the end. + // + // If we find a file at some point in the path we fail out because the + // series of directories can not be made if a file exists with the same + // name. + for (Dqn_usize index = 0; index < path16.size; index++) { + bool first_char = index == (path16.size - 1); + wchar_t ch = path16.data[index]; + if (ch == '/' || ch == '\\' || first_char) { + wchar_t temp = path16.data[index]; + if (!first_char) + path16.data[index] = 0; // Temporarily null terminate it + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check + + if (successful) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // NOTE: The directory exists, continue iterating the path + } else { + // NOTE: There's some kind of file that exists at the path + // but it's not a directory. This request to make a + // directory is invalid. + return false; + } + } else { + // NOTE: There's nothing that exists at this path, we can create + // a directory here + result |= (CreateDirectoryW(path16.data, nullptr) == 0); + } + + if (!first_char) + path16.data[index] = temp; // Undo null termination + } + } + return result; +} + +DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + if (path16.size) { + result = DeleteFileW(path16.data); + if (!result) + result = RemoveDirectoryW(path16.data); + } + return result; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access) +{ + Dqn_OSFile result = {}; + if (!Dqn_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~Dqn_OSFileAccess_All) || ((access & Dqn_OSFileAccess_All) == 0)) { + DQN_INVALID_CODE_PATH; + return result; + } + + unsigned long create_flag = 0; + switch (open_mode) { + case Dqn_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; + case Dqn_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; + case Dqn_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; + default: DQN_INVALID_CODE_PATH; return result; + } + + unsigned long access_mode = 0; + if (access & Dqn_OSFileAccess_AppendOnly) { + DQN_ASSERTF((access & ~Dqn_OSFileAccess_AppendOnly) == 0, + "Append can only be applied exclusively to the file, other access modes not permitted"); + access_mode = FILE_APPEND_DATA; + } else { + if (access & Dqn_OSFileAccess_Read) + access_mode |= GENERIC_READ; + if (access & Dqn_OSFileAccess_Write) + access_mode |= GENERIC_WRITE; + if (access & Dqn_OSFileAccess_Execute) + access_mode |= GENERIC_EXECUTE; + } + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, + /*DWORD dwDesiredAccess*/ access_mode, + /*DWORD dwShareMode*/ 0, + /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, + /*DWORD dwCreationDisposition*/ create_flag, + /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, + /*HANDLE hTemplateFile*/ nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + result.error_size = + DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(result.error, + DQN_ARRAY_UCOUNT(result.error), + "Open file failed: %.*s for \"%.*s\"", + DQN_STR_FMT(error.msg), + DQN_STR_FMT(path)); + return result; + } + + result.handle = handle; + return result; +} + +DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size) +{ + if (!file || !file->handle || !buffer || size <= 0 || file->error_size) + return false; + + bool result = true; + char const *end = DQN_CAST(char *) buffer + size; + for (char const *ptr = DQN_CAST(char const *) buffer; result && ptr != end;) { + unsigned long write_size = DQN_CAST(unsigned long)DQN_MIN((unsigned long)-1, end - ptr); + unsigned long bytes_written = 0; + result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; + ptr += bytes_written; + } + + if (!result) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + file->error_size = + DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(file->error, + DQN_ARRAY_UCOUNT(file->error), + "Write file failed (%u): %.*s", + error.code, + DQN_STR_FMT(error.msg)); + } + return result; +} + +DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) +{ + if (!file || !file->handle || file->error_size) + return; + CloseHandle(file->handle); + *file = {}; +} + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); + // NOTE: Convert to UTF16 ////////////////////////////////////////////////////////////////////// + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + + // NOTE: Get the file handle /////////////////////////////////////////////////////////////////// + void *file_handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, + /*DWORD dwDesiredAccess*/ GENERIC_READ, + /*DWORD dwShareMode*/ FILE_SHARE_READ, + /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, + /*DWORD dwCreationDisposition*/ OPEN_EXISTING, + /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY, + /*HANDLE hTemplateFile*/ nullptr); + if (file_handle == INVALID_HANDLE_VALUE) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Failed to open file '%.*s' for reading: (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg)); + return result; + } + DQN_DEFER { CloseHandle(file_handle); }; + + // NOTE: Query the file size /////////////////////////////////////////////////////////////////// + LARGE_INTEGER win_file_size; + if (!GetFileSizeEx(file_handle, &win_file_size)) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Failed to query file size [file=%.*s, reason=%.*s]", DQN_STR_FMT(path), DQN_STR_FMT(error.msg)); + return result; + } + + unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart; + if (!DQN_CHECKF(bytes_desired == win_file_size.QuadPart, + "Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) { + return result; + } + + // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// + Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, bytes_desired, Dqn_ZeroMem_No); + unsigned long bytes_read = 0; + unsigned long read_result = ReadFile(/*HANDLE hFile*/ file_handle, + /*LPVOID lpBuffer*/ buffer.data, + /*DWORD nNumberOfBytesToRead*/ bytes_desired, + /*LPDWORD lpNumberOfByesRead*/ &bytes_read, + /*LPOVERLAPPED lpOverlapped*/ nullptr); + + if (read_result == 0) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Failed to load file '%.*s' (via 'ReadFile'): (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg)); + return result; + } + + if (bytes_read != bytes_desired) { + Dqn_WinError error = Dqn_Win_LastError(scratch.arena); + Dqn_Log_ErrorF("Loaded file '%.*s' (via 'ReadFile') did not read the desired number of bytes %u, we read %u: (%u) %.*s", + DQN_STR_FMT(path), + bytes_desired, + bytes_read, + error.code, + DQN_STR_FMT(error.msg)); + return result; + } + + buffer.data[bytes_desired] = 0; + result = buffer; + temp_mem.mem = {}; + return result; +} +#endif // !defined(DQN_NO_OS_FILE_API) + +// NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// +DQN_API void Dqn_OS_Exit(uint32_t exit_code) +{ + ExitProcess(exit_code); +} + +DQN_API Dqn_OSExecResult Dqn_OS_ExecWait(Dqn_OSExecAsyncHandle handle) +{ + Dqn_OSExecResult result = {}; + if (!handle.process || handle.os_error_code) { + result.os_error_code = handle.os_error_code; + return result; + } + + if (handle.exit_code) { + result.exit_code = handle.exit_code; + return result; + } + + DWORD exec_result = WaitForSingleObject(handle.process, INFINITE); + if (exec_result == WAIT_FAILED) { + result.os_error_code = GetLastError(); + return result; + } + + DWORD exit_status; + if (!GetExitCodeProcess(handle.process, &exit_status)) { + result.os_error_code = GetLastError(); + return result; + } + + result.exit_code = exit_status; + CloseHandle(handle.process); + return result; +} + +DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync(Dqn_Slice cmd_line, Dqn_Str8 working_dir) +{ + Dqn_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 cmd_rendered = Dqn_Slice_Str8Render(scratch.arena, cmd_line, DQN_STR8(" ")); + Dqn_Str16 cmd16 = Dqn_Win_Str8ToStr16(scratch.arena, cmd_rendered); + Dqn_Str16 working_dir16 = Dqn_Win_Str8ToStr16(scratch.arena, working_dir); + + PROCESS_INFORMATION proc_info = {}; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + BOOL create_result = CreateProcessW(nullptr, cmd16.data, nullptr, nullptr, true, 0, nullptr, working_dir16.data, &startup_info, &proc_info); + if (!create_result) { + result.os_error_code = GetLastError(); + return result; + } + + CloseHandle(proc_info.hThread); + result.process = proc_info.hProcess; + return result; +} + +#if !defined(DQN_NO_SEMAPHORE) +// NOTE: [$SEMA] Dqn_OSSemaphore /////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSSemaphore Dqn_OS_SemaphoreInit(uint32_t initial_count) +{ + Dqn_OSSemaphore result = {}; + SECURITY_ATTRIBUTES security_attribs = {}; + result.win32_handle = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/); + return result; +} + +DQN_API bool Dqn_OS_SemaphoreHasData(Dqn_OSSemaphore *semaphore) +{ + bool result = false; + if (semaphore) { + result = semaphore->win32_handle; + } + return result; +} + +DQN_API void Dqn_OS_SemaphoreDeinit(Dqn_OSSemaphore *semaphore) +{ + if (!Dqn_OS_SemaphoreHasData(semaphore)) + return; + CloseHandle(semaphore->win32_handle); + *semaphore = {}; +} + +DQN_API void Dqn_OS_SemaphoreIncrement(Dqn_OSSemaphore *semaphore, uint32_t amount) +{ + if (!Dqn_OS_SemaphoreHasData(semaphore)) + return; + LONG prev_count = 0; + ReleaseSemaphore(DQN_CAST(HANDLE *)semaphore->win32_handle, amount, &prev_count); +} + +DQN_API Dqn_OSSemaphoreWaitResult Dqn_OS_SemaphoreWait(Dqn_OSSemaphore *semaphore, uint32_t timeout_ms) +{ + Dqn_OSSemaphoreWaitResult result = {}; + if (!Dqn_OS_SemaphoreHasData(semaphore)) + return result; + + if (!semaphore->win32_handle) + return result; + + DWORD wait_result = WaitForSingleObject(semaphore->win32_handle, timeout_ms == DQN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms); + if (wait_result == WAIT_TIMEOUT) + result = Dqn_OSSemaphoreWaitResult_Timeout; + else if (wait_result == WAIT_OBJECT_0) + result = Dqn_OSSemaphoreWaitResult_Success; + return result; +} +#endif // !defined(DQN_NO_SEMAPHORE) + +#if !defined(DQN_NO_THREAD) +// NOTE: [$MUTX] Dqn_OSMutex /////////////////////////////////////////////////////////////////////// +DQN_API Dqn_OSMutex Dqn_OS_MutexInit() +{ + Dqn_OSMutex result = {}; + + CRITICAL_SECTION crit_section = {}; + InitializeCriticalSection(&crit_section); + + static_assert(sizeof(CRITICAL_SECTION) <= sizeof(result.win32_handle), "Insufficient bytes to store Win32 mutex opaquely in our abstracted Dqn_OSMutex"); + DQN_MEMCPY(result.win32_handle, &crit_section, sizeof(crit_section)); + return result; +} + +DQN_API void Dqn_OS_MutexDeinit(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle; + DeleteCriticalSection(crit_section); + DQN_MEMSET(mutex->win32_handle, 0, DQN_ARRAY_UCOUNT(mutex->win32_handle)); +} + +DQN_API void Dqn_OS_MutexLock(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle; + EnterCriticalSection(crit_section); +} + +DQN_API void Dqn_OS_MutexUnlock(Dqn_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle; + LeaveCriticalSection(crit_section); +} + +// NOTE: [$THRD] Dqn_OSThread ///////////////////////////////////////////////////////////////////// +static DWORD __stdcall Dqn_OS_ThreadFunc_(void *user_context) +{ + Dqn_OSThread *thread = DQN_CAST(Dqn_OSThread *)user_context; + Dqn_OS_SemaphoreWait(&thread->init_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT); + thread->func(thread); + return 0; +} + +DQN_API bool Dqn_OS_ThreadInit(Dqn_OSThread *thread, Dqn_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = Dqn_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + DWORD thread_id = 0; + SECURITY_ATTRIBUTES security_attribs = {}; + thread->handle = CreateThread(&security_attribs, + 0 /*stack_size*/, + Dqn_OS_ThreadFunc_, + thread, + 0 /*creation_flags*/, + &thread_id); + + result = thread->handle != INVALID_HANDLE_VALUE; + if (result) { + thread->thread_id = thread_id; + } + + if (result) { + Dqn_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + Dqn_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DQN_API void Dqn_OS_ThreadDeinit(Dqn_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); + thread->handle = INVALID_HANDLE_VALUE; + thread->thread_id = {}; +} + +DQN_API uint32_t Dqn_OS_ThreadID() +{ + unsigned long result = GetCurrentThreadId(); + return result; +} +#endif // !defined(DQN_NO_THREAD) + +// NOTE: [$HTTP] Dqn_OSHttp //////////////////////////////////////////////////////////////////////// +void Dqn_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength) +{ + (void)session; + (void)dwStatusInformationLength; + + Dqn_OSHttpResponse *response = DQN_CAST(Dqn_OSHttpResponse *)dwContext; + HINTERNET request = DQN_CAST(HINTERNET)response->win32_request_handle; + Dqn_WinError error = {}; + DWORD const READ_BUFFER_SIZE = DQN_MEGABYTES(1); + + if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) { + DWORD status = 0; + DWORD status_size = sizeof(status_size); + if (WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &status_size, + WINHTTP_NO_HEADER_INDEX)) { + response->http_status = DQN_CAST(uint16_t)status; + + // NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel + // will buffer the response into a single buffer and return us the full size of the + // request. + // + // or + // + // You may call WinHttpReadData directly to write the memory into our buffer directly. + // This is advantageous to avoid a copy from the kernel buffer into our buffer. If the + // end user application knows the typical payload size then they can optimise for this + // to prevent unnecessary allocation on the user side. + void *buffer = Dqn_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, Dqn_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = Dqn_Win_LastError(&response->tmp_arena); + } else { + error = Dqn_Win_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) { + DWORD bytes_read = dwStatusInformationLength; + if (bytes_read) { + Dqn_Str8 prev_buffer = Dqn_Str8_Init(DQN_CAST(char *) lpvStatusInformation, bytes_read); + Dqn_Str8Builder_AppendRef(&response->builder, prev_buffer); + + void *buffer = Dqn_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, Dqn_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = Dqn_Win_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) { + WINHTTP_ASYNC_RESULT *async_result = DQN_CAST(WINHTTP_ASYNC_RESULT *)lpvStatusInformation; + error = Dqn_Win_ErrorCodeToMsg(&response->tmp_arena, DQN_CAST(uint32_t)async_result->dwError); + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) { + if (!WinHttpReceiveResponse(request, 0)) + error = Dqn_Win_LastError(&response->tmp_arena); + } + + // NOTE: If the request handle is missing, then, the response has been freed. + // MSDN says that this callback can still be called after closing the handle + // and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR. + if (response->win32_request_handle) { + bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0; + if (read_complete) { + response->body = Dqn_Str8Builder_Build(&response->builder, response->arena); + } + + if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) { + Dqn_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + Dqn_Atomic_AddU32(&response->done, 1); + } + + if (error.code) { + response->error_code = error.code; + response->error_msg = error.msg; + } + } +} + +DQN_API void Dqn_OS_HttpRequestAsync(Dqn_OSHttpResponse *response, + Dqn_Arena *arena, + Dqn_Str8 host, + Dqn_Str8 path, + Dqn_OSHttpRequestSecure secure, + Dqn_Str8 method, + Dqn_Str8 body, + Dqn_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = response->scratch_arena ? response->scratch_arena : &response->tmp_arena; + + Dqn_Arena *scratch_arena = response->scratch_arena; + Dqn_Scratch scratch_ = Dqn_Scratch_Get(arena); + if (!scratch_arena) { + scratch_arena = scratch_.arena; + } + + Dqn_WinError error = {}; + DQN_DEFER { + response->error_msg = error.msg; + response->error_code = error.code; + if (error.code) { + // NOTE: 'Wait' handles failures gracefully, skipping the wait and + // cleans up the request + Dqn_OS_HttpRequestWait(response); + Dqn_Atomic_AddU32(&response->done, 1); + } + }; + + response->win32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); + if (!response->win32_request_session) { + error = Dqn_Win_LastError(&response->tmp_arena); + return; + } + + DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | + WINHTTP_CALLBACK_STATUS_READ_COMPLETE | + WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | + WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE; + if (WinHttpSetStatusCallback(response->win32_request_session, + DQN_CAST(WINHTTP_STATUS_CALLBACK)Dqn_OS_HttpRequestWin32Callback, + callback_flags, + DQN_CAST(DWORD_PTR)nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) { + error = Dqn_Win_LastError(&response->tmp_arena); + return; + } + + Dqn_Str16 host16 = Dqn_Win_Str8ToStr16(scratch_arena, host); + response->win32_request_connection = WinHttpConnect(response->win32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/); + if (!response->win32_request_connection) { + error = Dqn_Win_LastError(&response->tmp_arena); + return; + } + + Dqn_Str16 method16 = Dqn_Win_Str8ToStr16(scratch_arena, method); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch_arena, path); + response->win32_request_handle = WinHttpOpenRequest(response->win32_request_connection, + method16.data, + path16.data, + nullptr /*version*/, + nullptr /*referrer*/, + nullptr /*accept types*/, + secure ? WINHTTP_FLAG_SECURE : 0); + if (!response->win32_request_handle) { + error = Dqn_Win_LastError(&response->tmp_arena); + return; + } + + Dqn_Str16 headers16 = Dqn_Win_Str8ToStr16(scratch_arena, headers); + response->on_complete_semaphore = Dqn_OS_SemaphoreInit(0); + if (!WinHttpSendRequest(response->win32_request_handle, + headers16.data, + DQN_CAST(DWORD)headers16.size, + body.data /*optional data*/, + DQN_CAST(DWORD) body.size /*optional length*/, + DQN_CAST(DWORD) body.size /*total content length*/, + DQN_CAST(DWORD_PTR)response)) { + error = Dqn_Win_LastError(&response->tmp_arena); + return; + } +} + +DQN_API void Dqn_OS_HttpRequestFree(Dqn_OSHttpResponse *response) +{ + // NOTE: Cleanup + // NOTE: These calls are synchronous even when the HTTP request is async. + WinHttpCloseHandle(response->win32_request_handle); + WinHttpCloseHandle(response->win32_request_connection); + WinHttpCloseHandle(response->win32_request_session); + + response->win32_request_session = nullptr; + response->win32_request_connection = nullptr; + response->win32_request_handle = nullptr; + Dqn_Arena_Deinit(&response->tmp_arena); + if (Dqn_OS_SemaphoreHasData(&response->on_complete_semaphore)) + Dqn_OS_SemaphoreDeinit(&response->on_complete_semaphore); + + *response = {}; +} + +// NOTE: [$WIND] Dqn_Win /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_WinError Dqn_Win_ErrorCodeToMsg(Dqn_Arena *arena, uint32_t error_code) +{ + Dqn_WinError result = {}; + result.code = error_code; + if (!arena) + return result; + + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + void *module_to_get_errors_from = nullptr; + + if (result.code >= 12000 && result.code <= 12175) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + module_to_get_errors_from = GetModuleHandleA("winhttp.dll"); + } + + wchar_t *error16 = nullptr; + DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, + /*LPCVOID lpSource */ module_to_get_errors_from, + /*DWORD dwMessageId */ result.code, + /*DWORD dwLanguageId*/ 0, + /*LPWSTR lpBuffer */ (LPWSTR)&error16, + /*DWORD nSize */ 0, + /*va_list *Arguments */ nullptr); + if (size) + result.msg = Dqn_Win_Str16ToStr8(arena, {error16, size}); + if (error16) + LocalFree(error16); + + return result; +} + +DQN_API Dqn_WinError Dqn_Win_LastError(Dqn_Arena *arena) +{ + Dqn_WinError result = Dqn_Win_ErrorCodeToMsg(arena, GetLastError()); + return result; +} + +DQN_API void Dqn_Win_MakeProcessDPIAware() +{ + typedef bool SetProcessDpiAwareProc(void); + typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); + typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); + + // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It + // appears we can make this robust by just loading user32.dll and using + // GetProcAddress on the DPI function. If it's not there, we're on an old + // version of windows, so we can call an older version of the API. + void *lib_handle = LoadLibraryA("user32.dll"); + if (!lib_handle) + return; + + if (auto *set_process_dpi_awareness_context = DQN_CAST(SetProcessDpiAwarenessContextProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwarenessContext")) { + set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } else if (auto *set_process_dpi_awareness = DQN_CAST(SetProcessDpiAwarenessProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwareness")) { + set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); + } else if (auto *set_process_dpi_aware = DQN_CAST(SetProcessDpiAwareProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAware")) { + set_process_dpi_aware(); + } +} + +// NOTE: Windows UTF8 to Str16 ////////////////////////////////////////////// +DQN_API Dqn_Str16 Dqn_Win_Str8ToStr16(Dqn_Arena *arena, Dqn_Str8 src) +{ + Dqn_Str16 result = {}; + if (!arena || !Dqn_Str8_HasData(src)) + return result; + + int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); + if (required_size <= 0) + return result; + + wchar_t *buffer = Dqn_Arena_NewArray(arena, wchar_t, required_size + 1, Dqn_ZeroMem_No); + if (!buffer) + return result; + + int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, buffer, required_size); + if (DQN_CHECK(chars_written == required_size)) { + result.data = buffer; + result.size = chars_written; + result.data[result.size] = 0; + } + return result; +} + +DQN_API int Dqn_Win_Str8ToStr16Buffer(Dqn_Str8 src, wchar_t *dest, int dest_size) +{ + int result = 0; + if (!Dqn_Str8_HasData(src)) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, dest, DQN_CAST(int)dest_size); + dest[DQN_MIN(result, dest_size - 1)] = 0; + return result; +} + +// NOTE: Windows Str16 To UTF8 ////////////////////////////////////////////////////////////////// +DQN_API int Dqn_Win_Str16ToStr8Buffer(Dqn_Str16 src, char *dest, int dest_size) +{ + int result = 0; + if (!Dqn_Str16_HasData(src)) + return result; + + int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DQN_CAST(int)dest_size, nullptr, nullptr); + dest[DQN_MIN(result, dest_size - 1)] = 0; + return result; +} + +DQN_API Dqn_Str8 Dqn_Win_Str16ToStr8(Dqn_Arena *arena, Dqn_Str16 src) +{ + Dqn_Str8 result = {}; + if (!arena || !Dqn_Str16_HasData(src)) + return result; + + int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); + Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, required_size, Dqn_ZeroMem_No); + if (!Dqn_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DQN_CAST(int)buffer.size, nullptr, nullptr); + if (DQN_CHECK(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + temp_mem.mem = {}; + } + + return result; +} + +// NOTE: Windows Executable Directory ////////////////////////////////////////// +DQN_API Dqn_Str16 Dqn_Win_EXEPathW(Dqn_Arena *arena) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str16 result = {}; + Dqn_usize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No); + if (!module_path) + return result; + module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + Dqn_usize index_of_last_slash = 0; + for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = Dqn_Arena_NewArray(arena, wchar_t, module_size + 1, Dqn_ZeroMem_No); + result.size = module_size; + DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DQN_API Dqn_Str16 Dqn_Win_EXEDirW(Dqn_Arena *arena) +{ + // TODO(doyle): Implement a Dqn_Str16_BinarySearchReverse + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str16 result = {}; + Dqn_usize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No); + if (!module_path) + return result; + module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + Dqn_usize index_of_last_slash = 0; + for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = Dqn_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, Dqn_ZeroMem_No); + result.size = index_of_last_slash; + DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DQN_API Dqn_Str8 Dqn_Win_WorkingDir(Dqn_Arena *arena, Dqn_Str8 suffix) +{ + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str16 suffix16 = Dqn_Win_Str8ToStr16(scratch.arena, suffix); + Dqn_Str16 dir16 = Dqn_Win_WorkingDirW(scratch.arena, suffix16); + Dqn_Str8 result = Dqn_Win_Str16ToStr8(arena, dir16); + return result; +} + +DQN_API Dqn_Str16 Dqn_Win_WorkingDirW(Dqn_Arena *arena, Dqn_Str16 suffix) +{ + DQN_ASSERT(suffix.size >= 0); + Dqn_Str16 result = {}; + + // NOTE: required_size is the size required *including* the null-terminator + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + unsigned long required_size = GetCurrentDirectoryW(0, nullptr); + unsigned long desired_size = required_size + DQN_CAST(unsigned long) suffix.size; + + wchar_t *scratch_w_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, desired_size, Dqn_ZeroMem_No); + if (!scratch_w_path) + return result; + + unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, scratch_w_path); + if ((bytes_written_wo_null_terminator + 1) != required_size) { + // TODO(dqn): Error + return result; + } + + wchar_t *w_path = Dqn_Arena_NewArray(arena, wchar_t, desired_size, Dqn_ZeroMem_No); + if (!w_path) + return result; + + if (suffix.size) { + DQN_MEMCPY(w_path, scratch_w_path, sizeof(*scratch_w_path) * bytes_written_wo_null_terminator); + DQN_MEMCPY(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); + w_path[desired_size] = 0; + } + + result = Dqn_Str16{w_path, DQN_CAST(Dqn_usize)(desired_size - 1)}; + return result; +} + +DQN_API bool Dqn_Win_FolderWIterate(Dqn_Str16 path, Dqn_Win_FolderIteratorW *it) +{ + WIN32_FIND_DATAW find_data = {}; + if (it->handle) { + if (FindNextFileW(it->handle, &find_data) == 0) + return false; + } else { + it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ + FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ + &find_data, /*LPVOID lpFindFileData,*/ + FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ + nullptr, /*LPVOID lpSearchFilter,*/ + FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); + + if (it->handle == INVALID_HANDLE_VALUE) + return false; + } + + it->file_name_buf[0] = 0; + it->file_name = Dqn_Str16{it->file_name_buf, 0}; + + do { + if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) + continue; + + it->file_name.size = Dqn_CStr16_Size(find_data.cFileName); + DQN_ASSERT(it->file_name.size < (DQN_ARRAY_UCOUNT(it->file_name_buf) - 1)); + DQN_MEMCPY(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); + it->file_name_buf[it->file_name.size] = 0; + break; + } while (FindNextFileW(it->handle, &find_data) != 0); + + return it->file_name.size > 0; +} + +DQN_API bool Dqn_Win_FolderIterate(Dqn_Str8 path, Dqn_Win_FolderIterator *it) +{ + if (!Dqn_Str8_HasData(path) || !it || path.size <= 0) + return false; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Win_FolderIteratorW wide_it = {}; + Dqn_Str16 path16 = {}; + if (it->handle) { + wide_it.handle = it->handle; + } else { + bool needs_asterisks = Dqn_Str8_EndsWith(path, DQN_STR8("\\")) || + Dqn_Str8_EndsWith(path, DQN_STR8("/")); + bool has_glob = Dqn_Str8_EndsWith(path, DQN_STR8("\\*")) || + Dqn_Str8_EndsWith(path, DQN_STR8("/*")); + + Dqn_Str8 adjusted_path = path; + if (!has_glob) { + // NOTE: We are missing the glob for enumerating the files, we will + // add those characters in this branch, so overwrite the null + // character, add the glob and re-null terminate the buffer. + if (needs_asterisks) + adjusted_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s*", DQN_STR_FMT(path)); + else + adjusted_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/*", DQN_STR_FMT(path)); + } + + path16 = Dqn_Win_Str8ToStr16(scratch.arena, adjusted_path); + if (path16.size <= 0) // Conversion error + return false; + } + + bool result = Dqn_Win_FolderWIterate(path16, &wide_it); + it->handle = wide_it.handle; + if (result) { + int size = Dqn_Win_Str16ToStr8Buffer(wide_it.file_name, it->file_name_buf, DQN_ARRAY_UCOUNT(it->file_name_buf)); + it->file_name = Dqn_Str8_Init(it->file_name_buf, size); + } + + return result; +} diff --git a/dqn_os_win32.h b/dqn_os_win32.h new file mode 100644 index 0000000..8748c8f --- /dev/null +++ b/dqn_os_win32.h @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| +// +// dqn_os_win32.h -- Windows only functions, and, implementation of the OS layer +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct Dqn_WinError +{ + unsigned long code; + Dqn_Str8 msg; +}; + +// NOTE: Windows Str8 <-> Str16 /////////////////////////////////////////// +struct Dqn_Win_FolderIteratorW +{ + void *handle; + Dqn_Str16 file_name; + wchar_t file_name_buf[512]; +}; + +struct Dqn_Win_FolderIterator +{ + void *handle; + Dqn_Str8 file_name; + char file_name_buf[512]; +}; + +// NOTE: [$WIND] Dqn_Win /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_WinError Dqn_Win_ErrorCodeToMsg (Dqn_Arena *arena, uint32_t error_code); +DQN_API Dqn_WinError Dqn_Win_LastError (Dqn_Arena *arena); +DQN_API void Dqn_Win_MakeProcessDPIAware(); +// NOTE: Windows Str8 <-> Str16 //////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str16 Dqn_Win_Str8ToStr16 (Dqn_Arena *arena, Dqn_Str8 src); +DQN_API int Dqn_Win_Str8ToStr16Buffer (Dqn_Str16 src, char *dest, int dest_size); +DQN_API Dqn_Str8 Dqn_Win_Str16ToStr8 (Dqn_Arena *arena, Dqn_Str16 src); +DQN_API int Dqn_Win_Str16ToStr8Buffer (Dqn_Str16 src, char *dest, int dest_size); +// NOTE: Path navigation /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str16 Dqn_Win_EXEPathW (Dqn_Arena *arena); +DQN_API Dqn_Str16 Dqn_Win_EXEDirW (Dqn_Arena *arena); +DQN_API Dqn_Str8 Dqn_Win_WorkingDir (Dqn_Arena *arena, Dqn_Str8 suffix); +DQN_API Dqn_Str16 Dqn_Win_WorkingDirW (Dqn_Arena *arena, Dqn_Str16 suffix); +DQN_API bool Dqn_Win_FolderIterate (Dqn_Str8 path, Dqn_Win_FolderIterator *it); +DQN_API bool Dqn_Win_FolderWIterate (Dqn_Str16 path, Dqn_Win_FolderIteratorW *it); diff --git a/dqn_platform.cpp b/dqn_platform.cpp deleted file mode 100644 index 51770fa..0000000 --- a/dqn_platform.cpp +++ /dev/null @@ -1,1993 +0,0 @@ -// TODO(doyle): Use our temp scratch arenas instead of a massive array on the -// stack. -// NOTE: Max size from MSDN, using \\? syntax, but the ? bit can be expanded -// even more so the max size is kind of not well defined. -#if defined(DQN_OS_WIN32) && !defined(DQN_OS_WIN32_MAX_PATH) - #define DQN_OS_WIN32_MAX_PATH 32767 + 128 /*fudge*/ -#endif - -#if !defined(DQN_NO_FS) -// NOTE: [$FSYS] Dqn_Fs ============================================================================ -#if defined(DQN_OS_WIN32) -DQN_API uint64_t Dqn_Win__FileTimeToSeconds(FILETIME const *time) -{ - ULARGE_INTEGER time_large_int = {}; - time_large_int.u.LowPart = time->dwLowDateTime; - time_large_int.u.HighPart = time->dwHighDateTime; - uint64_t result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; - return result; -} -#endif - -DQN_API bool Dqn_Fs_Exists(Dqn_Str8 path) -{ - bool result = false; - if (!Dqn_Str8_IsValid(path)) - return result; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - if (path16.size) { - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { - result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && - !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - } - #else - struct stat stat_result; - if (lstat(path.data, &stat_result) != -1) - result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); - #endif - return result; -} - -DQN_API bool Dqn_Fs_DirExists(Dqn_Str8 path) -{ - bool result = false; - if (!Dqn_Str8_IsValid(path)) - return result; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - if (path16.size) { - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { - result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && - (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - } - - #else - struct stat stat_result; - if (lstat(path.data, &stat_result) != -1) - result = S_ISDIR(stat_result.st_mode); - #endif - return result; -} - -DQN_API Dqn_FsInfo Dqn_Fs_GetInfo(Dqn_Str8 path) -{ - Dqn_FsInfo result = {}; - if (!Dqn_Str8_IsValid(path)) - return result; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) - return result; - - result.exists = true; - result.create_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftCreationTime); - result.last_access_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftLastAccessTime); - result.last_write_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftLastWriteTime); - - LARGE_INTEGER large_int = {}; - large_int.u.HighPart = DQN_CAST(int32_t)attrib_data.nFileSizeHigh; - large_int.u.LowPart = attrib_data.nFileSizeLow; - result.size = (uint64_t)large_int.QuadPart; - - if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { - if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - result.type = Dqn_FsInfoType_Directory; - } else { - result.type = Dqn_FsInfoType_File; - } - } - - #else - struct stat file_stat; - if (lstat(path.data, &file_stat) != -1) { - result.exists = true; - result.size = file_stat.st_size; - result.last_access_time_in_s = file_stat.st_atime; - result.last_write_time_in_s = file_stat.st_mtime; - - // TODO(dqn): Seems linux does not support creation time via stat. We - // shoddily deal with this. - result.create_time_in_s = DQN_MIN(result.last_access_time_in_s, result.last_write_time_in_s); - } - #endif - return result; -} - -DQN_API bool Dqn_Fs_Copy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) -{ - bool result = false; - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src); - Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest); - - int fail_if_exists = overwrite == false; - result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; - - if (!result) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to copy the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", - DQN_STR_FMT(src), - DQN_STR_FMT(dest), - DQN_STR_FMT(error.msg)); - } - #elif defined(DQN_PLATFORM_EMSCRIPTEN) - DQN_ASSERTF(false, "Unsupported on Emscripten because of their VFS model"); - #else - int src_fd = open(src.data, O_RDONLY); - int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); - - if (src_fd != -1 && dest_fd != -1) { - struct stat stat_existing; - fstat(src_fd, &stat_existing); - Dqn_usize bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); - result = (bytes_written == stat_existing.st_size); - } - - if (src_fd != -1) - close(src_fd); - - if (dest_fd != -1) - close(dest_fd); - #endif - - return result; -} - -DQN_API bool Dqn_Fs_MakeDir(Dqn_Str8 path) -{ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - bool result = true; - - #if defined(DQN_OS_WIN32) - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - - // NOTE: Go back from the end of the string to all the directories in the - // string, and try to create them. Since Win32 API cannot create - // intermediate directories that don't exist in a path we need to go back - // and record all the directories until we encounter one that exists. - // - // From that point onwards go forwards and make all the directories - // inbetween by null-terminating the string temporarily, creating the - // directory and so forth until we reach the end. - // - // If we find a file at some point in the path we fail out because the - // series of directories can not be made if a file exists with the same - // name. - for (Dqn_usize index = 0; index < path16.size; index++) { - bool first_char = index == (path16.size - 1); - wchar_t ch = path16.data[index]; - if (ch == '/' || ch == '\\' || first_char) { - wchar_t temp = path16.data[index]; - if (!first_char) - path16.data[index] = 0; // Temporarily null terminate it - - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check - - if (successful) { - if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - // NOTE: The directory exists, continue iterating the path - } else { - // NOTE: There's some kind of file that exists at the path - // but it's not a directory. This request to make a - // directory is invalid. - return false; - } - } else { - // NOTE: There's nothing that exists at this path, we can create - // a directory here - result |= (CreateDirectoryW(path16.data, nullptr) == 0); - } - - if (!first_char) - path16.data[index] = temp; // Undo null termination - } - } - #else - // TODO(doyle): Implement this without using the path indexes, it's not - // necessary. See Windows implementation. - Dqn_usize path_indexes_size = 0; - uint16_t path_indexes[64] = {}; - - Dqn_Str8 copy = Dqn_Str8_Copy(scratch.allocator, path); - for (Dqn_usize index = copy.size - 1; index < copy.size; index--) { - bool first_char = index == (copy.size - 1); - char ch = copy.data[index]; - if (ch == '/' || first_char) { - char temp = copy.data[index]; - - if (!first_char) - copy.data[index] = 0; // Temporarily null terminate it - - bool is_file = Dqn_Fs_Exists(copy); - - if (!first_char) - copy.data[index] = temp; // Undo null termination - - if (is_file) { - // NOTE: There's something that exists in at this path, but - // it's not a directory. This request to make a directory is - // invalid. - return false; - } else { - if (Dqn_Fs_DirExists(copy)) { - // NOTE: We found a directory, we can stop here and start - // building up all the directories that didn't exist up to - // this point. - break; - } else { - // NOTE: There's nothing that exists at this path, we can - // create a directory here - path_indexes[path_indexes_size++] = DQN_CAST(uint16_t)index; - } - } - } - } - - for (Dqn_usize index = path_indexes_size - 1; result && index < path_indexes_size; index--) { - uint16_t path_index = path_indexes[index]; - char temp = copy.data[path_index]; - - if (index != 0) copy.data[path_index] = 0; - result |= mkdir(copy.data, 0774) == 0; - if (index != 0) copy.data[path_index] = temp; - } - #endif - - return result; -} - -DQN_API bool Dqn_Fs_Move(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) -{ - bool result = false; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src); - Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest); - - unsigned long flags = MOVEFILE_COPY_ALLOWED; - if (overwrite) { - flags |= MOVEFILE_REPLACE_EXISTING; - } - - result = MoveFileExW(src16.data, dest16.data, flags) != 0; - if (!result) { - Dqn_ThreadScratch inner_scratch = Dqn_Thread_GetScratch(scratch.arena); - Dqn_WinError error = Dqn_Win_LastError(inner_scratch.arena); - Dqn_Log_ErrorF("Failed to move the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s", - DQN_STR_FMT(src), - DQN_STR_FMT(dest), - DQN_STR_FMT(error.msg)); - } - #else - // See: https://github.com/gingerBill/gb/blob/master/gb.h - bool file_moved = true; - if (link(src.data, dest.data) == -1) { - // NOTE: Link can fail if we're trying to link across different volumes - // so we fall back to a binary directory. - file_moved |= Dqn_Fs_Copy(src, dest, overwrite); - } - - if (file_moved) - result = (unlink(src.data) != -1); // Remove original file - #endif - return result; -} - -DQN_API bool Dqn_Fs_Delete(Dqn_Str8 path) -{ - bool result = false; - if (!Dqn_Str8_IsValid(path)) - return result; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - if (path16.size) { - result = DeleteFileW(path16.data); - if (!result) - result = RemoveDirectoryW(path16.data); - } - #else - result = remove(path.data) == 0; - #endif - return result; -} - -// NOTE: R/W Entire File =========================================================================== -DQN_API char *Dqn_Fs_ReadCStr8(char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator) -{ - char *result = nullptr; - if (!path) - return result; - - if (path_size <= 0) - path_size = Dqn_CStr8_Size(path); - - (void)allocator; - (void)file_size; - - #if defined(DQN_OS_WIN32) - // NOTE: Convert to UTF16 ================================================== - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); - Dqn_Str8 path8 = Dqn_Str8_Init(path, path_size); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path8); - - // NOTE: Get the file handle =============================================== - void *file_handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, - /*DWORD dwDesiredAccess*/ GENERIC_READ, - /*DWORD dwShareMode*/ 0, - /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, - /*DWORD dwCreationDisposition*/ OPEN_EXISTING, - /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY, - /*HANDLE hTemplateFile*/ nullptr); - if (file_handle == INVALID_HANDLE_VALUE) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to open file for reading [file=%.*s, reason=%.*s]", DQN_STR_FMT(path8), DQN_STR_FMT(error.msg)); - return nullptr; - } - DQN_DEFER { CloseHandle(file_handle); }; - - // NOTE: Query the file size - // ------------------------------------------------------------------------- - LARGE_INTEGER win_file_size; - if (!GetFileSizeEx(file_handle, &win_file_size)) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to query file size [file=%.*s, reason=%.*s]", DQN_STR_FMT(path8), DQN_STR_FMT(error.msg)); - return nullptr; - } - - unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart; - if (!DQN_CHECKF(bytes_desired == win_file_size.QuadPart, - "Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) { - return nullptr; - } - - // NOTE: Read the file from disk - // ------------------------------------------------------------------------- - result = DQN_CAST(char *)Dqn_Allocator_Alloc(allocator, - bytes_desired, - alignof(char), - Dqn_ZeroMem_No); - unsigned long bytes_read = 0; - unsigned long read_result = ReadFile(/*HANDLE hFile*/ file_handle, - /*LPVOID lpBuffer*/ result, - /*DWORD nNumberOfBytesToRead*/ bytes_desired, - /*LPDWORD lpNumberOfByesRead*/ &bytes_read, - /*LPOVERLAPPED lpOverlapped*/ nullptr); - - if (read_result == 0) { - Dqn_Allocator_Dealloc(allocator, result, bytes_desired); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("'ReadFile' failed to load file to memory [file=%.*s, reason=%.*s]", DQN_STR_FMT(path8), DQN_STR_FMT(error.msg)); - return nullptr; - } - - if (bytes_read != bytes_desired) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Allocator_Dealloc(allocator, result, bytes_desired); - Dqn_Log_ErrorF("'ReadFile' failed to read all bytes into file [file=%.*s, bytes_desired=%u, bytes_read=%u, reason=%.*s]", - DQN_STR_FMT(path8), - bytes_desired, - bytes_read, - DQN_STR_FMT(error.msg)); - return nullptr; - } - - if (file_size) { - *file_size = Dqn_Safe_SaturateCastI64ToISize(bytes_read); - } - #else - Dqn_usize file_size_ = 0; - if (!file_size) - file_size = &file_size_; - - FILE *file_handle = fopen(path, "rb"); - if (!file_handle) { - Dqn_Log_ErrorF("Failed to open file '%s' using fopen\n", path); - return result; - } - - DQN_DEFER { fclose(file_handle); }; - fseek(file_handle, 0, SEEK_END); - *file_size = ftell(file_handle); - - if (DQN_CAST(long)(*file_size) == -1L) { - Dqn_Log_ErrorF("Failed to determine '%s' file size using ftell\n", path); - return result; - } - - rewind(file_handle); - result = DQN_CAST(char *)Dqn_Allocator_Alloc(allocator, - *file_size, - alignof(char), - Dqn_ZeroMem_No); - if (!result) { - Dqn_Log_ErrorF("Failed to allocate %zu bytes to read file '%s'\n", *file_size + 1, path); - return result; - } - - result[*file_size] = 0; - if (fread(result, DQN_CAST(size_t)(*file_size), 1, file_handle) != 1) { - Dqn_Allocator_Dealloc(allocator, result, *file_size); - Dqn_Log_ErrorF("Failed to read %zu bytes into buffer from '%s'\n", *file_size, path); - return result; - } - #endif - return result; -} - -DQN_API Dqn_Str8 Dqn_Fs_Read(Dqn_Str8 path, Dqn_Allocator allocator) -{ - Dqn_usize file_size = 0; - char *string = Dqn_Fs_ReadCStr8(path.data, path.size, &file_size, allocator); - Dqn_Str8 result = Dqn_Str8_Init(string, file_size); - return result; -} - -DQN_API bool Dqn_Fs_WriteCStr8(char const *path, Dqn_usize path_size, char const *buffer, Dqn_usize buffer_size) -{ - bool result = false; - if (!path || !buffer || buffer_size <= 0) - return result; - - #if defined(DQN_OS_WIN32) - if (path_size <= 0) - path_size = Dqn_CStr8_Size(path); - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 path8 = Dqn_Str8_Init(path, path_size); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path8); - - void *file_handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, - /*DWORD dwDesiredAccess*/ GENERIC_WRITE, - /*DWORD dwShareMode*/ 0, - /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, - /*DWORD dwCreationDisposition*/ CREATE_ALWAYS, - /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, - /*HANDLE hTemplateFile*/ nullptr); - - if (file_handle == INVALID_HANDLE_VALUE) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to open file for writing [file=%.*s, reason=%.*s]", DQN_STR_FMT(path8), DQN_STR_FMT(error.msg)); - return result; - } - DQN_DEFER { CloseHandle(file_handle); }; - - unsigned long bytes_written = 0; - result = WriteFile(file_handle, buffer, DQN_CAST(unsigned long)buffer_size, &bytes_written, nullptr /*lpOverlapped*/); - DQN_ASSERT(bytes_written == buffer_size); - return result; - #else - // TODO(dqn): Use OS apis - (void)path_size; - - FILE *file_handle = fopen(path, "w+b"); - if (!file_handle) { - Dqn_Log_ErrorF("Failed to 'fopen' to get the file handle [file=%s]", path); - return result; - } - DQN_DEFER { fclose(file_handle); }; - - result = fwrite(buffer, buffer_size, 1 /*count*/, file_handle) == 1 /*count*/; - if (!result) - Dqn_Log_ErrorF("Failed to 'fwrite' memory to file [file=%s]", path); - - return result; - #endif -} - -DQN_API bool Dqn_Fs_Write(Dqn_Str8 file_path, Dqn_Str8 buffer) -{ - bool result = Dqn_Fs_WriteCStr8(file_path.data, file_path.size, buffer.data, buffer.size); - return result; -} - -// NOTE: R/W Stream API ============================================================================ -DQN_API Dqn_FsFile Dqn_Fs_OpenFile(Dqn_Str8 path, Dqn_FsFileOpen open_mode, uint32_t access) -{ - Dqn_FsFile result = {}; - if (!Dqn_Str8_IsValid(path) || path.size <= 0) - return result; - - if ((access & ~Dqn_FsFileAccess_All) || ((access & Dqn_FsFileAccess_All) == 0)) { - DQN_INVALID_CODE_PATH; - return result; - } - - #if defined(DQN_OS_WIN32) - unsigned long create_flag = 0; - switch (open_mode) { - case Dqn_FsFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; - case Dqn_FsFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; - case Dqn_FsFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; - default: DQN_INVALID_CODE_PATH; return result; - } - - unsigned long access_mode = 0; - if (access & Dqn_FsFileAccess_AppendOnly) { - DQN_ASSERTF((access & ~Dqn_FsFileAccess_AppendOnly) == 0, - "Append can only be applied exclusively to the file, other access modes not permitted"); - access_mode = FILE_APPEND_DATA; - } else { - if (access & Dqn_FsFileAccess_Read) - access_mode |= GENERIC_READ; - if (access & Dqn_FsFileAccess_Write) - access_mode |= GENERIC_WRITE; - if (access & Dqn_FsFileAccess_Execute) - access_mode |= GENERIC_EXECUTE; - } - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, - /*DWORD dwDesiredAccess*/ access_mode, - /*DWORD dwShareMode*/ 0, - /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, - /*DWORD dwCreationDisposition*/ create_flag, - /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, - /*HANDLE hTemplateFile*/ nullptr); - - if (handle == INVALID_HANDLE_VALUE) { - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - result.error_size = - DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate(result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: %.*s for \"%.*s\"", - DQN_STR_FMT(error.msg), - DQN_STR_FMT(path)); - return result; - } - #else - if (access & Dqn_FsFileAccess_Execute) { - result.error_size = DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: execute access not supported for \"%.*s\"", - DQN_STR_FMT(path)); - DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen - return result; - } - - // NOTE: fopen interface is not as expressive as the Win32 - // We will fopen the file beforehand to setup the state/check for validity - // before closing and reopening it if valid with the correct request access - // permissions. - { - FILE *handle = nullptr; - switch (open_mode) { - case Dqn_FsFileOpen_CreateAlways: handle = fopen(path.data, "w"); break; - case Dqn_FsFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break; - case Dqn_FsFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; - default: DQN_INVALID_CODE_PATH; break; - } - if (!handle) { - result.error_size = DQN_CAST(uint16_t)Dqn_SNPrintFDotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: Could not open file in requested mode %d for \"%.*s\"", - open_mode, - DQN_STR_FMT(path)); - return result; - } - fclose(handle); - } - - char const *fopen_mode = nullptr; - if (access & Dqn_FsFileAccess_AppendOnly) { - fopen_mode = "a+"; - } else if (access & Dqn_FsFileAccess_Write) { - fopen_mode = "w+"; - } else if (access & Dqn_FsFileAccess_Read) { - fopen_mode = "r+"; - } - - FILE *handle = fopen(path.data, fopen_mode); - if (!handle) { - result.error_size = DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: Could not open file in fopen mode \"%s\" for \"%.*s\"", - fopen_mode, - DQN_STR_FMT(path)); - return result; - } - #endif - result.handle = handle; - return result; -} - -DQN_API bool Dqn_Fs_WriteFileBuffer(Dqn_FsFile *file, void const *buffer, Dqn_usize size) -{ - if (!file || !file->handle || !buffer || size <= 0 || file->error_size) - return false; - - bool result = true; - #if defined(DQN_OS_WIN32) - char const *end = DQN_CAST(char *)buffer + size; - for (char const *ptr = DQN_CAST(char const *)buffer; result && ptr != end; ) { - unsigned long write_size = DQN_CAST(unsigned long)DQN_MIN((unsigned long)-1, end - ptr); - unsigned long bytes_written = 0; - result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; - ptr += bytes_written; - } - - if (!result) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - file->error_size = - DQN_CAST(uint16_t) Dqn_SNPrintFDotTruncate(file->error, - DQN_ARRAY_UCOUNT(file->error), - "Write file failed (%u): %.*s", - error.code, - DQN_STR_FMT(error.msg)); - } - #else - result = fwrite(buffer, DQN_CAST(Dqn_usize)size, 1 /*count*/, DQN_CAST(FILE *)file->handle) == 1 /*count*/; - #endif - return result; -} - -DQN_API bool Dqn_Fs_WriteFile(Dqn_FsFile *file, Dqn_Str8 buffer) -{ - bool result = Dqn_Fs_WriteFileBuffer(file, buffer.data, buffer.size); - return result; -} - -DQN_API bool Dqn_Fs_WriteFileFV(Dqn_FsFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args) -{ - bool result = false; - if (!file || !fmt) - return result; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.allocator, fmt, args); - result = Dqn_Fs_WriteFileBuffer(file, buffer.data, buffer.size); - return result; -} - -DQN_API bool Dqn_Fs_WriteFileF(Dqn_FsFile *file, DQN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = Dqn_Fs_WriteFileFV(file, fmt, args); - va_end(args); - return result; -} - -DQN_API void Dqn_Fs_CloseFile(Dqn_FsFile *file) -{ - if (!file || !file->handle || file->error_size) - return; - - #if defined(DQN_OS_WIN32) - CloseHandle(file->handle); - #else - fclose(DQN_CAST(FILE *)file->handle); - #endif - *file = {}; -} -#endif // !defined(DQN_NO_FS) - -DQN_API bool Dqn_FsPath_AddRef(Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_Str8 path) -{ - if (!arena || !fs_path || !Dqn_Str8_IsValid(path)) - return false; - - if (path.size <= 0) - return true; - - Dqn_Str8 const delimiter_array[] = { - DQN_STR8("\\"), - DQN_STR8("/") - }; - for (;;) { - Dqn_Str8BinarySplitResult delimiter = Dqn_Str8_BinarySplitArray(path, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array)); - for (; delimiter.lhs.data; delimiter = Dqn_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DQN_ARRAY_UCOUNT(delimiter_array))) { - if (delimiter.lhs.size <= 0) - continue; - - Dqn_FsPathLink *link = Dqn_Arena_New(arena, Dqn_FsPathLink, Dqn_ZeroMem_Yes); - if (!link) - return false; - - link->string = delimiter.lhs; - link->prev = fs_path->tail; - if (fs_path->tail) { - fs_path->tail->next = link; - } else { - fs_path->head = link; - } - fs_path->tail = link; - fs_path->links_size += 1; - fs_path->string_size += delimiter.lhs.size; - } - - if (!delimiter.lhs.data) - break; - } - - return true; -} - -DQN_API bool Dqn_FsPath_Add(Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_Str8 path) -{ - Dqn_Str8 copy = Dqn_Str8_Copy(Dqn_Arena_Allocator(arena), path); - bool result = Dqn_FsPath_AddRef(arena, fs_path, copy); - return result; -} - -DQN_API bool Dqn_FsPath_AddF(Dqn_Arena *arena, Dqn_FsPath *fs_path, DQN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - Dqn_Str8 path = Dqn_Str8_InitFV(Dqn_Arena_Allocator(arena), fmt, args); - va_end(args); - bool result = Dqn_FsPath_AddRef(arena, fs_path, path); - return result; -} - -DQN_API bool Dqn_FsPath_Pop(Dqn_FsPath *fs_path) -{ - if (!fs_path) - return false; - - if (fs_path->tail) { - DQN_ASSERT(fs_path->head); - fs_path->links_size -= 1; - fs_path->string_size -= fs_path->tail->string.size; - fs_path->tail = fs_path->tail->prev; - if (fs_path->tail) { - fs_path->tail->next = nullptr; - } else { - fs_path->head = nullptr; - } - } else { - DQN_ASSERT(!fs_path->head); - } - - return true; -} - -DQN_API Dqn_Str8 Dqn_FsPath_Convert(Dqn_Arena *arena, Dqn_Str8 path) -{ - Dqn_FsPath fs_path = {}; - Dqn_FsPath_AddRef(arena, &fs_path, path); - Dqn_Str8 result = Dqn_FsPath_Build(arena, &fs_path); - return result; -} - -DQN_API Dqn_Str8 Dqn_FsPath_ConvertF(Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...) -{ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - va_list args; - va_start(args, fmt); - Dqn_Str8 path = Dqn_Str8_InitFV(scratch.allocator, fmt, args); - va_end(args); - Dqn_Str8 result = Dqn_FsPath_Convert(arena, path); - return result; -} - -DQN_API Dqn_Str8 Dqn_FsPath_BuildWithSeparator(Dqn_Arena *arena, Dqn_FsPath const *fs_path, Dqn_Str8 path_separator) -{ - Dqn_Str8 result = {}; - if (!fs_path || fs_path->links_size <= 0) - return result; - - // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' - Dqn_usize string_size = fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); - result = Dqn_Str8_Allocate(Dqn_Arena_Allocator(arena), string_size, Dqn_ZeroMem_No); - if (result.data) { - char *dest = result.data; - for (Dqn_FsPathLink *link = fs_path->head; link; link = link->next) { - Dqn_Str8 string = link->string; - DQN_MEMCPY(dest, string.data, string.size); - dest += string.size; - - if (link != fs_path->tail) { - DQN_MEMCPY(dest, path_separator.data, path_separator.size); - dest += path_separator.size; - } - } - } - - result.data[string_size] = 0; - return result; -} - -// NOTE: [$DATE] Dqn_Date ========================================================================== -DQN_API Dqn_DateHMSTime Dqn_Date_LocalTimeHMSNow() -{ - Dqn_DateHMSTime result = {}; -#if defined(DQN_OS_WIN32) - SYSTEMTIME sys_time; - GetLocalTime(&sys_time); - result.hour = DQN_CAST(uint8_t)sys_time.wHour; - result.minutes = DQN_CAST(uint8_t)sys_time.wMinute; - result.seconds = DQN_CAST(uint8_t)sys_time.wSecond; - - result.day = DQN_CAST(uint8_t)sys_time.wDay; - result.month = DQN_CAST(uint8_t)sys_time.wMonth; - result.year = DQN_CAST(int16_t)sys_time.wYear; -#else - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - - // NOTE: localtime_r is used because it is thread safe - // See: https://linux.die.net/man/3/localtime - // According to POSIX.1-2004, localtime() is required to behave as though - // tzset(3) was called, while localtime_r() does not have this requirement. - // For portable code tzset(3) should be called before localtime_r(). - for (static bool once = true; once; once = false) - tzset(); - - struct tm time = {}; - localtime_r(&ts.tv_sec, &time); - - result.hour = time.tm_hour; - result.minutes = time.tm_min; - result.seconds = time.tm_sec; - - result.day = DQN_CAST(uint8_t)time.tm_mday; - result.month = DQN_CAST(uint8_t)time.tm_mon + 1; - result.year = 1900 + DQN_CAST(int16_t)time.tm_year; -#endif - - return result; -} - -DQN_API Dqn_DateHMSTimeStr8 Dqn_Date_LocalTimeHMSStr8(Dqn_DateHMSTime time, char date_separator, char hms_separator) -{ - Dqn_DateHMSTimeStr8 result = {}; - result.hms_size = DQN_CAST(uint8_t) STB_SPRINTF_DECORATE(snprintf)(result.hms, - DQN_ARRAY_ICOUNT(result.hms), - "%02d%c%02d%c%02d", - time.hour, - hms_separator, - time.minutes, - hms_separator, - time.seconds); - - result.date_size = DQN_CAST(uint8_t) STB_SPRINTF_DECORATE(snprintf)(result.date, - DQN_ARRAY_ICOUNT(result.date), - "%d%c%02d%c%02d", - time.year, - date_separator, - time.month, - date_separator, - time.day); - - DQN_ASSERT(result.hms_size < DQN_ARRAY_UCOUNT(result.hms)); - DQN_ASSERT(result.date_size < DQN_ARRAY_UCOUNT(result.date)); - return result; -} - -DQN_API Dqn_DateHMSTimeStr8 Dqn_Date_LocalTimeHMSStr8Now(char date_separator, char hms_separator) -{ - Dqn_DateHMSTime time = Dqn_Date_LocalTimeHMSNow(); - Dqn_DateHMSTimeStr8 result = Dqn_Date_LocalTimeHMSStr8(time, date_separator, hms_separator); - return result; -} - -DQN_API uint64_t Dqn_Date_EpochTime() -{ - #if defined(DQN_OS_WIN32) - const uint64_t UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks" - const uint64_t TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds - - FILETIME file_time; - GetSystemTimeAsFileTime(&file_time); - - LARGE_INTEGER date_time; - date_time.u.LowPart = file_time.dwLowDateTime; - date_time.u.HighPart = file_time.dwHighDateTime; - uint64_t result = (date_time.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND; - #else - uint64_t result = time(nullptr); - #endif - return result; -} - -#if defined(DQN_OS_WIN32) -#if !defined(DQN_NO_WIN) -// NOTE: [$WIND] Dqn_Win =========================================================================== -DQN_API Dqn_WinError Dqn_Win_LastError(Dqn_Arena *arena) -{ - Dqn_WinError result = {}; - result.code = GetLastError(); - if (arena) { - unsigned long flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; - void *module_to_get_errors_from = nullptr; - - if (result.code >= 12000 && result.code <= 12175) { // WinINET Errors - flags |= FORMAT_MESSAGE_FROM_HMODULE; - module_to_get_errors_from = GetModuleHandleA("wininet.dll"); - } - - int32_t size = FormatMessageA(flags, - module_to_get_errors_from, // LPCVOID lpSource, - result.code, // unsigned long dwMessageId, - 0, // unsigned long dwLanguageId, - nullptr, // LPSTR lpBuffer, - 0, // unsigned long nSize, - nullptr); // va_list * Arguments - - if (size) { - Dqn_Str8 buffer = Dqn_Str8_Allocate(Dqn_Arena_Allocator(arena), size, Dqn_ZeroMem_No); - int32_t buffer_size = DQN_CAST(int32_t) buffer.size; - int32_t new_size = FormatMessageA(flags, - module_to_get_errors_from, // LPCVOID lpSource, - result.code, // unsigned long dwMessageId, - 0, // unsigned long dwLanguageId, - buffer.data, // LPSTR lpBuffer, - buffer_size, // unsigned long nSize, - nullptr); // va_list * Arguments - if (DQN_CHECK(new_size == size)) - result.msg = buffer; - } - } - return result; -} - -DQN_API void Dqn_Win_MakeProcessDPIAware() -{ - typedef bool SetProcessDpiAwareProc(void); - typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); - typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); - - // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It - // appears we can make this robust by just loading user32.dll and using - // GetProcAddress on the DPI function. If it's not there, we're on an old - // version of windows, so we can call an older version of the API. - void *lib_handle = LoadLibraryA("user32.dll"); - if (!lib_handle) - return; - - if (auto *set_process_dpi_awareness_context = DQN_CAST(SetProcessDpiAwarenessContextProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwarenessContext")) { - set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } else if (auto *set_process_dpi_awareness = DQN_CAST(SetProcessDpiAwarenessProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwareness")) { - set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); - } else if (auto *set_process_dpi_aware = DQN_CAST(SetProcessDpiAwareProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAware")) { - set_process_dpi_aware(); - } -} - -// NOTE: Windows UTF8 to Str16 ============================================== -DQN_API Dqn_Str16 Dqn_Win_Str8ToStr16(Dqn_Arena *arena, Dqn_Str8 src) -{ - Dqn_Str16 result = {}; - if (!arena || !Dqn_Str8_IsValid(src)) - return result; - - int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); - if (required_size <= 0) - return result; - - wchar_t *buffer = Dqn_Arena_NewArray(arena, wchar_t, required_size + 1, Dqn_ZeroMem_No); - if (!buffer) - return result; - - int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, buffer, required_size); - if (DQN_CHECK(chars_written == required_size)) { - result.data = buffer; - result.size = chars_written; - result.data[result.size] = 0; - } - return result; -} - -DQN_API int Dqn_Win_Str8ToStr16Buffer(Dqn_Str8 src, wchar_t *dest, int dest_size) -{ - int result = 0; - if (!Dqn_Str8_IsValid(src)) - return result; - - result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); - if (result <= 0 || result > dest_size || !dest) - return result; - - result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, dest, DQN_CAST(int)dest_size); - dest[DQN_MIN(result, dest_size - 1)] = 0; - return result; -} - -// NOTE: Windows Str16 To UTF8 ================================================================== -DQN_API int Dqn_Win_Str16ToStr8Buffer(Dqn_Str16 src, char *dest, int dest_size) -{ - int result = 0; - if (!Dqn_Str8_IsValid(src)) - return result; - - int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size); - if (src_size <= 0) - return result; - - result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); - if (result <= 0 || result > dest_size || !dest) - return result; - - result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DQN_CAST(int)dest_size, nullptr, nullptr); - dest[DQN_MIN(result, dest_size - 1)] = 0; - return result; -} - -DQN_API Dqn_Str8 Dqn_Win_Str16ToStr8(Dqn_Arena *arena, Dqn_Str16 src) -{ - Dqn_Str8 result = {}; - if (!arena || !Dqn_Str8_IsValid(src)) - return result; - - int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size); - if (src_size <= 0) - return result; - - int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); - if (required_size <= 0) - return result; - - // NOTE: Str8 allocate ensures there's one extra byte for - // null-termination already so no-need to +1 the required size - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(arena); - Dqn_Str8 buffer = Dqn_Str8_Allocate(Dqn_Arena_Allocator(arena), required_size, Dqn_ZeroMem_No); - if (!Dqn_Str8_IsValid(buffer)) - return result; - - int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DQN_CAST(int)buffer.size, nullptr, nullptr); - if (DQN_CHECK(chars_written == required_size)) { - result = buffer; - result.data[result.size] = 0; - } else { - // NOTE: Revert the temp memory because we encountered an error - Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); - } - - return result; -} - -// NOTE: Windows Executable Directory ========================================== -DQN_API Dqn_Str16 Dqn_Win_EXEPathW(Dqn_Arena *arena) -{ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_Str16 result = {}; - Dqn_usize module_size = 0; - wchar_t *module_path = nullptr; - do { - module_size += 256; - module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No); - if (!module_path) - return result; - module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size); - } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); - - Dqn_usize index_of_last_slash = 0; - for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--) - index_of_last_slash = module_path[index] == '\\' ? index : 0; - - result.data = Dqn_Arena_NewArray(arena, wchar_t, module_size + 1, Dqn_ZeroMem_No); - result.size = module_size; - DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); - result.data[result.size] = 0; - return result; -} - -DQN_API Dqn_Str16 Dqn_Win_EXEDirW(Dqn_Arena *arena) -{ - // TODO(doyle): Implement a Dqn_Str16_BinarySearchReverse - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_Str16 result = {}; - Dqn_usize module_size = 0; - wchar_t *module_path = nullptr; - do { - module_size += 256; - module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No); - if (!module_path) - return result; - module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size); - } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); - - Dqn_usize index_of_last_slash = 0; - for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--) - index_of_last_slash = module_path[index] == '\\' ? index : 0; - - result.data = Dqn_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, Dqn_ZeroMem_No); - result.size = index_of_last_slash; - DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); - result.data[result.size] = 0; - return result; -} - -DQN_API Dqn_Str8 Dqn_Win_WorkingDir(Dqn_Allocator allocator, Dqn_Str8 suffix) -{ - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); - Dqn_Str16 suffix16 = Dqn_Win_Str8ToStr16(scratch.arena, suffix); - Dqn_Str16 dir16 = Dqn_Win_WorkingDirW(Dqn_Arena_Allocator(scratch.arena), suffix16); - Dqn_Str8 result = Dqn_Win_Str16ToStr8(scratch.arena, dir16); - return result; -} - -DQN_API Dqn_Str16 Dqn_Win_WorkingDirW(Dqn_Allocator allocator, Dqn_Str16 suffix) -{ - DQN_ASSERT(suffix.size >= 0); - Dqn_Str16 result = {}; - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); - - // NOTE: required_size is the size required *including* the null-terminator - unsigned long required_size = GetCurrentDirectoryW(0, nullptr); - unsigned long desired_size = required_size + DQN_CAST(unsigned long) suffix.size; - - wchar_t *scratch_w_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, desired_size, Dqn_ZeroMem_No); - if (!scratch_w_path) - return result; - - unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, scratch_w_path); - if ((bytes_written_wo_null_terminator + 1) != required_size) { - // TODO(dqn): Error - return result; - } - - wchar_t *w_path = Dqn_Allocator_NewArray(allocator, wchar_t, desired_size, Dqn_ZeroMem_No); - if (!w_path) - return result; - - if (suffix.size) { - DQN_MEMCPY(w_path, scratch_w_path, sizeof(*scratch_w_path) * bytes_written_wo_null_terminator); - DQN_MEMCPY(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); - w_path[desired_size] = 0; - } - - result = Dqn_Str16{w_path, DQN_CAST(Dqn_usize)(desired_size - 1)}; - return result; -} - -DQN_API bool Dqn_Win_FolderWIterate(Dqn_Str16 path, Dqn_Win_FolderIteratorW *it) -{ - WIN32_FIND_DATAW find_data = {}; - if (it->handle) { - if (FindNextFileW(it->handle, &find_data) == 0) - return false; - } else { - it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ - FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ - &find_data, /*LPVOID lpFindFileData,*/ - FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ - nullptr, /*LPVOID lpSearchFilter,*/ - FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); - - if (it->handle == INVALID_HANDLE_VALUE) - return false; - } - - it->file_name_buf[0] = 0; - it->file_name = Dqn_Str16{it->file_name_buf, 0}; - - do { - if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) - continue; - - it->file_name.size = Dqn_CStr16_Size(find_data.cFileName); - DQN_ASSERT(it->file_name.size < (DQN_ARRAY_UCOUNT(it->file_name_buf) - 1)); - DQN_MEMCPY(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); - it->file_name_buf[it->file_name.size] = 0; - break; - } while (FindNextFileW(it->handle, &find_data) != 0); - - return it->file_name.size > 0; -} - -DQN_API bool Dqn_Win_FolderIterate(Dqn_Str8 path, Dqn_Win_FolderIterator *it) -{ - if (!Dqn_Str8_IsValid(path) || !it || path.size <= 0) - return false; - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Win_FolderIteratorW wide_it = {}; - Dqn_Str16 path16 = {}; - if (it->handle) { - wide_it.handle = it->handle; - } else { - bool needs_asterisks = Dqn_Str8_EndsWith(path, DQN_STR8("\\")) || - Dqn_Str8_EndsWith(path, DQN_STR8("/")); - bool has_glob = Dqn_Str8_EndsWith(path, DQN_STR8("\\*")) || - Dqn_Str8_EndsWith(path, DQN_STR8("/*")); - - Dqn_Str8 adjusted_path = path; - if (!has_glob) { - // NOTE: We are missing the glob for enumerating the files, we will - // add those characters in this branch, so overwrite the null - // character, add the glob and re-null terminate the buffer. - if (needs_asterisks) - adjusted_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s*", DQN_STR_FMT(path)); - else - adjusted_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/*", DQN_STR_FMT(path)); - } - - path16 = Dqn_Win_Str8ToStr16(scratch.arena, adjusted_path); - if (path16.size <= 0) // Conversion error - return false; - } - - bool result = Dqn_Win_FolderWIterate(path16, &wide_it); - it->handle = wide_it.handle; - if (result) { - int size = Dqn_Win_Str16ToStr8Buffer(wide_it.file_name, it->file_name_buf, DQN_ARRAY_UCOUNT(it->file_name_buf)); - it->file_name = Dqn_Str8_Init(it->file_name_buf, size); - } - - return result; -} -#endif // !defined(DQN_NO_WIN) - -#if !defined(DQN_NO_WINNET) -// NOTE: [$WINN] Dqn_WinNet ======================================================================== -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitCStr8(char const *url, int url_size) -{ - URL_COMPONENTSA components = {}; - components.dwStructSize = sizeof(components); - components.dwHostNameLength = url_size; - components.dwUrlPathLength = url_size; - - // Seperate the URL into bits and bobs - Dqn_WinNetHandle result = {}; - if (!InternetCrackUrlA(url, url_size, 0 /*flags*/, &components)) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("InternetCrackUrlA failed [reason=%.*s]", DQN_STR_FMT(error.msg)); - return result; - } - - if (components.lpszHostName == nullptr) { - Dqn_Log_ErrorF("Windows returnd a null host-name after trying to crack the URL.\n\nURL: %.*s", url_size, url); - return result; - } - - if (url[url_size] != 0) { - Dqn_Log_ErrorF("URL '%.*s' must be null-terminated", url_size, url); - return result; - } - - if (components.dwHostNameLength > (DQN_ARRAY_UCOUNT(result.host_name) - 1)) { - Dqn_Log_ErrorF("Host name is longer than the maximum supported [max=%zu]", DQN_ARRAY_UCOUNT(result.host_name) - 1); - return result; - } - - result.host_name_size = components.dwHostNameLength; - DQN_MEMCPY(result.host_name, components.lpszHostName, result.host_name_size); - result.host_name[result.host_name_size] = 0; - - result.url_size = components.dwUrlPathLength; - result.url = components.lpszUrlPath; - - // Create the Win32 networking handles we need - result.internet_open_handle = InternetOpenA("Generic/Win32", - INTERNET_OPEN_TYPE_PRECONFIG, - nullptr /*proxy*/, - nullptr /*proxy bypass*/, - 0 /*flags*/); - - result.internet_connect_handle = InternetConnectA(result.internet_open_handle, - result.host_name, - INTERNET_DEFAULT_HTTPS_PORT, - nullptr /*username*/, - nullptr /*password*/, - INTERNET_SERVICE_HTTP, - 0 /*flags*/, - 0 /*context*/); - - result.state = Dqn_WinNetHandleState_Initialised; - return result; -} - -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInit(Dqn_Str8 url) -{ - Dqn_WinNetHandle result = Dqn_Win_NetHandleInitCStr8(url.data, DQN_CAST(int)url.size); - return result; -} - -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethodCStr8(char const *url, int url_size, char const *http_method) -{ - Dqn_WinNetHandle result = Dqn_Win_NetHandleInitCStr8(url, url_size); - Dqn_Win_NetHandleSetHTTPMethod(&result, http_method); - return result; -} - -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethod(Dqn_Str8 url, Dqn_Str8 http_method) -{ - Dqn_WinNetHandle result = Dqn_Win_NetHandleInit(url); - Dqn_Win_NetHandleSetHTTPMethod(&result, http_method.data); - return result; -} - -DQN_API void Dqn_Win_NetHandleClose(Dqn_WinNetHandle *handle) -{ - if (!Dqn_Win_NetHandleIsValid(handle)) - return; - - InternetCloseHandle(handle->internet_open_handle); - InternetCloseHandle(handle->internet_connect_handle); - InternetCloseHandle(handle->http_handle); - handle->internet_open_handle = nullptr; - handle->internet_connect_handle = nullptr; - handle->http_handle = nullptr; -} - -DQN_API bool Dqn_Win_NetHandleIsValid(Dqn_WinNetHandle const *handle) -{ - bool result = handle && handle->state >= Dqn_WinNetHandleState_Initialised; - return result; -} - -DQN_API void Dqn_Win_NetHandleSetUserAgentCStr8(Dqn_WinNetHandle *handle, char const *user_agent, int user_agent_size) -{ - if (!Dqn_Win_NetHandleIsValid(handle)) - return; - - InternetSetOptionA(handle->internet_open_handle, INTERNET_OPTION_USER_AGENT, (void *)user_agent, user_agent_size); -} - -DQN_API bool Dqn_Win_NetHandleSetHTTPMethod(Dqn_WinNetHandle *handle, - char const *http_verb) -{ - if (!Dqn_Win_NetHandleIsValid(handle)) - return false; - - if (handle->http_handle) { - InternetCloseHandle(handle->http_handle); - handle->http_handle = nullptr; - handle->state = Dqn_WinNetHandleState_Initialised; - } - - if (handle->state != Dqn_WinNetHandleState_Initialised) - return false; - - handle->http_handle = HttpOpenRequestA(handle->internet_connect_handle, - http_verb, - handle->url, - nullptr /*http version*/, - nullptr /*referrer*/, - nullptr, - INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_SECURE, - 0 /*context*/); - handle->state = Dqn_WinNetHandleState_HttpMethodReady; - return true; -} - -DQN_API bool Dqn_Win_NetHandleSetRequestHeaderCStr8(Dqn_WinNetHandle *handle, - char const *header, - uint32_t header_size, - uint32_t mode) -{ - if (!Dqn_Win_NetHandleIsValid(handle) || !header || header_size <= 0) - return false; - - if (mode >= Dqn_WinNetHandleRequestHeaderFlag_Count) - return false; - - if (handle->state < Dqn_WinNetHandleState_HttpMethodReady) - return false; - - if (!DQN_CHECK(handle->http_handle)) - return false; - - unsigned long modifier = 0; - if (mode == 0) { - } else if (mode == Dqn_WinNetHandleRequestHeaderFlag_Add) { - modifier = HTTP_ADDREQ_FLAG_ADD; - } else if (mode == Dqn_WinNetHandleRequestHeaderFlag_AddIfNew) { - modifier = HTTP_ADDREQ_FLAG_ADD_IF_NEW; - } else if (mode == Dqn_WinNetHandleRequestHeaderFlag_Replace) { - modifier = HTTP_ADDREQ_FLAG_REPLACE; - } else { - Dqn_Log_ErrorF("Unrecognised flag for adding a request header"); - return false; - } - - bool result = HttpAddRequestHeadersA( - handle->http_handle, - header, - header_size, - modifier - ); - - return result; -} - -DQN_API bool Dqn_Win_NetHandleSetRequestHeaderStr8(Dqn_WinNetHandle *handle, Dqn_Str8 header, uint32_t mode) -{ - bool result = Dqn_Win_NetHandleSetRequestHeaderCStr8(handle, header.data, Dqn_Safe_SaturateCastISizeToUInt(header.size), mode); - return result; -} - -DQN_API Dqn_WinNetHandleResponse Dqn_Win_NetHandleSendRequest(Dqn_WinNetHandle *handle, Dqn_Allocator allocator, char const *post_data, unsigned long post_data_size) -{ - Dqn_WinNetHandleResponse result = {}; - if (!Dqn_Win_NetHandleIsValid(handle)) - return result; - - if (handle->state != Dqn_WinNetHandleState_HttpMethodReady) - return result; - - if (!handle->http_handle) - return result; - - if (!HttpSendRequestA(handle->http_handle, nullptr /*headers*/, 0 /*headers length*/, (char *)post_data, post_data_size)) { - handle->state = Dqn_WinNetHandleState_RequestFailed; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_WinError error = Dqn_Win_LastError(scratch.arena); - Dqn_Log_ErrorF("Failed to send request to %.*s [reason=%.*s]", - handle->host_name_size, - handle->host_name, - DQN_STR_FMT(error.msg)); - return result; - } - - handle->state = Dqn_WinNetHandleState_RequestGood; - unsigned long buffer_size = 0; - int query_result = HttpQueryInfoA(handle->http_handle, HTTP_QUERY_RAW_HEADERS_CRLF, nullptr, &buffer_size, nullptr); - if (!DQN_CHECK(query_result != ERROR_INSUFFICIENT_BUFFER)) - return result; - - result.raw_headers = Dqn_Str8_Allocate(allocator, buffer_size, Dqn_ZeroMem_No); - if (!result.raw_headers.data) - return result; - - query_result = HttpQueryInfoA(handle->http_handle, HTTP_QUERY_RAW_HEADERS_CRLF, result.raw_headers.data, &buffer_size, nullptr); - if (!query_result) { - Dqn_Allocator_Dealloc(allocator, result.raw_headers.data, buffer_size); - return result; - } - - Dqn_Str8 delimiter = DQN_STR8("\r\n"); - Dqn_usize splits_required = Dqn_Str8_Split(result.raw_headers, delimiter, nullptr, 0); - result.headers = Dqn_Allocator_NewArray(allocator, Dqn_Str8, splits_required, Dqn_ZeroMem_No); - result.headers_size = Dqn_Str8_Split(result.raw_headers, delimiter, result.headers, splits_required); - - bool found_content_type = false; - bool found_content_length = false; - for (Dqn_usize header_index = 0; header_index < result.headers_size; header_index++) { - Dqn_Str8 header = result.headers[header_index]; - - Dqn_Str8BinarySplitResult key_value_split = Dqn_Str8_BinarySplit(header, DQN_STR8(":")); - Dqn_Str8 value = key_value_split.lhs; - Dqn_Str8 key = key_value_split.rhs; - - key = Dqn_Str8_TrimWhitespaceAround(key); - value = Dqn_Str8_TrimWhitespaceAround(value); - - if (Dqn_Str8_EqInsensitive(key, DQN_STR8("Content-Type"))) { - DQN_ASSERT(!found_content_type); - if (!found_content_type) { - found_content_type = true; - result.content_type = value; - } - } else if (Dqn_Str8_EqInsensitive(key, DQN_STR8("Content-Length"))) { - DQN_ASSERT(!found_content_length); - if (!found_content_length) { - found_content_length = true; - result.content_length = Dqn_Str8_ToU64(value, 0 /*separator*/).value; - } - } - - if (found_content_type && found_content_length) - break; - } - - return result; -} - -DQN_API bool Dqn_Win_NetHandlePump(Dqn_WinNetHandle *handle, - char *dest, - int dest_size, - size_t *download_size) -{ - if (!Dqn_Win_NetHandleIsValid(handle)) - return false; - - if (handle->state != Dqn_WinNetHandleState_RequestGood) - return false; - - bool result = true; - unsigned long bytes_read; - if (InternetReadFile(handle->http_handle, dest, dest_size, &bytes_read)) { - if (bytes_read == 0) - result = false; - *download_size = bytes_read; - } else { - *download_size = 0; - result = false; - } - - if (!result) { - // NOTE: If it's false here, we've finished downloading/the pumping the - // handled finished. We can reset the handle state to allow the user to - // re-use this handle by calling the function again with new post data. - // IF they need to set a new URL/resource location then they need to - // make a new handle for that otherwise they can re-use this handle to - // hit that same end point. - handle->state = Dqn_WinNetHandleState_Initialised; - InternetCloseHandle(handle->http_handle); - handle->http_handle = nullptr; - } - - return result; -} - -struct Dqn_Win_NetChunk -{ - char data[DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE]; - size_t size; - Dqn_Win_NetChunk *next; -}; - -DQN_API char *Dqn_Win_NetHandlePumpCStr8(Dqn_WinNetHandle *handle, Dqn_Arena *arena, size_t *download_size) -{ - if (handle->state != Dqn_WinNetHandleState_RequestGood) - return nullptr; - - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - size_t total_size = 0; - Dqn_Win_NetChunk *first_chunk = nullptr; - for (Dqn_Win_NetChunk *last_chunk = nullptr;;) { - Dqn_Win_NetChunk *chunk = Dqn_Arena_New(scratch.arena, Dqn_Win_NetChunk, Dqn_ZeroMem_Yes); - bool pump_result = Dqn_Win_NetHandlePump(handle, chunk->data, DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE, &chunk->size); - if (chunk->size) { - total_size += chunk->size; - if (first_chunk) { - last_chunk->next = chunk; - last_chunk = chunk; - } else { - first_chunk = chunk; - last_chunk = chunk; - } - } - - if (!pump_result) - break; - } - - char *result = Dqn_Arena_NewArray(arena, char, total_size + 1 /*null-terminator*/, Dqn_ZeroMem_No); - char *result_ptr = result; - for (Dqn_Win_NetChunk *chunk = first_chunk; chunk; chunk = chunk->next) { - DQN_MEMCPY(result_ptr, chunk->data, chunk->size); - result_ptr += chunk->size; - } - - *download_size = total_size; - result[total_size] = 0; - return result; -} - -DQN_API Dqn_Str8 Dqn_Win_NetHandlePumpStr8(Dqn_WinNetHandle *handle, Dqn_Arena *arena) -{ - size_t size = 0; - char *download = Dqn_Win_NetHandlePumpCStr8(handle, arena, &size); - Dqn_Str8 result = Dqn_Str8_Init(download, size); - return result; -} - -DQN_API void Dqn_Win_NetHandlePumpToCRTFile(Dqn_WinNetHandle *handle, FILE *file) -{ - for (bool keep_pumping = true; keep_pumping;) { - char buffer[DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE]; - size_t buffer_size = 0; - keep_pumping = Dqn_Win_NetHandlePump(handle, buffer, sizeof(buffer), &buffer_size); - fprintf(file, "%.*s", (int)buffer_size, buffer); - } -} - -DQN_API char *Dqn_Win_NetHandlePumpToAllocCStr8(Dqn_WinNetHandle *handle, size_t *download_size) -{ - size_t total_size = 0; - Dqn_Win_NetChunk *first_chunk = nullptr; - for (Dqn_Win_NetChunk *last_chunk = nullptr;;) { - auto *chunk = DQN_CAST(Dqn_Win_NetChunk *)Dqn_VMem_Reserve(sizeof(Dqn_Win_NetChunk), Dqn_VMemCommit_Yes, Dqn_VMemPage_ReadWrite); - bool pump_result = Dqn_Win_NetHandlePump(handle, chunk->data, DQN_WIN_NET_HANDLE_DOWNLOAD_SIZE, &chunk->size); - if (chunk->size) { - total_size += chunk->size; - if (first_chunk) { - last_chunk->next = chunk; - last_chunk = chunk; - } else { - first_chunk = chunk; - last_chunk = chunk; - } - } - - if (!pump_result) - break; - } - - auto *result = DQN_CAST(char *)Dqn_VMem_Reserve(total_size * sizeof(char), Dqn_VMemCommit_Yes, Dqn_VMemPage_ReadWrite); - char *result_ptr = result; - for (Dqn_Win_NetChunk *chunk = first_chunk; chunk;) { - DQN_MEMCPY(result_ptr, chunk->data, chunk->size); - result_ptr += chunk->size; - - Dqn_Win_NetChunk *prev_chunk = chunk; - chunk = chunk->next; - Dqn_VMem_Release(prev_chunk, sizeof(*prev_chunk)); - } - - *download_size = total_size; - result[total_size] = 0; - return result; -} - -DQN_API Dqn_Str8 Dqn_Win_NetHandlePumpToAllocStr8(Dqn_WinNetHandle *handle) -{ - size_t download_size = 0; - char *download = Dqn_Win_NetHandlePumpToAllocCStr8(handle, &download_size); - Dqn_Str8 result = Dqn_Str8_Init(download, download_size); - return result; -} -#endif // !defined(DQN_NO_WINNET) -#endif // defined(DQN_OS_WIN32) - -// NOTE: [$OSYS] Dqn_OS ============================================================================ -DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size) -{ - if (!buffer || size < 0) - return false; - - if (size == 0) - return true; - -#if defined(DQN_OS_WIN32) - bool init = true; - Dqn_TicketMutex_Begin(&g_dqn_library->win32_bcrypt_rng_mutex); - if (!g_dqn_library->win32_bcrypt_rng_handle) - { - wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&g_dqn_library->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (!g_dqn_library->win32_bcrypt_rng_handle || init_status != 0) - { - Dqn_Log_ErrorF("Failed to initialise random number generator, error: %d", init_status); - init = false; - } - } - Dqn_TicketMutex_End(&g_dqn_library->win32_bcrypt_rng_mutex); - - if (!init) - return false; - - long gen_status = BCryptGenRandom(g_dqn_library->win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/); - if (gen_status != 0) - { - Dqn_Log_ErrorF("Failed to generate random bytes: %d", gen_status); - return false; - } - -#else - DQN_ASSERTF(size <= 32, - "We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix guarantees 32 " - "bytes can always be fulfilled by this system at a time"); - // TODO(doyle): https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c - // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html - int read_bytes = 0; - do - { - read_bytes = getrandom(buffer, size, 0); - - // NOTE: EINTR can not be triggered if size <= 32 bytes - } while (read_bytes != size || errno == EAGAIN); -#endif - - return true; -} - -#if (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) -DQN_API Dqn_Str8 Dqn_OS_EXEPath(Dqn_Arena *arena) -{ - Dqn_Str8 result = {}; - if (!arena) - return result; - - #if defined(DQN_OS_WIN32) - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_Str16 exe_dir16 = Dqn_Win_EXEPathW(scratch.arena); - result = Dqn_Win_Str16ToStr8(arena, exe_dir16); - #else - int required_size_wo_null_terminator = 0; - for (int try_size = 128;; try_size *= 2) { - auto scoped_arena = Dqn_ArenaTempMemoryScope(arena); - char *try_buf = Dqn_Arena_NewArray(arena, char, try_size, Dqn_ZeroMem_No); - int bytes_written = readlink("/proc/self/exe", try_buf, try_size); - if (bytes_written == -1) { - // Failed, we're unable to determine the executable directory - break; - } else if (bytes_written == try_size) { - // Try again, if returned size was equal- we may of prematurely - // truncated according to the man pages - continue; - } else { - // readlink will give us the path to the executable. Once we - // determine the correct buffer size required to get the full file - // path, we do some post-processing on said string and extract just - // the directory. - - // TODO(dqn): It'd be nice if there's some way of keeping this - // try_buf around, memcopy the byte and trash the try_buf from the - // arena. Instead we just get the size and redo the call one last - // time after this "calculate" step. - DQN_ASSERTF(bytes_written < try_size, "bytes_written can never be greater than the try size, function writes at most try_size"); - required_size_wo_null_terminator = bytes_written; - break; - } - } - - if (required_size_wo_null_terminator) { - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(arena); - char *exe_path = Dqn_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, Dqn_ZeroMem_No); - exe_path[required_size_wo_null_terminator] = 0; - - int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator); - if (bytes_written == -1) { - // Note that if read-link fails again can be because there's - // a potential race condition here, our exe or directory could have - // been deleted since the last call, so we need to be careful. - Dqn_Arena_EndTempMemory(temp_memory, true /*cancel*/); - } else { - result = Dqn_Str8_Init(exe_path, required_size_wo_null_terminator); - } - } - #endif - return result; -} - -DQN_API Dqn_Str8 Dqn_OS_EXEDir(Dqn_Arena *arena) -{ - Dqn_Str8 result = {}; - if (!arena) - return result; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena); - Dqn_Str8 exe_path = Dqn_OS_EXEPath(scratch.arena); - Dqn_Str8 separators[] = {DQN_STR8("/"), DQN_STR8("\\")}; - Dqn_Str8BinarySplitResult split = Dqn_Str8_BinarySplitReverseArray(exe_path, separators, DQN_ARRAY_UCOUNT(separators)); - result = Dqn_Str8_Copy(Dqn_Arena_Allocator(arena), split.lhs); - return result; -} -#endif // (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) - -DQN_API void Dqn_OS_SleepMs(Dqn_uint milliseconds) -{ - #if defined(DQN_OS_WIN32) - Sleep(milliseconds); - #else - struct timespec ts; - ts.tv_sec = milliseconds / 1000; - ts.tv_nsec = (milliseconds % 1000) * 1'000'000; - nanosleep(&ts, nullptr); - #endif -} - -DQN_FILE_SCOPE void Dqn_OS_PerfCounter_Init() -{ - // TODO(doyle): Move this to Dqn_Library_Init - #if defined(DQN_OS_WIN32) - if (g_dqn_library->win32_qpc_frequency.QuadPart == 0) - QueryPerformanceFrequency(&g_dqn_library->win32_qpc_frequency); - #endif -} - -DQN_API Dqn_f64 Dqn_OS_PerfCounterS(uint64_t begin, uint64_t end) -{ - Dqn_OS_PerfCounter_Init(); - uint64_t ticks = end - begin; - #if defined(DQN_OS_WIN32) - Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; - #else - Dqn_f64 result = ticks / 1'000'000'000; - #endif - return result; -} - -DQN_API Dqn_f64 Dqn_OS_PerfCounterMs(uint64_t begin, uint64_t end) -{ - Dqn_OS_PerfCounter_Init(); - uint64_t ticks = end - begin; - #if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; - #else - Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)1'000'000; - #endif - return result; -} - -DQN_API Dqn_f64 Dqn_OS_PerfCounterMicroS(uint64_t begin, uint64_t end) -{ - Dqn_OS_PerfCounter_Init(); - uint64_t ticks = end - begin; - #if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; - #else - Dqn_f64 result = ticks / DQN_CAST(Dqn_f64)1'000; - #endif - return result; -} - -DQN_API Dqn_f64 Dqn_OS_PerfCounterNs(uint64_t begin, uint64_t end) -{ - Dqn_OS_PerfCounter_Init(); - uint64_t ticks = end - begin; - #if defined(DQN_OS_WIN32) - Dqn_f64 result = (ticks * 1'000'000'000) / DQN_CAST(Dqn_f64)g_dqn_library->win32_qpc_frequency.QuadPart; - #else - Dqn_f64 result = ticks; - #endif - return result; -} - -DQN_API uint64_t Dqn_OS_PerfCounterFrequency() -{ - uint64_t result = 0; - #if defined(DQN_OS_WIN32) - LARGE_INTEGER integer = {}; - QueryPerformanceFrequency(&integer); - result = integer.QuadPart; - #else - // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which - // increments at nanosecond granularity. - result = 1'000'000'000; - #endif - return result; -} - -DQN_API uint64_t Dqn_OS_PerfCounterNow() -{ - uint64_t result = 0; - #if defined(DQN_OS_WIN32) - LARGE_INTEGER integer = {}; - QueryPerformanceCounter(&integer); - result = integer.QuadPart; - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - result = DQN_CAST(uint64_t)ts.tv_sec * 1'000'000'000 + DQN_CAST(uint64_t)ts.tv_nsec; - #endif - - return result; -} - -DQN_API Dqn_OSTimer Dqn_OS_TimerBegin() -{ - Dqn_OSTimer result = {}; - result.start = Dqn_OS_PerfCounterNow(); - return result; -} - -DQN_API void Dqn_OS_TimerEnd(Dqn_OSTimer *timer) -{ - timer->end = Dqn_OS_PerfCounterNow(); -} - -DQN_API Dqn_f64 Dqn_OS_TimerS(Dqn_OSTimer timer) -{ - Dqn_f64 result = Dqn_OS_PerfCounterS(timer.start, timer.end); - return result; -} - -DQN_API Dqn_f64 Dqn_OS_TimerMs(Dqn_OSTimer timer) -{ - Dqn_f64 result = Dqn_OS_PerfCounterMs(timer.start, timer.end); - return result; -} - -DQN_API Dqn_f64 Dqn_OS_TimerMicroS(Dqn_OSTimer timer) -{ - Dqn_f64 result = Dqn_OS_PerfCounterMicroS(timer.start, timer.end); - return result; -} - -DQN_API Dqn_f64 Dqn_OS_TimerNs(Dqn_OSTimer timer) -{ - Dqn_f64 result = Dqn_OS_PerfCounterNs(timer.start, timer.end); - return result; -} - -DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) -{ - uint64_t os_frequency = Dqn_OS_PerfCounterFrequency(); - uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; - uint64_t tsc_begin = Dqn_CPU_TSC(); - uint64_t result = 0; - if (tsc_begin) { - uint64_t os_elapsed = 0; - for (uint64_t os_begin = Dqn_OS_PerfCounterNow(); os_elapsed < os_target_elapsed; ) - os_elapsed = Dqn_OS_PerfCounterNow() - os_begin; - uint64_t tsc_end = Dqn_CPU_TSC(); - uint64_t tsc_elapsed = tsc_end - tsc_begin; - result = tsc_elapsed / os_elapsed * os_frequency; - } - return result; -} - -// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= -Dqn_ThreadScratch::Dqn_ThreadScratch(Dqn_ThreadContext *context, uint8_t context_index) -{ - allocator = context->scratch_allocators[context_index]; - arena = context->scratch_arenas[context_index]; - temp_memory = Dqn_Arena_BeginTempMemory(arena); - destructed = false; -} - -Dqn_ThreadScratch::~Dqn_ThreadScratch() -{ - DQN_ASSERT(destructed == false); - Dqn_Arena_EndTempMemory(temp_memory, /*cancel*/ false); - destructed = true; -} - -DQN_API uint32_t Dqn_Thread_GetID() -{ - #if defined(DQN_OS_WIN32) - unsigned long result = GetCurrentThreadId(); - #else - pid_t result = gettid(); - DQN_ASSERT(gettid() >= 0); - #endif - return (uint32_t)result; -} - -DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext() -{ - DQN_THREAD_LOCAL Dqn_ThreadContext result = {}; - if (!result.init) { - result.init = true; - Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog; - DQN_HARD_ASSERTF(g_dqn_library && g_dqn_library->lib_init, "Library must be initialised by calling Dqn_Library_Init()"); - - // NOTE: Setup scratch arenas - for (uint8_t index = 0; index < DQN_ARRAY_UCOUNT(result.scratch_arenas); index++) { - result.scratch_arenas[index] = Dqn_ArenaCatalog_AllocF(catalog, - DQN_MEGABYTES(16) /*size*/, - DQN_KILOBYTES(64) /*commit*/, - "Thread %u Scratch Arena %u", - Dqn_Thread_GetID(), - index); - result.scratch_allocators[index] = Dqn_Arena_Allocator(result.scratch_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(void const *conflict_arena) -{ - Dqn_ThreadContext *context = Dqn_Thread_GetContext(); - uint8_t context_index = (uint8_t)-1; - for (uint8_t index = 0; index < DQN_ARRAY_UCOUNT(context->scratch_arenas); index++) { - Dqn_Arena *arena = context->scratch_arenas[index]; - if (!conflict_arena || arena != conflict_arena) { - context_index = index; - break; - } - } - - DQN_ASSERT(context_index != (uint8_t)-1); - return Dqn_ThreadScratch(context, context_index); -} diff --git a/dqn_platform.h b/dqn_platform.h deleted file mode 100644 index 38da5b1..0000000 --- a/dqn_platform.h +++ /dev/null @@ -1,497 +0,0 @@ -#if !defined(DQN_NO_FS) -#if defined(DQN_OS_WIN32) && defined(DQN_NO_WIN) - #error "Filesystem APIs requires Windows API, DQN_NO_WIN must not be defined" -#endif -// NOTE: [$FSYS] Dqn_Fs ============================================================================ -// NOTE: FS Manipulation =========================================================================== -// TODO(dqn): We should have a Dqn_Str8 interface and a CStr8 interface -// -// NOTE: API ======================================================================================= -// @proc Dqn_FsDelete -// @desc Delete the item specified at the path. This function *CAN* not delete directories unless -// the directory is empty. -// @return True if deletion was successful, false otherwise - -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; -}; - -DQN_API bool Dqn_Fs_Exists (Dqn_Str8 path); -DQN_API bool Dqn_Fs_DirExists(Dqn_Str8 path); -DQN_API Dqn_FsInfo Dqn_Fs_GetInfo (Dqn_Str8 path); -DQN_API bool Dqn_Fs_Copy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); -DQN_API bool Dqn_Fs_MakeDir (Dqn_Str8 path); -DQN_API bool Dqn_Fs_Move (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite); -DQN_API bool Dqn_Fs_Delete (Dqn_Str8 path); - -// NOTE: R/W Entire File =========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Fs_WriteStr8, Dqn_Fs_WriteCStr8 -// @desc Write the string to a file at the path overwriting if necessary. - -// @proc Dqn_Fs_ReadStr8, Dqn_Fs_ReadCStr8 -// @desc Read the file at the path to a string. - -DQN_API bool Dqn_Fs_WriteCStr8(char const *file_path, Dqn_usize file_path_size, char const *buffer, Dqn_usize buffer_size); -DQN_API bool Dqn_Fs_Write (Dqn_Str8 file_path, Dqn_Str8 buffer); -DQN_API char *Dqn_Fs_ReadCStr8 (char const *path, Dqn_usize path_size, Dqn_usize *file_size, Dqn_Allocator allocator); -DQN_API Dqn_Str8 Dqn_Fs_Read (Dqn_Str8 path, Dqn_Allocator allocator); - -// NOTE: R/W Stream API ============================================================================ -// NOTE: API ======================================================================================= -// @proc Dqn_Fs_OpenFile -// @desc Open a handle to the file - -// @proc Dqn_Fs_WriteFile -// @desc Append to the file specified by the handle with the given buffer. - -// @proc Dqn_Fs_CloseFile -// @desc Close the file at specified by the handle - -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 access 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_Str8 path, Dqn_FsFileOpen open_mode, uint32_t access); -DQN_API bool Dqn_Fs_WriteFileBuffer(Dqn_FsFile *file, void const *data, Dqn_usize size); -DQN_API bool Dqn_Fs_WriteFile (Dqn_FsFile *file, Dqn_Str8 buffer); -DQN_API bool Dqn_Fs_WriteFileFV (Dqn_FsFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API bool Dqn_Fs_WriteFileF (Dqn_FsFile *file, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API void Dqn_Fs_CloseFile (Dqn_FsFile *file); -#endif // !defined(DQN_NO_FS) - -// NOTE: File system paths ========================================================================= -// Helper data structure for building paths suitable for OS consumption. -// -// NOTE: API ======================================================================================= -// @proc Dqn_FsPath_AddRef, Dqn_FsPath_Add -// @desc Append a path to the file path. The passed in path can be specify -// both a single level or multiple directories with different path separators. -// The path will be decomposed into individual sections in the function. -// -// For example passing -// - "path/to/your/desired/folder" is valid -// - "path" is valid -// - "path/to\your/desired\folder" is valid - -// @proc Dqn_FsPath_Pop -// @desc Remove the last appended path level from the current path stored in -// the FsPath. -// -// For example "path/to/your/desired/folder" popped produces -// "path/to/your/desired" - -// @proc Dqn_FsPath_Convert -// @desc Convert the path specified in the string to the OS native separated -// path. - -#if !defined(Dqn_FsPathOSSeperator) - #if defined(DQN_OS_WIN32) - #define Dqn_FsPathOSSeperator "\\" - #else - #define Dqn_FsPathOSSeperator "/" - #endif - #define Dqn_FsPathOSSeperatorString DQN_STR8(Dqn_FsPathOSSeperator) -#endif - -struct Dqn_FsPathLink -{ - Dqn_Str8 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_Str8 path); -DQN_API bool Dqn_FsPath_Add (Dqn_Arena *arena, Dqn_FsPath *fs_path, Dqn_Str8 path); -DQN_API bool Dqn_FsPath_AddF (Dqn_Arena *arena, Dqn_FsPath *fs_path, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API bool Dqn_FsPath_Pop (Dqn_FsPath *fs_path); -DQN_API Dqn_Str8 Dqn_FsPath_BuildWithSeparator(Dqn_Arena *arena, Dqn_FsPath const *fs_path, Dqn_Str8 path_separator); -DQN_API Dqn_Str8 Dqn_FsPath_Convert (Dqn_Arena *arena, Dqn_Str8 path); -DQN_API Dqn_Str8 Dqn_FsPath_ConvertF (Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...); -#define Dqn_FsPath_BuildFwdSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STR8("/")) -#define Dqn_FsPath_BuildBackSlash(arena, fs_path) Dqn_FsPath_BuildWithSeparator(arena, fs_path, DQN_STR8("\\")) - -#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 - -// NOTE: [$DATE] Dqn_Date ========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Date_EpochTime -// @desc Produce the time elapsed since the Unix epoch -// (e.g. 1970-01-01T00:00:00Z) in seconds - -struct Dqn_DateHMSTimeStr8 -{ - 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; -}; - -DQN_API Dqn_DateHMSTime Dqn_Date_LocalTimeHMSNow (); -DQN_API Dqn_DateHMSTimeStr8 Dqn_Date_LocalTimeHMSStr8Now(char date_separator = '-', char hms_separator = ':'); -DQN_API Dqn_DateHMSTimeStr8 Dqn_Date_LocalTimeHMSStr8 (Dqn_DateHMSTime time, char date_separator = '-', char hms_separator = ':'); -DQN_API uint64_t Dqn_Date_EpochTime (); - -#if defined(DQN_OS_WIN32) -#if !defined(DQN_NO_WIN) -// NOTE: [$WIND] Dqn_Win =========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Win_LastErrorToBuffer, Dqn_Win_LastError -// @desc Retrieve the latest error code and message Windows produced for the -// most recent Win32 API call. - -// @proc Dqn_Win_MakeProcessDPIAware -// @desc 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. - -struct Dqn_WinError -{ - unsigned long code; - Dqn_Str8 msg; -}; -DQN_API Dqn_WinError Dqn_Win_LastError(Dqn_Arena *arena); -DQN_API void Dqn_Win_MakeProcessDPIAware(); - -// NOTE: Windows Str8 <-> Str16 =========================================== -// Convert a UTF8 <-> 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. -// -// Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) -// written/required for conversion. 0 if there was a conversion error and can be -// queried using 'Dqn_Win_LastError' - -DQN_API Dqn_Str16 Dqn_Win_Str8ToStr16 (Dqn_Arena *arena, Dqn_Str8 src); -DQN_API int Dqn_Win_Str8ToStr16Buffer(Dqn_Str16 src, char *dest, int dest_size); -DQN_API Dqn_Str8 Dqn_Win_Str16ToStr8 (Dqn_Arena *arena, Dqn_Str16 src); -DQN_API int Dqn_Win_Str16ToStr8Buffer(Dqn_Str16 src, char *dest, int dest_size); - -// NOTE: Path navigation =========================================================================== -// NOTE: API ======================================================================================= -// @proc Dqn_Win_EXEDirW, Dqn_Win_EXEDirWArena -// @desc 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. - -// @proc Dqn_Win_WorkingDir, Dqn_Win_WorkingDirW -// @param[in] suffix (Optional) A suffix to append to the current working directory - -// @proc Dqn_Win_FolderIterate, Dqn_Win_FolderWIterate -// @desc Iterate the files in the specified folder at the path -#if 0 - for (Dqn_WinFolderIterator it = {}; Dqn_Win_FolderIterate("C:/your/path/", &it); ) { - printf("%.*s\n", DQN_STRING_FMT(it.file_name)); - } -#endif - -struct Dqn_Win_FolderIteratorW -{ - void *handle; - Dqn_Str16 file_name; - wchar_t file_name_buf[512]; -}; - -struct Dqn_Win_FolderIterator -{ - void *handle; - Dqn_Str8 file_name; - char file_name_buf[512]; -}; - -DQN_API Dqn_Str16 Dqn_Win_EXEPathW (Dqn_Arena *arena); -DQN_API Dqn_Str16 Dqn_Win_EXEDirW (Dqn_Arena *arena); -DQN_API Dqn_Str8 Dqn_Win_WorkingDir (Dqn_Allocator allocator, Dqn_Str8 suffix); -DQN_API Dqn_Str16 Dqn_Win_WorkingDirW (Dqn_Allocator allocator, Dqn_Str16 suffix); -DQN_API bool Dqn_Win_FolderIterate (Dqn_Str8 path, Dqn_Win_FolderIterator *it); -DQN_API bool Dqn_Win_FolderWIterate(Dqn_Str16 path, Dqn_Win_FolderIteratorW *it); -#endif // !defined(DQN_NO_WIN) - -#if !defined(DQN_NO_WINNET) -// NOTE: [$WINN] Dqn_WinNet ======================================================================== -// 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 -// -// NOTE: API ======================================================================================= -// @proc Dqn_Win_NetHandleInitHTTPMethod, Dqn_Win_NetHandleInitHTTPMethodCStr8 -// @desc 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 - -// @proc Dqn_Win_NetHandleSetHTTPMethod -// @desc 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. - -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; -}; - -enum Dqn_WinNetHandleRequestHeaderFlag -{ - Dqn_WinNetHandleRequestHeaderFlag_Add, - Dqn_WinNetHandleRequestHeaderFlag_AddIfNew, - Dqn_WinNetHandleRequestHeaderFlag_Replace, - Dqn_WinNetHandleRequestHeaderFlag_Count, -}; - -struct Dqn_WinNetHandleResponse -{ - Dqn_Str8 raw_headers; - Dqn_Str8 *headers; - Dqn_usize headers_size; - - // NOTE: Headers pulled from the 'raw_headers' for convenience - uint64_t content_length; - Dqn_Str8 content_type; -}; - -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitCStr8 (char const *url, int url_size); -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInit (Dqn_Str8 url); -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethodCStr8 (char const *url, int url_size, char const *http_method); -DQN_API Dqn_WinNetHandle Dqn_Win_NetHandleInitHTTPMethod (Dqn_Str8 url, Dqn_Str8 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_NetHandleSetUserAgentCStr8 (Dqn_WinNetHandle *handle, char const *user_agent, int user_agent_size); -DQN_API bool Dqn_Win_NetHandleSetHTTPMethod (Dqn_WinNetHandle *handle, char const *method); -DQN_API bool Dqn_Win_NetHandleSetRequestHeaderCStr8(Dqn_WinNetHandle *handle, char const *header, int header_size, uint32_t mode); -DQN_API bool Dqn_Win_NetHandleSetRequestHeaderStr8 (Dqn_WinNetHandle *handle, Dqn_Str8 header, uint32_t mode); -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_NetHandlePumpCStr8 (Dqn_WinNetHandle *handle, Dqn_Arena *arena, size_t *download_size); -DQN_API Dqn_Str8 Dqn_Win_NetHandlePumpStr8 (Dqn_WinNetHandle *handle, Dqn_Arena *arena); -DQN_API void Dqn_Win_NetHandlePumpToCRTFile (Dqn_WinNetHandle *handle, FILE *file); -DQN_API char * Dqn_Win_NetHandlePumpToAllocCStr8 (Dqn_WinNetHandle *handle, size_t *download_size); -DQN_API Dqn_Str8 Dqn_Win_NetHandlePumpToAllocStr8 (Dqn_WinNetHandle *handle); -#endif // !defined(DQN_NO_WINNET) -#endif // defined(DQN_OS_WIN32) - -// NOTE: [$OSYS] Dqn_OS ============================================================================ -// NOTE: API ======================================================================================= -// @proc Dqn_OS_SecureRNGBytes -// @desc Generate cryptographically secure bytes - -// @proc Dqn_OS_EXEDir -// @desc Retrieve the executable directory without the trailing '/' or -// ('\' for windows). If this fails an empty string is returned. - -// @proc Dqn_OS_PerfCounterFrequency -// @desc Get the number of ticks in the performance counter per second for the -// operating system you're running on. This value can be used to calculate -// duration from OS performance counter ticks. - -// @proc Dqn_OS_EstimateTSCPerSecond -// @desc Estimate how many timestamp count's (TSC) there are per second. TSC -// is evaluated by calling __rdtsc() or the equivalent on the platform. This -// value can be used to convert TSC durations into seconds. -// -// @param duration_ms_to_gauge_tsc_frequency How many milliseconds to spend -// measuring the TSC rate of the current machine. 100ms is sufficient to -// produce a fairly accurate result with minimal blocking in applications. -// -// This may return 0 if querying the CPU timestamp counter is not supported -// on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). - -/// Record time between two time-points using the OS's performance counter. -struct Dqn_OSTimer -{ - uint64_t start; - uint64_t end; -}; - -DQN_API bool Dqn_OS_SecureRNGBytes (void *buffer, uint32_t size); -#if (defined(DQN_OS_WIN32) && !defined(DQN_NO_WIN)) || !defined(DQN_OS_WIN32) -DQN_API Dqn_Str8 Dqn_OS_EXEPath (Dqn_Arena *arena); -DQN_API Dqn_Str8 Dqn_OS_EXEDir (Dqn_Arena* arena); -#endif -DQN_API void Dqn_OS_SleepMs (Dqn_uint milliseconds); -DQN_API uint64_t Dqn_OS_PerfCounterNow (); -DQN_API uint64_t Dqn_OS_PerfCounterFrequency(); -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); -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); -DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency); - -// NOTE: [$TCTX] Dqn_ThreadContext ================================================================= -// 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 - -struct Dqn_ThreadContext -{ - Dqn_b32 init; - - // Scratch memory arena's for the calling thread - Dqn_Arena *scratch_arenas[2]; - - // Allocators that use the corresponding arena from the thread context. - // Provided for convenience when interfacing with allocator interfaces. - Dqn_Allocator scratch_allocators[2]; -}; - -struct Dqn_ThreadScratch -{ - Dqn_ThreadScratch(Dqn_ThreadContext *context, uint8_t context_index); - ~Dqn_ThreadScratch(); - - Dqn_Allocator allocator; - Dqn_Arena *arena; - Dqn_b32 destructed; - Dqn_ArenaTempMemory temp_memory; -}; - -// NOTE: Context =================================================================================== -DQN_API uint32_t Dqn_Thread_GetID(); -DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext(); -DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch(void const *conflict_arena); diff --git a/dqn_strings.cpp b/dqn_string.cpp similarity index 68% rename from dqn_strings.cpp rename to dqn_string.cpp index ffaa61a..190c71c 100644 --- a/dqn_strings.cpp +++ b/dqn_string.cpp @@ -1,9 +1,24 @@ -// NOTE: [$CSTR] Dqn_CStr8 ====================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ __$$\ +// $$ / \__| $$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__| +// \$$$$$$\ $$ | $$$$$$$ | $$ | $$ $$\$$ |$$ |$$$$\ +// \____$$\ $$ | $$ __$$< $$ | $$ \$$$$ |$$ |\_$$ | +// $$\ $$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | +// \$$$$$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |\$$$$$$ | +// \______/ \__| \__| \__|\______|\__| \__| \______/ +// +// dqn_string.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// NOTE: [$CSTR] Dqn_CStr8 ///////////////////////////////////////////////////////////////////////// DQN_API Dqn_usize Dqn_CStr8_FSize(DQN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); - Dqn_usize result = STB_SPRINTF_DECORATE(vsnprintf)(nullptr, 0, fmt, args); + Dqn_usize result = DQN_VSNPRINTF(nullptr, 0, fmt, args); va_end(args); return result; } @@ -12,7 +27,7 @@ DQN_API Dqn_usize Dqn_CStr8_FVSize(DQN_FMT_ATTRIB char const *fmt, va_list args) { va_list args_copy; va_copy(args_copy, args); - Dqn_usize result = STB_SPRINTF_DECORATE(vsnprintf)(nullptr, 0, fmt, args_copy); + Dqn_usize result = DQN_VSNPRINTF(nullptr, 0, fmt, args_copy); va_end(args_copy); return result; } @@ -38,17 +53,32 @@ DQN_API Dqn_usize Dqn_CStr16_Size(wchar_t const *src) return result; } -// NOTE: [$STR8] Dqn_Str8 ======================================================================= +// NOTE: [$STR6] Dqn_Str16 ///////////////////////////////////////////////////////////////////////// +DQN_API bool operator==(Dqn_Str16 const &lhs, Dqn_Str16 const &rhs) +{ + bool result = false; + if (lhs.size == rhs.size) + result = DQN_MEMCMP(lhs.data, rhs.data, lhs.size * sizeof(*lhs.data)) == 0; + return result; +} + +DQN_API bool operator!=(Dqn_Str16 const &lhs, Dqn_Str16 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +// NOTE: [$STR8] Dqn_Str8 ////////////////////////////////////////////////////////////////////////// DQN_API Dqn_Str8 Dqn_Str8_InitCStr8(char const *src) { - Dqn_usize size = Dqn_CStr8_Size(src); - Dqn_Str8 result = Dqn_Str8_Init(src, size); + Dqn_usize size = Dqn_CStr8_Size(src); + Dqn_Str8 result = Dqn_Str8_Init(src, size); return result; } DQN_API bool Dqn_Str8_IsAll(Dqn_Str8 string, Dqn_Str8IsAll is_all) { - bool result = Dqn_Str8_IsValid(string); + bool result = Dqn_Str8_HasData(string); if (!result) return result; @@ -73,7 +103,7 @@ DQN_API bool Dqn_Str8_IsAll(Dqn_Str8 string, Dqn_Str8IsAll is_all) DQN_API Dqn_Str8 Dqn_Str8_Slice(Dqn_Str8 string, Dqn_usize offset, Dqn_usize size) { Dqn_Str8 result = Dqn_Str8_Init(string.data, 0); - if (!Dqn_Str8_IsValid(result)) + if (!Dqn_Str8_HasData(string)) return result; Dqn_usize capped_offset = DQN_MIN(offset, string.size); @@ -92,7 +122,7 @@ DQN_API Dqn_Str8 Dqn_Str8_Advance(Dqn_Str8 string, Dqn_usize amount) DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitArray(Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size) { Dqn_Str8BinarySplitResult result = {}; - if (!Dqn_Str8_IsValid(string) || !find || find_size == 0) + if (!Dqn_Str8_HasData(string) || !find || find_size == 0) return result; result.lhs = string; @@ -121,7 +151,7 @@ DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplit(Dqn_Str8 string, Dqn_Str8 DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverseArray(Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size) { Dqn_Str8BinarySplitResult result = {}; - if (!Dqn_Str8_IsValid(string) || !find || find_size == 0) + if (!Dqn_Str8_HasData(string) || !find || find_size == 0) return result; result.lhs = string; @@ -147,17 +177,17 @@ DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverse(Dqn_Str8 string, D return result; } -DQN_API Dqn_usize Dqn_Str8_Split(Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8 *splits, Dqn_usize splits_count) +DQN_API Dqn_usize Dqn_Str8_Split(Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8 *splits, Dqn_usize splits_count, Dqn_Str8SplitIncludeEmptyStrings mode) { Dqn_usize result = 0; // The number of splits in the actual string. - if (!Dqn_Str8_IsValid(string) || !Dqn_Str8_IsValid(delimiter) || delimiter.size <= 0) + if (!Dqn_Str8_HasData(string) || !Dqn_Str8_HasData(delimiter) || delimiter.size <= 0) return result; Dqn_Str8BinarySplitResult split = {}; Dqn_Str8 first = string; do { split = Dqn_Str8_BinarySplit(first, delimiter); - if (split.lhs.size) { + if (split.lhs.size || mode == Dqn_Str8SplitIncludeEmptyStrings_Yes) { if (splits && result < splits_count) splits[result] = split.lhs; result++; @@ -168,15 +198,13 @@ DQN_API Dqn_usize Dqn_Str8_Split(Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8 * return result; } -DQN_API Dqn_Str8SplitAllocResult Dqn_Str8_SplitAlloc(Dqn_Allocator allocator, - Dqn_Str8 string, - Dqn_Str8 delimiter) +DQN_API Dqn_Slice Dqn_Str8_SplitAlloc(Dqn_Arena *arena, Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8SplitIncludeEmptyStrings mode) { - Dqn_Str8SplitAllocResult result = {}; - Dqn_usize splits_required = Dqn_Str8_Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0); - result.data = Dqn_Allocator_NewArray(allocator, Dqn_Str8, splits_required, Dqn_ZeroMem_No); + Dqn_Slice result = {}; + Dqn_usize splits_required = Dqn_Str8_Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); + result.data = Dqn_Arena_NewArray(arena, Dqn_Str8, splits_required, Dqn_ZeroMem_No); if (result.data) { - result.size = Dqn_Str8_Split(string, delimiter, result.data, splits_required); + result.size = Dqn_Str8_Split(string, delimiter, result.data, splits_required, mode); DQN_ASSERT(splits_required == result.size); } return result; @@ -185,7 +213,7 @@ DQN_API Dqn_Str8SplitAllocResult Dqn_Str8_SplitAlloc(Dqn_Allocator allocator, DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirstStringArray(Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size) { Dqn_Str8FindResult result = {}; - if (!Dqn_Str8_IsValid(string) || !find || find_size == 0) + if (!Dqn_Str8_HasData(string) || !find || find_size == 0) return result; for (Dqn_usize index = 0; !result.found && index < string.size; index++) { @@ -229,18 +257,26 @@ DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirst(Dqn_Str8 string, uint32_t flags) return result; } -DQN_API Dqn_Str8 Dqn_Str8_Segment(Dqn_Allocator allocator, Dqn_Str8 src, Dqn_usize segment_size, char segment_char) +DQN_API Dqn_Str8 Dqn_Str8_Segment(Dqn_Arena *arena, Dqn_Str8 src, Dqn_usize segment_size, char segment_char) { - Dqn_usize result_size = src.size; - if (result_size > segment_size) - result_size += (src.size / segment_size) - 1; // NOTE: No segment on the first chunk. + if (!segment_size || !Dqn_Str8_HasData(src)) { + Dqn_Str8 result = Dqn_Str8_Copy(arena, src); + return result; + } - Dqn_Str8 result = Dqn_Str8_Allocate(allocator, result_size, Dqn_ZeroMem_Yes); - Dqn_usize write_index = 0; + Dqn_usize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + Dqn_usize segment_counter = 0; + Dqn_Str8 result = Dqn_Str8_Alloc(arena, src.size + segments, Dqn_ZeroMem_Yes); + Dqn_usize write_index = 0; DQN_FOR_UINDEX(src_index, src.size) { result.data[write_index++] = src.data[src_index]; - if ((src_index + 1) % segment_size == 0 && (src_index + 1) < src.size) + if ((src_index + 1) % segment_size == 0 && segment_counter < segments) { result.data[write_index++] = segment_char; + segment_counter++; + } DQN_ASSERTF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index); } @@ -248,6 +284,38 @@ DQN_API Dqn_Str8 Dqn_Str8_Segment(Dqn_Allocator allocator, Dqn_Str8 src, Dqn_usi return result; } +DQN_API Dqn_Str8 Dqn_Str8_ReverseSegment(Dqn_Arena *arena, Dqn_Str8 src, Dqn_usize segment_size, char segment_char) +{ + if (!segment_size || !Dqn_Str8_HasData(src)) { + Dqn_Str8 result = Dqn_Str8_Copy(arena, src); + return result; + } + + Dqn_usize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + Dqn_usize write_counter = 0; + Dqn_usize segment_counter = 0; + Dqn_Str8 result = Dqn_Str8_Alloc(arena, src.size + segments, Dqn_ZeroMem_Yes); + Dqn_usize write_index = result.size - 1; + + + DQN_MSVC_WARNING_PUSH + DQN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (size_t src_index = src.size - 1; src_index < src.size; src_index--) { + DQN_MSVC_WARNING_POP + result.data[write_index--] = src.data[src_index]; + if (++write_counter % segment_size == 0 && segment_counter < segments) { + result.data[write_index--] = segment_char; + segment_counter++; + } + } + + DQN_ASSERT(write_index == SIZE_MAX); + return result; +} + DQN_API bool Dqn_Str8_Eq(Dqn_Str8 lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case) { @@ -342,7 +410,7 @@ DQN_API Dqn_Str8 Dqn_Str8_TrimAround(Dqn_Str8 string, Dqn_Str8 trim_string) DQN_API Dqn_Str8 Dqn_Str8_TrimWhitespaceAround(Dqn_Str8 string) { Dqn_Str8 result = string; - if (!Dqn_Str8_IsValid(string)) + if (!Dqn_Str8_HasData(string)) return result; char const *start = string.data; @@ -360,7 +428,7 @@ DQN_API Dqn_Str8 Dqn_Str8_TrimWhitespaceAround(Dqn_Str8 string) DQN_API Dqn_Str8 Dqn_Str8_TrimByteOrderMark(Dqn_Str8 string) { Dqn_Str8 result = string; - if (!Dqn_Str8_IsValid(result)) + if (!Dqn_Str8_HasData(result)) return result; // TODO(dqn): This is little endian @@ -382,7 +450,7 @@ DQN_API Dqn_Str8 Dqn_Str8_FileNameFromPath(Dqn_Str8 path) { Dqn_Str8 separators[] = {DQN_STR8("/"), DQN_STR8("\\")}; Dqn_Str8BinarySplitResult split = Dqn_Str8_BinarySplitReverseArray(path, separators, DQN_ARRAY_UCOUNT(separators)); - Dqn_Str8 result = split.rhs; + Dqn_Str8 result = Dqn_Str8_HasData(split.rhs) ? split.rhs : split.lhs; return result; } @@ -400,17 +468,26 @@ DQN_API Dqn_Str8 Dqn_Str8_FilePathNoExtension(Dqn_Str8 path) return result; } +DQN_API Dqn_Str8 Dqn_Str8_FileExtension(Dqn_Str8 path) +{ + Dqn_Str8BinarySplitResult split = Dqn_Str8_BinarySplitReverse(path, DQN_STR8(".")); + Dqn_Str8 result = split.rhs; + return result; +} + DQN_API Dqn_Str8ToU64Result Dqn_Str8_ToU64(Dqn_Str8 string, char separator) { // NOTE: Argument check Dqn_Str8ToU64Result result = {}; - if (!Dqn_Str8_IsValid(string)) + if (!Dqn_Str8_HasData(string)) { + result.success = true; return result; + } // NOTE: Sanitize input/output Dqn_Str8 trim_string = Dqn_Str8_TrimWhitespaceAround(string); if (trim_string.size == 0) { - result.success = false; + result.success = true; return result; } @@ -446,13 +523,15 @@ DQN_API Dqn_Str8ToI64Result Dqn_Str8_ToI64(Dqn_Str8 string, char separator) { // NOTE: Argument check Dqn_Str8ToI64Result result = {}; - if (!Dqn_Str8_IsValid(string)) + if (!Dqn_Str8_HasData(string)) { + result.success = true; return result; + } // NOTE: Sanitize input/output Dqn_Str8 trim_string = Dqn_Str8_TrimWhitespaceAround(string); if (trim_string.size == 0) { - result.success = false; + result.success = true; return result; } @@ -492,20 +571,20 @@ DQN_API Dqn_Str8 Dqn_Str8_Replace(Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, - Dqn_Allocator allocator, + Dqn_Arena *arena, Dqn_Str8EqCase eq_case) { Dqn_Str8 result = {}; - if (!Dqn_Str8_IsValid(string) || !Dqn_Str8_IsValid(find) || find.size > string.size || find.size == 0 || string.size == 0) { - result = Dqn_Str8_Copy(allocator, string); + if (!Dqn_Str8_HasData(string) || !Dqn_Str8_HasData(find) || find.size > string.size || find.size == 0 || string.size == 0) { + result = Dqn_Str8_Copy(arena, string); return result; } - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(allocator.user_context); - Dqn_Str8Builder string_builder = {}; - string_builder.allocator = scratch.allocator; - Dqn_usize max = string.size - find.size; - Dqn_usize head = start_index; + Dqn_Scratch scratch = Dqn_Scratch_Get(arena); + Dqn_Str8Builder string_builder = {}; + string_builder.arena = scratch.arena; + Dqn_usize max = string.size - find.size; + Dqn_usize head = start_index; for (Dqn_usize tail = head; tail <= max; tail++) { Dqn_Str8 check = Dqn_Str8_Slice(string, tail, find.size); @@ -530,25 +609,25 @@ DQN_API Dqn_Str8 Dqn_Str8_Replace(Dqn_Str8 string, if (string_builder.string_size == 0) { // NOTE: No replacement possible, so we just do a full-copy - result = Dqn_Str8_Copy(allocator, string); + result = Dqn_Str8_Copy(arena, string); } else { Dqn_Str8 remainder = Dqn_Str8_Init(string.data + head, string.size - head); Dqn_Str8Builder_AppendRef(&string_builder, remainder); - result = Dqn_Str8Builder_Build(&string_builder, allocator); + result = Dqn_Str8Builder_Build(&string_builder, arena); } return result; } -DQN_API Dqn_Str8 Dqn_Str8_ReplaceInsensitive(Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Allocator allocator) +DQN_API Dqn_Str8 Dqn_Str8_ReplaceInsensitive(Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Arena *arena) { - Dqn_Str8 result = Dqn_Str8_Replace(string, find, replace, start_index, allocator, Dqn_Str8EqCase_Insensitive); + Dqn_Str8 result = Dqn_Str8_Replace(string, find, replace, start_index, arena, Dqn_Str8EqCase_Insensitive); return result; } DQN_API void Dqn_Str8_Remove(Dqn_Str8 *string, Dqn_usize offset, Dqn_usize size) { - if (!string || !Dqn_Str8_IsValid(*string)) + if (!string || !Dqn_Str8_HasData(*string)) return; char *end = string->data + string->size; @@ -573,16 +652,16 @@ DQN_API bool operator!=(Dqn_Str8 const &lhs, Dqn_Str8 const &rhs) } #endif -DQN_API Dqn_Str8 Dqn_Str8_InitF(Dqn_Allocator allocator, DQN_FMT_ATTRIB char const *fmt, ...) +DQN_API Dqn_Str8 Dqn_Str8_InitF(Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...) { va_list va; va_start(va, fmt); - Dqn_Str8 result = Dqn_Str8_InitFV(allocator, fmt, va); + Dqn_Str8 result = Dqn_Str8_InitFV(arena, fmt, va); va_end(va); return result; } -DQN_API Dqn_Str8 Dqn_Str8_InitFV(Dqn_Allocator allocator, DQN_FMT_ATTRIB char const *fmt, va_list args) +DQN_API Dqn_Str8 Dqn_Str8_InitFV(Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, va_list args) { Dqn_Str8 result = {}; if (!fmt) @@ -590,82 +669,92 @@ DQN_API Dqn_Str8 Dqn_Str8_InitFV(Dqn_Allocator allocator, DQN_FMT_ATTRIB char co Dqn_usize size = Dqn_CStr8_FVSize(fmt, args); if (size) { - result = Dqn_Str8_Allocate(allocator, size, Dqn_ZeroMem_No); - if (Dqn_Str8_IsValid(result)) - STB_SPRINTF_DECORATE(vsnprintf)(result.data, Dqn_Safe_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); + result = Dqn_Str8_Alloc(arena, size, Dqn_ZeroMem_No); + if (Dqn_Str8_HasData(result)) + DQN_VSNPRINTF(result.data, Dqn_Safe_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); } return result; } -DQN_API Dqn_Str8 Dqn_Str8_Allocate(Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem) +DQN_API Dqn_Str8 Dqn_Str8_Alloc(Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem) { Dqn_Str8 result = {}; - result.data = (char *)Dqn_Allocator_Alloc(allocator, size + 1, alignof(char), zero_mem); + result.data = Dqn_Arena_NewArray(arena, char, size + 1, zero_mem); if (result.data) result.size = size; return result; } -DQN_API Dqn_Str8 Dqn_Str8_CopyCString(Dqn_Allocator allocator, char const *string, Dqn_usize size) +DQN_API Dqn_Str8 Dqn_Str8_CopyCString(Dqn_Arena *arena, char const *string, Dqn_usize size) { Dqn_Str8 result = {}; if (!string) return result; - result = Dqn_Str8_Allocate(allocator, size, Dqn_ZeroMem_No); - if (Dqn_Str8_IsValid(result)) { + result = Dqn_Str8_Alloc(arena, size, Dqn_ZeroMem_No); + if (Dqn_Str8_HasData(result)) { DQN_MEMCPY(result.data, string, size); result.data[size] = 0; } return result; } -DQN_API Dqn_Str8 Dqn_Str8_Copy(Dqn_Allocator allocator, Dqn_Str8 string) +DQN_API Dqn_Str8 Dqn_Str8_Copy(Dqn_Arena *arena, Dqn_Str8 string) { - Dqn_Str8 result = Dqn_Str8_CopyCString(allocator, string.data, string.size); + Dqn_Str8 result = Dqn_Str8_CopyCString(arena, string.data, string.size); return result; } -// NOTE: [$STRB] Dqn_Str8Builder ================================================================ -DQN_API bool Dqn_Str8Builder_AppendRef(Dqn_Str8Builder *builder, Dqn_Str8 string) +// NOTE: [$STRB] Dqn_Str8Builder //////////////////////////////////////////////////////////////// +DQN_API bool Dqn_Str8Builder_AppendRefArray(Dqn_Str8Builder *builder, Dqn_Slice array) { - if (!builder || !string.data || string.size <= 0) + if (!builder) return false; - Dqn_Str8Link *link = Dqn_Allocator_New(builder->allocator, Dqn_Str8Link, Dqn_ZeroMem_No); - if (!link) - return false; + for (Dqn_Str8 string : array) { + if (!builder || !string.data || string.size <= 0) + return false; - link->string = string; - link->next = NULL; + Dqn_Str8Link *link = Dqn_Arena_New(builder->arena, Dqn_Str8Link, Dqn_ZeroMem_No); + if (!link) + return false; - if (builder->head) - builder->tail->next = link; - else - builder->head = link; + link->string = string; + link->next = NULL; + + if (builder->head) + builder->tail->next = link; + else + builder->head = link; + + builder->tail = link; + builder->count++; + builder->string_size += string.size; + } - builder->tail = link; - builder->count++; - builder->string_size += string.size; return true; } -DQN_API bool Dqn_Str8Builder_AppendCopy(Dqn_Str8Builder *builder, Dqn_Str8 string) +DQN_API bool Dqn_Str8Builder_AppendCopyArray(Dqn_Str8Builder *builder, Dqn_Slice array) { - Dqn_Str8 copy = Dqn_Str8_Copy(builder->allocator, string); - bool result = Dqn_Str8Builder_AppendRef(builder, copy); - return result; + for (Dqn_Str8 string : array) { + Dqn_Str8 copy = Dqn_Str8_Copy(builder->arena, string); + if (!Dqn_Str8Builder_AppendRef(builder, copy)) + return false; + } + return true; } DQN_API bool Dqn_Str8Builder_AppendFV(Dqn_Str8Builder *builder, DQN_FMT_ATTRIB char const *fmt, va_list args) { - Dqn_Str8 string = Dqn_Str8_InitFV(builder->allocator, fmt, args); + Dqn_Str8 string = Dqn_Str8_InitFV(builder->arena, fmt, args); if (string.size == 0) return true; + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(builder->arena); bool result = Dqn_Str8Builder_AppendRef(builder, string); if (!result) - Dqn_Allocator_Dealloc(builder->allocator, string.data, string.size + 1); + Dqn_Arena_TempMemEnd(temp_mem); return result; } @@ -678,13 +767,27 @@ DQN_API bool Dqn_Str8Builder_AppendF(Dqn_Str8Builder *builder, DQN_FMT_ATTRIB ch return result; } -DQN_API Dqn_Str8 Dqn_Str8Builder_Build(Dqn_Str8Builder const *builder, Dqn_Allocator allocator) +DQN_API bool Dqn_Str8Builder_AppendRef(Dqn_Str8Builder *builder, Dqn_Str8 string) +{ + Dqn_Slice array = Dqn_Slice_Init(&string, 1); + bool result = Dqn_Str8Builder_AppendRefArray(builder, array); + return result; +} + +DQN_API bool Dqn_Str8Builder_AppendCopy(Dqn_Str8Builder *builder, Dqn_Str8 string) +{ + Dqn_Slice array = Dqn_Slice_Init(&string, 1); + bool result = Dqn_Str8Builder_AppendCopyArray(builder, array); + return result; +} + +DQN_API Dqn_Str8 Dqn_Str8Builder_Build(Dqn_Str8Builder const *builder, Dqn_Arena *arena) { Dqn_Str8 result = DQN_ZERO_INIT; if (!builder || builder->string_size <= 0 || builder->count <= 0) return result; - result.data = Dqn_Allocator_NewArray(allocator, char, builder->string_size + 1, Dqn_ZeroMem_No); + result.data = Dqn_Arena_NewArray(arena, char, builder->string_size + 1, Dqn_ZeroMem_No); if (!result.data) return result; @@ -698,7 +801,46 @@ DQN_API Dqn_Str8 Dqn_Str8Builder_Build(Dqn_Str8Builder const *builder, Dqn_Alloc return result; } -// NOTE: [$CHAR] Dqn_Char ========================================================================== +DQN_API Dqn_Str8 Dqn_Str8Builder_BuildCRT(Dqn_Str8Builder const *builder) +{ + Dqn_Str8 result = DQN_ZERO_INIT; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result.data = DQN_CAST(char *)malloc(builder->string_size + 1); + if (!result.data) + return result; + + for (Dqn_Str8Link *link = builder->head; link; link = link->next) { + DQN_MEMCPY(result.data + result.size, link->string.data, link->string.size); + result.size += link->string.size; + } + + result.data[result.size] = 0; + DQN_ASSERT(result.size == builder->string_size); + return result; +} + +DQN_API Dqn_Slice Dqn_Str8Builder_BuildSlice(Dqn_Str8Builder const *builder, Dqn_Arena *arena) +{ + Dqn_Slice result = DQN_ZERO_INIT; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result = Dqn_Slice_Alloc(arena, builder->count, Dqn_ZeroMem_No); + if (!result.data) + return result; + + Dqn_usize slice_index = 0; + for (Dqn_Str8Link *link = builder->head; link; link = link->next) + result.data[slice_index++] = Dqn_Str8_Copy(arena, link->string); + + DQN_ASSERT(slice_index == builder->count); + return result; +} + + +// NOTE: [$CHAR] Dqn_Char ////////////////////////////////////////////////////////////////////////// DQN_API bool Dqn_Char_IsAlphabet(char ch) { bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); @@ -766,7 +908,7 @@ DQN_API char Dqn_Char_ToLower(char ch) return result; } -// NOTE: [$UTFX] Dqn_UTF =========================================================================== +// NOTE: [$UTFX] Dqn_UTF /////////////////////////////////////////////////////////////////////////// DQN_API int Dqn_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint) { // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ @@ -779,33 +921,29 @@ DQN_API int Dqn_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint) // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | // ----------------------------------------+----------------------------+--------------------+ - if (codepoint <= 0b0111'1111) - { - utf8[0] = DQN_CAST(uint8_t)codepoint; + if (codepoint <= 0b0111'1111) { + utf8[0] = DQN_CAST(uint8_t) codepoint; return 1; } - if (codepoint <= 0b0111'1111'1111) - { + if (codepoint <= 0b0111'1111'1111) { utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y return 2; } - if (codepoint <= 0b1111'1111'1111'1111) - { + if (codepoint <= 0b1111'1111'1111'1111) { utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z + utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z return 3; } - if (codepoint <= 0b1'1111'1111'1111'1111'1111) - { + if (codepoint <= 0b1'1111'1111'1111'1111'1111) { utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z - utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w + utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z + utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w return 4; } @@ -822,17 +960,15 @@ DQN_API int Dqn_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint) // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000–U+10FFFF | // ----------------------------------------+------------------------------------+------------------+ - if (codepoint <= 0b1111'1111'1111'1111) - { - utf16[0] = DQN_CAST(uint16_t)codepoint; + if (codepoint <= 0b1111'1111'1111'1111) { + utf16[0] = DQN_CAST(uint16_t) codepoint; return 1; } - if (codepoint <= 0b1111'1111'1111'1111'1111) - { + if (codepoint <= 0b1111'1111'1111'1111'1111) { uint32_t surrogate_codepoint = codepoint + 0x10000; - utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x - utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y + utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x + utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y return 2; } diff --git a/dqn_string.h b/dqn_string.h new file mode 100644 index 0000000..b58f20e --- /dev/null +++ b/dqn_string.h @@ -0,0 +1,388 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ __$$\ +// $$ / \__| $$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__| +// \$$$$$$\ $$ | $$$$$$$ | $$ | $$ $$\$$ |$$ |$$$$\ +// \____$$\ $$ | $$ __$$< $$ | $$ \$$$$ |$$ |\_$$ | +// $$\ $$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | +// \$$$$$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |\$$$$$$ | +// \______/ \__| \__| \__|\______|\__| \__| \______/ +// +// dqn_string.h -- UTF8/16 string manipulation +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// [$CSTR] Dqn_CStr8 -- C-string helpers +// [$STR8] Dqn_Str8 -- Pointer and length strings +// [$STRB] Dqn_Str8Builder -- Construct strings dynamically +// [$FSTR] Dqn_FStr8 -- Fixed-size strings +// [$CHAR] Dqn_Char -- Character ascii/digit.. helpers +// [$UTFX] Dqn_UTF -- Unicode helpers +// +// NOTE: [$STR8] Dqn_Str8 ////////////////////////////////////////////////////////////////////////// +struct Dqn_Str8Link +{ + Dqn_Str8 string; // The string + Dqn_Str8Link *next; // The next string in the linked list +}; + +struct Dqn_Str16 // A pointer and length style string that holds slices to UTF16 bytes. +{ + wchar_t *data; // The UTF16 bytes of the string + Dqn_usize size; // The number of characters in the string + + #if defined(__cplusplus) + wchar_t const *begin() const { return data; } // Const begin iterator for range-for loops + wchar_t const *end () const { return data + size; } // Const end iterator for range-for loops + wchar_t *begin() { return data; } // Begin iterator for range-for loops + wchar_t *end () { return data + size; } // End iterator for range-for loops + #endif +}; + +struct Dqn_Str8BinarySplitResult +{ + Dqn_Str8 lhs; + Dqn_Str8 rhs; +}; + +struct Dqn_Str8FindResult +{ + bool found; // True if string was found. If false, the subsequent fields below are not set. + Dqn_usize index; // The index in the buffer where the found string starts + Dqn_Str8 match; // The matching string in the buffer that was searched + Dqn_Str8 match_to_end_of_buffer; // The substring containing the found string to the end of the buffer + Dqn_Str8 start_to_before_match; // The substring from the start of the buffer up until the found string, not including it +}; + +enum Dqn_Str8IsAll +{ + Dqn_Str8IsAll_Digits, + Dqn_Str8IsAll_Hex, +}; + +enum Dqn_Str8EqCase +{ + Dqn_Str8EqCase_Sensitive, + Dqn_Str8EqCase_Insensitive, +}; + +enum Dqn_Str8FindFlag +{ + Dqn_Str8FindFlag_Digit = 1 << 0, // 0-9 + Dqn_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' + Dqn_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z + Dqn_Str8FindFlag_Plus = 1 << 3, // + + Dqn_Str8FindFlag_Minus = 1 << 4, // - + Dqn_Str8FindFlag_AlphaNum = Dqn_Str8FindFlag_Alphabet | Dqn_Str8FindFlag_Digit, +}; + +enum Dqn_Str8SplitIncludeEmptyStrings +{ + Dqn_Str8SplitIncludeEmptyStrings_No, + Dqn_Str8SplitIncludeEmptyStrings_Yes, +}; + +struct Dqn_Str8ToU64Result +{ + bool success; + uint64_t value; +}; + +struct Dqn_Str8ToI64Result +{ + bool success; + int64_t value; +}; + +// NOTE: [$FSTR] Dqn_FStr8 ///////////////////////////////////////////////////////////////////////// +#if !defined(DQN_NO_FSTR8) +template struct Dqn_FStr8 +{ + char data[N+1]; + Dqn_usize size; + + char *begin() { return data; } + char *end () { return data + size; } + char const *begin() const { return data; } + char const *end () const { return data + size; } +}; +#endif // !defined(DQN_NO_FSTR8) + +struct Dqn_Str8Builder +{ + Dqn_Arena *arena; // Allocator to use to back the string list + Dqn_Str8Link *head; // First string in the linked list of strings + Dqn_Str8Link *tail; // Last string in the linked list of strings + Dqn_usize string_size; // The size in bytes necessary to construct the current string + Dqn_usize count; // The number of links in the linked list of strings +}; + +// NOTE: [$CSTR] Dqn_CStr8 ///////////////////////////////////////////////////////////////////////// +template constexpr Dqn_usize Dqn_CStr8_ArrayUCount (char const (&literal)[N]) { (void)literal; return N - 1; } +template constexpr Dqn_usize Dqn_CStr8_ArrayICount (char const (&literal)[N]) { (void)literal; return N - 1; } +DQN_API Dqn_usize Dqn_CStr8_FSize (DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_usize Dqn_CStr8_FVSize (DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API Dqn_usize Dqn_CStr8_Size (char const *a); +DQN_API Dqn_usize Dqn_CStr16_Size (wchar_t const *a); + +// NOTE: [$STR6] Dqn_Str16 ///////////////////////////////////////////////////////////////////////// +#define DQN_STR16(string) Dqn_Str16{(wchar_t *)(string), sizeof(string)/sizeof(string[0]) - 1} +#define Dqn_Str16_HasData(string) ((string).data && (string).size) + +#if defined(__cplusplus) +DQN_API bool operator== (Dqn_Str16 const &lhs, Dqn_Str16 const &rhs); +DQN_API bool operator!= (Dqn_Str16 const &lhs, Dqn_Str16 const &rhs); +#endif + +// NOTE: [$STR8] Dqn_Str8 ////////////////////////////////////////////////////////////////////////// +#define DQN_STR8(string) Dqn_Str8{(char *)(string), (sizeof(string) - 1)} +#define DQN_STR_FMT(string) (int)((string).size), (string).data +#define Dqn_Str8_Init(data, size) Dqn_Str8{(char *)(data), (size_t)(size)} + +DQN_API Dqn_Str8 Dqn_Str8_InitCStr8 (char const *src); +#define Dqn_Str8_HasData(string) ((string).data && (string).size) +DQN_API bool Dqn_Str8_IsAll (Dqn_Str8 string, Dqn_Str8IsAll is_all); + +DQN_API Dqn_Str8 Dqn_Str8_InitF (Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API Dqn_Str8 Dqn_Str8_InitFV (Dqn_Arena *arena, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API Dqn_Str8 Dqn_Str8_Alloc (Dqn_Arena *arena, Dqn_usize size, Dqn_ZeroMem zero_mem); +DQN_API Dqn_Str8 Dqn_Str8_CopyCString (Dqn_Arena *arena, char const *string, Dqn_usize size); +DQN_API Dqn_Str8 Dqn_Str8_Copy (Dqn_Arena *arena, Dqn_Str8 string); + +DQN_API Dqn_Str8 Dqn_Str8_Slice (Dqn_Str8 string, Dqn_usize offset, Dqn_usize size); +DQN_API Dqn_Str8 Dqn_Str8_Advance (Dqn_Str8 string, Dqn_usize amount); +DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitArray (Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); +DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplit (Dqn_Str8 string, Dqn_Str8 find); +DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverseArray(Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); +DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverse (Dqn_Str8 string, Dqn_Str8 find); +DQN_API Dqn_usize Dqn_Str8_Split (Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8 *splits, Dqn_usize splits_count, Dqn_Str8SplitIncludeEmptyStrings mode); +DQN_API Dqn_Slice Dqn_Str8_SplitAlloc (Dqn_Arena *arena, Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8SplitIncludeEmptyStrings mode); + +DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirstStringArray (Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); +DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirstString (Dqn_Str8 string, Dqn_Str8 find); +DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirst (Dqn_Str8 string, uint32_t flags); +DQN_API Dqn_Str8 Dqn_Str8_Segment (Dqn_Arena *arena, Dqn_Str8 src, Dqn_usize segment_size, char segment_char); +DQN_API Dqn_Str8 Dqn_Str8_ReverseSegment (Dqn_Arena *arena, Dqn_Str8 src, Dqn_usize segment_size, char segment_char); + +DQN_API bool Dqn_Str8_Eq (Dqn_Str8 lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API bool Dqn_Str8_EqInsensitive (Dqn_Str8 lhs, Dqn_Str8 rhs); +DQN_API bool Dqn_Str8_StartsWith (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API bool Dqn_Str8_StartsWithInsensitive (Dqn_Str8 string, Dqn_Str8 prefix); +DQN_API bool Dqn_Str8_EndsWith (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API bool Dqn_Str8_EndsWithInsensitive (Dqn_Str8 string, Dqn_Str8 prefix); +DQN_API bool Dqn_Str8_HasChar (Dqn_Str8 string, char ch); + +DQN_API Dqn_Str8 Dqn_Str8_TrimPrefix (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API Dqn_Str8 Dqn_Str8_TrimSuffix (Dqn_Str8 string, Dqn_Str8 suffix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API Dqn_Str8 Dqn_Str8_TrimAround (Dqn_Str8 string, Dqn_Str8 trim_string); +DQN_API Dqn_Str8 Dqn_Str8_TrimWhitespaceAround (Dqn_Str8 string); +DQN_API Dqn_Str8 Dqn_Str8_TrimByteOrderMark (Dqn_Str8 string); + +DQN_API Dqn_Str8 Dqn_Str8_FileNameFromPath (Dqn_Str8 path); +DQN_API Dqn_Str8 Dqn_Str8_FileNameNoExtension (Dqn_Str8 path); +DQN_API Dqn_Str8 Dqn_Str8_FilePathNoExtension (Dqn_Str8 path); +DQN_API Dqn_Str8 Dqn_Str8_FileExtension (Dqn_Str8 path); + +DQN_API Dqn_Str8ToU64Result Dqn_Str8_ToU64 (Dqn_Str8 string, char separator); +DQN_API Dqn_Str8ToI64Result Dqn_Str8_ToI64 (Dqn_Str8 string, char separator); + +DQN_API Dqn_Str8 Dqn_Str8_Replace (Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Arena *arena, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); +DQN_API Dqn_Str8 Dqn_Str8_ReplaceInsensitive (Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Arena *arena); +DQN_API void Dqn_Str8_Remove (Dqn_Str8 *string, Dqn_usize offset, Dqn_usize size); + +#if defined(__cplusplus) +DQN_API bool operator== (Dqn_Str8 const &lhs, Dqn_Str8 const &rhs); +DQN_API bool operator!= (Dqn_Str8 const &lhs, Dqn_Str8 const &rhs); +#endif + +// NOTE: [$FSTR] Dqn_Str8Builder /////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_Str8Builder_AppendRefArray (Dqn_Str8Builder *builder, Dqn_Slice string); +DQN_API bool Dqn_Str8Builder_AppendCopyArray(Dqn_Str8Builder *builder, Dqn_Slice string); +DQN_API bool Dqn_Str8Builder_AppendFV (Dqn_Str8Builder *builder, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_Str8Builder_AppendF (Dqn_Str8Builder *builder, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API bool Dqn_Str8Builder_AppendRef (Dqn_Str8Builder *builder, Dqn_Str8 string); +DQN_API bool Dqn_Str8Builder_AppendCopy (Dqn_Str8Builder *builder, Dqn_Str8 string); +DQN_API Dqn_Str8 Dqn_Str8Builder_Build (Dqn_Str8Builder const *builder, Dqn_Arena *arena); +DQN_API Dqn_Str8 Dqn_Str8Builder_BuildCRT (Dqn_Str8Builder const *builder); +DQN_API Dqn_Slice Dqn_Str8Builder_BuildSlice (Dqn_Str8Builder const *builder, Dqn_Arena *arena); + +// NOTE: [$FSTR] Dqn_FStr8 ////////////////////////////////////////////////////////////////////// +#if !defined(DQN_NO_FSTR8) +template Dqn_FStr8 Dqn_FStr8_InitF (DQN_FMT_ATTRIB char const *fmt, ...); +template Dqn_usize Dqn_FStr8_Max (Dqn_FStr8 const *string); +template void Dqn_FStr8_Clear (Dqn_FStr8 *string); +template bool Dqn_FStr8_AppendFV (Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, va_list va); +template bool Dqn_FStr8_AppendF (Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, ...); +template bool Dqn_FStr8_AppendCStr8 (Dqn_FStr8 *string, char const *value, Dqn_usize size); +template bool Dqn_FStr8_Append (Dqn_FStr8 *string, Dqn_Str8 value); +template Dqn_Str8 Dqn_FStr8_ToStr8 (Dqn_FStr8 const *string); +template bool Dqn_FStr8_Eq (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case); +template bool Dqn_FStr8_EqStr8 (Dqn_FStr8 const *lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case); +template bool Dqn_FStr8_EqInsensitive (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs); +template bool Dqn_FStr8_EqStr8Insensitive (Dqn_FStr8 const *lhs, Dqn_Str8 rhs); +template bool Dqn_FStr8_EqFStr8 (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case); +template bool Dqn_FStr8_EqFStr8Insensitive (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs); +template bool operator== (Dqn_FStr8 const &lhs, Dqn_FStr8 const &rhs); +template bool operator!= (Dqn_FStr8 const &lhs, Dqn_FStr8 const &rhs); +#endif // !defined(DQN_NO_FSTR8) + +// NOTE: [$CHAR] Dqn_Char ////////////////////////////////////////////////////////////////////////// +DQN_API bool Dqn_Char_IsAlphabet (char ch); +DQN_API bool Dqn_Char_IsDigit (char ch); +DQN_API bool Dqn_Char_IsAlphaNum (char ch); +DQN_API bool Dqn_Char_IsWhitespace (char ch); +DQN_API bool Dqn_Char_IsHex (char ch); +DQN_API uint8_t Dqn_Char_HexToU8 (char ch); +DQN_API char Dqn_Char_ToHex (char ch); +DQN_API char Dqn_Char_ToHexUnchecked (char ch); +DQN_API char Dqn_Char_ToLower (char ch); + +// NOTE: [$UTFX] Dqn_UTF /////////////////////////////////////////////////////////////////////////// +DQN_API int Dqn_UTF8_EncodeCodepoint (uint8_t utf8[4], uint32_t codepoint); +DQN_API int Dqn_UTF16_EncodeCodepoint (uint16_t utf16[2], uint32_t codepoint); + +#if !defined(DQN_NO_FSTR8) +// NOTE: [$FSTR] Dqn_FStr8 ///////////////////////////////////////////////////////////////////////// +template Dqn_FStr8 Dqn_FStr8_InitF(DQN_FMT_ATTRIB char const *fmt, ...) +{ + Dqn_FStr8 result = {}; + if (fmt) { + va_list args; + va_start(args, fmt); + Dqn_FStr8_AppendFV(&result, fmt, args); + va_end(args); + } + return result; +} + +template Dqn_usize Dqn_FStr8_Max(Dqn_FStr8 const *) +{ + Dqn_usize result = N; + return result; +} + +template void Dqn_FStr8_Clear(Dqn_FStr8 *string) +{ + *string = {}; +} + +template bool Dqn_FStr8_AppendFV(Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, va_list args) +{ + bool result = false; + if (!string || !fmt) + return result; + + Dqn_usize require = Dqn_CStr8_FVSize(fmt, args) + 1 /*null_terminate*/; + Dqn_usize space = (N + 1) - string->size; + result = require <= space; + string->size += DQN_VSNPRINTF(string->data + string->size, DQN_CAST(int)space, fmt, args); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not. + string->size = DQN_MIN(string->size, N); + return result; +} + +template bool Dqn_FStr8_AppendF(Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, ...) +{ + bool result = false; + if (!string || !fmt) + return result; + va_list args; + va_start(args, fmt); + result = Dqn_FStr8_AppendFV(string, fmt, args); + va_end(args); + return result; +} + +template bool Dqn_FStr8_AppendCStr8(Dqn_FStr8 *string, char const *src, Dqn_usize size) +{ + DQN_ASSERT(string->size <= N); + bool result = false; + if (!string || !src || size == 0 || string->size >= N) + return result; + + Dqn_usize space = N - string->size; + result = size <= space; + DQN_MEMCPY(string->data + string->size, src, DQN_MIN(space, size)); + string->size = DQN_MIN(string->size + size, N); + string->data[string->size] = 0; + return result; +} + +template bool Dqn_FStr8_Append(Dqn_FStr8 *string, Dqn_Str8 src) +{ + bool result = Dqn_FStr8_AppendCStr8(string, src.data, src.size); + return result; +} + +template Dqn_Str8 Dqn_FStr8_ToStr8(Dqn_FStr8 const *string) +{ + Dqn_Str8 result = {}; + if (!string || string->size <= 0) + return result; + + result.data = DQN_CAST(char *)string->data; + result.size = string->size; + return result; +} + +template bool Dqn_FStr8_Eq(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; +} + +template bool Dqn_FStr8_EqStr8(Dqn_FStr8 const *lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs, eq_case); + return result; +} + +template bool Dqn_FStr8_EqInsensitive(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, Dqn_Str8EqCase_Insensitive); + return result; +} + +template bool Dqn_FStr8_EqStr8Insensitive(Dqn_FStr8 const *lhs, Dqn_Str8 rhs) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs, Dqn_Str8EqCase_Insensitive); + return result; +} + +template bool Dqn_FStr8_EqFStr8(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; +} + +template bool Dqn_FStr8_EqFStr8Insensitive(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs) +{ + Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); + Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); + bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, Dqn_Str8EqCase_Insensitive); + return result; +} + +template bool operator==(Dqn_FStr8 const &lhs, Dqn_FStr8 const &rhs) +{ + bool result = Dqn_FStr8_Eq(&lhs, &rhs, Dqn_Str8EqCase_Sensitive); + return result; +} + +template bool operator!=(Dqn_FStr8 const &lhs, Dqn_FStr8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} +#endif // !defined(DQN_NO_FSTR8) diff --git a/dqn_strings.h b/dqn_strings.h deleted file mode 100644 index d8f9111..0000000 --- a/dqn_strings.h +++ /dev/null @@ -1,649 +0,0 @@ -// NOTE: [$CSTR] Dqn_CStr8 ====================================================================== -// @proc Dqn_CStr8_ArrayCount -// @desc Calculate the size of a cstring literal/array at compile time -// @param literal The cstring literal/array to calculate the size for -// @return The size of the cstring not including the null-terminating byte - -// @proc Dqn_CStr8_FSize, Dqn_CStr8_FVSize -// Calculate the required size to format the given format cstring. -// @param[in] fmt The format string to calculate the size for -// @return The size required to format the string, not including the null -// terminator. - -// @proc Dqn_CStr8_Size -// @desc Calculate the string length of the null-terminated string. -// @param[in] a The string whose length is to be determined -// @return The length of the string - -DQN_API template constexpr Dqn_usize Dqn_CStr8_ArrayUCount(char const (&literal)[N]) { (void)literal; return N - 1; } -DQN_API template constexpr Dqn_usize Dqn_CStr8_ArrayICount(char const (&literal)[N]) { (void)literal; return N - 1; } -DQN_API Dqn_usize Dqn_CStr8_FSize (DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API Dqn_usize Dqn_CStr8_FVSize (DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API Dqn_usize Dqn_CStr8_Size (char const *a); -DQN_API Dqn_usize Dqn_CStr16_Size (wchar_t const *a); - -// NOTE: [$STR8] Dqn_Str8 ======================================================================= -// NOTE: API -// @proc Dqn_Str8_Init -// @desc Initialise a string from a pointer and length -// The string is invalid (i.e. Dqn_Str8_IsValid() returns false) if size is -// negative or the string is null. - -// @proc Dqn_Str8_InitCString -// @desc Initialise a string from a cstring -// The cstring must be null-terminated as its length is evaluated using -// strlen. The string is invalid (i.e. Dqn_Str8_IsValid() returns false) if -// size is negative or the string is null. - -// @proc Dqn_Str8_InitF -// @desc Create a string from a printf style format string -// @param[in] allocator The allocator the string will be allocated from -// @param[in] fmt The printf style format cstring - -// @proc Dqn_Str8_InitFV -// @desc Create a string from a printf style format string using a va_list -// @param[in] arena The allocator the string will be allocated from -// @param[in] fmt The printf style format cstring -// @param[in] va The variable argument list -// -// @proc Dqn_Str8_IsValid -// @desc Determine if the values of the given string are valid -// A string is invalid if size is negative or the string is null. -// @return True if the string is valid, false otherwise. - -// @proc Dqn_Str8 Dqn_Str8_Slice -// @desc Create a slice from a pre-existing string. -// The requested slice is clamped to within the bounds of the original string. -// @param[in] string The string to slice -// @param[in] offset The starting byte to slice from -// @param[in] size The size of the slice -// @return The sliced string - -// @proc Dqn_Str8_BinarySplit -// @desc Split a string into the substring occuring prior and after the first -// occurence of the `delimiter`. Neither strings include the `delimiter`. -// -// @param[in] string The string to split -// @param[in] string_size The size of the string -// @param[in] delimiter The character to split the string on -// @param[out] lhs_size The size of the left hand side of the split string -// @param[out] rhs The right hand side of the split string -// @param[out] rhs_size The size of the right hand side of the split string -// -// @return The left hand side of the split string. The original pointer is -// returned if the arguments were invalid. - -// @proc Dqn_Str8_Split -// @desc Split a string by the delimiting character. -// This function can evaluate the number of splits required in the return value -// by setting `splits` to null and `splits_count` to 0. -// @param[in] string The source string to split -// @param[in] delimiter The substring to split the string on -// @param[out] splits (Optional) The destination array to write the splits to. -// @param[in] splits_count The number of splits that can be written into the -// `splits` array. -// @return The number of splits in the `string`. If the return value is >= -// 'splits_count' then there are more splits in the string than can be written -// to the `splits` array. The number of splits written is capped to the -// capacity given by the caller, i.e. `splits_count`. This function should be -// called again with a sufficiently sized array if all splits are desired. - -// @proc Dqn_Str8_Segment -// @desc Segment a string by inserting the `segment_char` every `segment_size` -// characters in the string. For example, '123456789' split with -// `segment_char` ' ' and `segment_size` '3' would yield, '123 456 789'. - -// @proc Dqn_Str8_Allocate -// @desc Create an empty string with the requested size -// @param[in] allocator The allocator the string will be allocated from -// @param[in] size The size in bytes of the string to allocate -// @param[in] zero_mem Enum to indicate if the string's memory should be cleared - -// @proc Dqn_Str8_CopyCString -// @desc Create a copy of the given cstring -// @param[in] allocator The allocator the string will be allocated from -// @param[in] string The cstring to copy -// @param[in] size The size of the cstring to copy. This cannot be <= 0 -// @return A copy of the string, invalid string if any argument was invalid. - -// @proc Dqn_Str8_Copy -// @desc Create a copy of the given string -// @param[in] allocator The allocator the string will be allocated from -// @param[in] string The string to copy -// @return A copy of the string, invalid string if any argument was invalid. - -// @proc Dqn_Str8_Eq, Dqn_Str8_EqInsensitive -// @desc Compare a string for equality with or without case sensitivity. -// @param[in] lhs The first string to compare equality with -// @param[in] rhs The second string to compare equality with -// @param[in] lhs The first string's size -// @param[in] rhs The second string's size -// @param[in] eq_case Set the comparison to be case sensitive or insensitive -// @return True if the arguments are valid, non-null and the strings -// are equal, false otherwise. - -// @proc Dqn_Str8_StartsWith, Dqn_Str8_StartsWithInsensitive, -// Dqn_Str8_EndsWith, Dqn_Str8_EndswithInsensitive -// @desc Check if a string starts/ends with the specified prefix -// `EndsWithInsensitive` is case insensitive -// @param[in] string The string to check for the prefix -// @param[in] prefix The prefix to check against the string -// @param[in] eq_case Set the comparison to be case sensitive or insensitive -// @return True if the string is valid, non-null and has the specified prefix, -// false otherwise. - -// @proc Dqn_Str8_TrimPrefix, Dqn_Str8_TrimSuffix -// @desc Remove the prefix/suffix respectively from the given `string. -// -// @param[in] string The string to trim -// @param[in] prefix The prefix to trim from the string -// @param[in] suffix The suffix to trim from the string -// @param[in] eq_case Set the comparison to be case sensitive or insensitive -// @param[out] trimmed_string The size of the trimmed string -// -// @return The trimmed string. The original input string is returned if -// arguments are invalid or no trim was possible. - -// @proc Dqn_Str8_TrimWhitespaceAround -// @desc Trim whitespace from the prefix and suffix of the string -// -// @param[in] string The string to trim -// @param[in] string_size The size of the string -// @param[out] trimmed_string The size of the trimmed string -// -// @return The trimmed string. The original input string is returned if -// arguments are invalid or no trim was possible. - -// @proc Dqn_Str8_TrimByteOrderMark -// @desc Trim UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark prefix in the string. -// -// @param[in] string The string to trim -// @param[in] string_size The size of the string -// @param[out] trimmed_string The size of the trimmed string -// -// @return The trimmed string. The original input string is returned if -// arguments are invalid or no trim was possible. - -// @proc Dqn_Str8_FileNameFromPath -// @desc Get the file name from a file path. The file name is evaluated by -// searching from the end of the string backwards to the first occurring path -// separator '/' or '\'. If no path separator is found, the original string is -// returned. This function preserves the file extension if there were any. -// -// @param[in] path A file path on the disk -// @param[in] size The size of the file path string, if size is '-1' the null -// terminated string length is evaluated. -// @param[out] file_name_size The size of the returned file name -// -// @return The file name in the file path, if none is found, the original path -// string is returned. Null pointer if arguments are null or invalid. - -// @proc Dqn_Str8_ToI64, Dqn_Str8_ToU64 -// @desc Convert a number represented as a string to a signed 64 bit number. -// -// The `separator` is an optional digit separator for example, if `separator` -// is set to ',' then "1,234" will successfully be parsed to '1234'. -// -// Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, -// i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of -// digits, the seperator or the permitted prefixes as previously mentioned -// otherwise this function will return false, i.e. "1234 dog" will cause the -// function to return false, however, the output is greedily converted and -// will be evaluated to "1234". -// -// `ToU64` only '+' prefix is permitted -// `ToI64` either '+' or '-' prefix is permitted -// -// @param[in] separator The character used to separate the digits, if any. Set -// this to 0, if no separators are permitted. - -// @proc Dqn_Str8_Replace, Dqn_Str8_ReplaceInsensitive -// @desc TODO(doyle): Write description - -// @proc Dqn_Str8_Remove -// @desc Remove the substring denoted by the begin index and the size from the string -// string in-place using MEMMOVE to shift the string back. - -// @proc Dqn_Str8_Find -// @desc @param start_index Set an index within the string string to start the search -// from, if not desired, set to 0 -// @return A string that points to the matching find, otherwise a 0 length string. - -// @proc DQN_STR8 -// @desc Construct a UTF8 c-string literal into a Dqn_Str8 referencing a -// string stored in the data-segment. This string is read-only. - -// @proc DQN_STR16 -// @desc Construct a UTF16 c-string literal into a Dqn_Str16 referencing a string -// stored in the data-segment. This string is read-only. - -// @proc DQN_STR_FMT -// @desc Unpack a string into arguments for printing a string into a printf style -// format string. - -struct Dqn_Str8Link -{ - Dqn_Str8 string; // The string - Dqn_Str8Link *next; // The next string in the linked list -}; - -struct Dqn_Str16 /// A pointer and length style string that holds slices to UTF16 bytes. -{ - wchar_t *data; // The UTF16 bytes of the string - Dqn_usize size; // The number of characters in the string - - #if defined(__cplusplus) - wchar_t const *begin() const { return data; } ///< Const begin iterator for range-for loops - wchar_t const *end () const { return data + size; } ///< Const end iterator for range-for loops - wchar_t *begin() { return data; } ///< Begin iterator for range-for loops - wchar_t *end () { return data + size; } ///< End iterator for range-for loops - #endif -}; - -struct Dqn_Str8BinarySplitResult -{ - Dqn_Str8 lhs; - Dqn_Str8 rhs; -}; - -struct Dqn_Str8FindResult -{ - bool found; // True if string was found. If false, the subsequent fields below are not set. - Dqn_usize index; // The index in the buffer where the found string starts - Dqn_Str8 match; // The matching string in the buffer that was searched - Dqn_Str8 match_to_end_of_buffer; // The substring containing the found string to the end of the buffer - Dqn_Str8 start_to_before_match; // The substring from the start of the buffer up until the found string, not including it -}; - -// NOTE: Macros ==================================================================================== -#define DQN_STR8(string) Dqn_Str8{(char *)(string), sizeof(string) - 1} -#define DQN_STR16(string) Dqn_Str16{(wchar_t *)(string), (sizeof(string)/sizeof(string[0])) - 1} -#define DQN_STR_FMT(string) (int)((string).size), (string).data - -#if defined(__cplusplus) -#define Dqn_Str8_Init(data, size) (Dqn_Str8{(char *)(data), (Dqn_usize)(size)}) -#else -#define Dqn_Str8_Init(data, size) (Dqn_Str8){(data), (size)} -#endif - -// NOTE: API ======================================================================================= -enum Dqn_Str8IsAll -{ - Dqn_Str8IsAll_Digits, - Dqn_Str8IsAll_Hex, -}; - -enum Dqn_Str8EqCase -{ - Dqn_Str8EqCase_Sensitive, - Dqn_Str8EqCase_Insensitive, -}; - -enum Dqn_Str8FindFlag -{ - Dqn_Str8FindFlag_Digit = 1 << 0, // 0-9 - Dqn_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' - Dqn_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z - Dqn_Str8FindFlag_Plus = 1 << 3, // + - Dqn_Str8FindFlag_Minus = 1 << 4, // - - Dqn_Str8FindFlag_AlphaNum = Dqn_Str8FindFlag_Alphabet | Dqn_Str8FindFlag_Digit, -}; - -struct Dqn_Str8SplitAllocResult -{ - Dqn_Str8 *data; - Dqn_usize size; -}; - -struct Dqn_Str8ToU64Result -{ - bool success; - uint64_t value; -}; - -struct Dqn_Str8ToI64Result -{ - bool success; - int64_t value; -}; - -DQN_API Dqn_Str8 Dqn_Str8_InitCStr8 (char const *src); -#define Dqn_Str8_IsValid(string) ((string).data) -DQN_API bool Dqn_Str8_IsAll (Dqn_Str8 string, Dqn_Str8IsAll is_all); - -DQN_API Dqn_Str8 Dqn_Str8_InitF (Dqn_Allocator allocator, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API Dqn_Str8 Dqn_Str8_InitFV (Dqn_Allocator allocator, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API Dqn_Str8 Dqn_Str8_Allocate (Dqn_Allocator allocator, Dqn_usize size, Dqn_ZeroMem zero_mem); -DQN_API Dqn_Str8 Dqn_Str8_CopyCString (Dqn_Allocator allocator, char const *string, Dqn_usize size); -DQN_API Dqn_Str8 Dqn_Str8_Copy (Dqn_Allocator allocator, Dqn_Str8 string); - -DQN_API Dqn_Str8 Dqn_Str8_Slice (Dqn_Str8 string, Dqn_usize offset, Dqn_usize size); -DQN_API Dqn_Str8 Dqn_Str8_Advance (Dqn_Str8 string, Dqn_usize amount); -DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitArray (Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); -DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplit (Dqn_Str8 string, Dqn_Str8 find); -DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverseArray(Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); -DQN_API Dqn_Str8BinarySplitResult Dqn_Str8_BinarySplitReverse (Dqn_Str8 string, Dqn_Str8 find); -DQN_API Dqn_usize Dqn_Str8_Split (Dqn_Str8 string, Dqn_Str8 delimiter, Dqn_Str8 *splits, Dqn_usize splits_count); -DQN_API Dqn_Str8SplitAllocResult Dqn_Str8_SplitAlloc (Dqn_Allocator allocator, Dqn_Str8 string, Dqn_Str8 delimiter); - -DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirstStringArray (Dqn_Str8 string, Dqn_Str8 const *find, Dqn_usize find_size); -DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirstString (Dqn_Str8 string, Dqn_Str8 find); -DQN_API Dqn_Str8FindResult Dqn_Str8_FindFirst (Dqn_Str8 string, uint32_t flags); -DQN_API Dqn_Str8 Dqn_Str8_Segment (Dqn_Allocator allocator, Dqn_Str8 src, Dqn_usize segment_size, char segment_char); - -DQN_API bool Dqn_Str8_Eq (Dqn_Str8 lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API bool Dqn_Str8_EqInsensitive (Dqn_Str8 lhs, Dqn_Str8 rhs); -DQN_API bool Dqn_Str8_StartsWith (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API bool Dqn_Str8_StartsWithInsensitive (Dqn_Str8 string, Dqn_Str8 prefix); -DQN_API bool Dqn_Str8_EndsWith (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API bool Dqn_Str8_EndsWithInsensitive (Dqn_Str8 string, Dqn_Str8 prefix); -DQN_API bool Dqn_Str8_HasChar (Dqn_Str8 string, char ch); - -DQN_API Dqn_Str8 Dqn_Str8_TrimPrefix (Dqn_Str8 string, Dqn_Str8 prefix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API Dqn_Str8 Dqn_Str8_TrimSuffix (Dqn_Str8 string, Dqn_Str8 suffix, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API Dqn_Str8 Dqn_Str8_TrimAround (Dqn_Str8 string, Dqn_Str8 trim_string); -DQN_API Dqn_Str8 Dqn_Str8_TrimWhitespaceAround (Dqn_Str8 string); -DQN_API Dqn_Str8 Dqn_Str8_TrimByteOrderMark (Dqn_Str8 string); - -DQN_API Dqn_Str8 Dqn_Str8_FileNameFromPath (Dqn_Str8 path); -DQN_API Dqn_Str8 Dqn_Str8_FileNameNoExtension (Dqn_Str8 path); -DQN_API Dqn_Str8 Dqn_Str8_FilePathNoExtension (Dqn_Str8 path); - -DQN_API Dqn_Str8ToU64Result Dqn_Str8_ToU64 (Dqn_Str8 string, char separator); -DQN_API Dqn_Str8ToI64Result Dqn_Str8_ToI64 (Dqn_Str8 string, char separator); - -DQN_API Dqn_Str8 Dqn_Str8_Replace (Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Allocator allocator, Dqn_Str8EqCase eq_case = Dqn_Str8EqCase_Sensitive); -DQN_API Dqn_Str8 Dqn_Str8_ReplaceInsensitive (Dqn_Str8 string, Dqn_Str8 find, Dqn_Str8 replace, Dqn_usize start_index, Dqn_Allocator allocator); -DQN_API void Dqn_Str8_Remove (Dqn_Str8 *string, Dqn_usize offset, Dqn_usize size); - -#if defined(__cplusplus) -DQN_API bool operator== (Dqn_Str8 const &lhs, Dqn_Str8 const &rhs); -DQN_API bool operator!= (Dqn_Str8 const &lhs, Dqn_Str8 const &rhs); -#endif - -#if !defined(DQN_NO_FSTR8) -// NOTE: [$FSTR] Dqn_FStr8 ========================================================================= -// NOTE: API ======================================================================================= -// @proc Dqn_FStr8_InitF -// @desc Create a fixed string from the format string. The result string is -// null-terminated. -// @param fmt[in] Format string specifier to create the fixed string from -// @return The created string, truncated if there was insufficient space - -// @proc Dqn_FStr8_Max -// @desc @param string[in] The string to query the maximum capacity of -// @return Maximum capacity of the fixed string - -// @proc Dqn_FStr8_Clear -// @desc Reset the characters in the string -// @param string[in] The string to clear - -// @proc Dqn_FStr8_AppendFV -// @desc Append a format string to the fixed string. On failure the string is -// appended to but truncated ensuring null-termination. -// @param string[in] The string to append to -// @param fmt[in] Format string to append to the fixed string -// @return True if append was successful, false otherwise. - -// @proc Dqn_FStr8_AppendF -// @desc @copydocs Dqn_FStr8_AppendF - -// @proc Dqn_FStr8_AppendCStr8 -// @desc Append a cstring to the fixed string. On failure the string is -// appended to but truncated ensuring null-termination. -// @param string[in] The string to append to -// @param value[in] Cstring to append to the fixed string -// @param size[in] Size of the cstring -// @return True if append was successful, false otherwise. - -// @proc Dqn_FStr8_Append -// @desc Append a string to the fixed string. On failure the string is -// appended to but truncated ensuring null-termination. -// @param string[in] The string to append to -// @param value[in] String to append to the fixed string -// determined before appending. -// @return True if append was successful, false otherwise. - -// @proc Dqn_FStr8_ToStr8 -// @desc Convert a fixed string to a string. The string holds a reference to the -// fixed string and is invalidated once fixed string is deleted. -// @param string[in] The fixed string to create a string from -// @return String referencing the contents of `string` - -// @proc Dqn_FStr8_Eq -// @desc @see Dqn_Str8_Eq - -// @proc Dqn_FStr8_EqStr8 -// @desc @see Dqn_Str8_Eq - -// @proc Dqn_FStr8_EqInsensitive -// @desc Compare a string for equality, case insensitive -// @see Dqn_Str8_Eq - -// @proc Dqn_FStr8_EqStr8Insensitive -// @desc Compare a string for equality, case insensitive -// @see Dqn_Str8_Eq - -template struct Dqn_FStr8 -{ - char data[N+1]; - Dqn_usize size; - - bool operator==(Dqn_FStr8 const &other) const { - if (size != other.size) return false; - bool result = DQN_MEMCMP(data, other.data, size); - return result; - } - - bool operator!=(Dqn_FStr8 const &other) const { return !(*this == other); } - - char *begin() { return data; } - char *end () { return data + size; } - char const *begin() const { return data; } - char const *end () const { return data + size; } -}; - -template Dqn_FStr8 Dqn_FStr8_InitF (DQN_FMT_ATTRIB char const *fmt, ...); -template Dqn_usize Dqn_FStr8_Max (Dqn_FStr8 const *string); -template void Dqn_FStr8_Clear (Dqn_FStr8 *string); -template bool Dqn_FStr8_AppendFV (Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, va_list va); -template bool Dqn_FStr8_AppendF (Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, ...); -template bool Dqn_FStr8_AppendCStr8 (Dqn_FStr8 *string, char const *value, Dqn_usize size); -template bool Dqn_FStr8_Append (Dqn_FStr8 *string, Dqn_Str8 value); -template Dqn_Str8 Dqn_FStr8_ToStr8 (Dqn_FStr8 const *string); -template bool Dqn_FStr8_Eq (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case); -template bool Dqn_FStr8_EqStr8 (Dqn_FStr8 const *lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case); -template bool Dqn_FStr8_EqInsensitive (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs); -template bool Dqn_FStr8_EqStr8Insensitive (Dqn_FStr8 const *lhs, Dqn_Str8 rhs); -template bool Dqn_FStr8_EqFStr8 (Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case); -template bool Dqn_FStr8_EqFStr8Insensitive(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs); -#endif // !defined(DQN_NO_FSTR8) - -// NOTE: [$STRB] Dqn_Str8Builder ================================================================ -// NOTE: API ======================================================================================= -// @proc Dqn_Str8Builder_AppendRef, Dqn_Str8_AppendCopy, -// Dqn_Str8_AppendFV, Dqn_Str8_AppendF -// @desc Append a string to the list of strings in the builder. -// -// The string is appended to the builder as follows -// - AppendRef: By reference -// - AppendCopy: By copy using the builder's allocator to copy the string -// - AppendFV, AppendF: Using a format string, allocated using the builder's -// allocator -// -// The string's data must persist whilst the string builder is being used. -// @param builder The builder to append the string to -// @param string The string to append to the builder -// @return True if append was successful, false if parameters are invalid -// or memory allocation failure. - -// @proc Dqn_Str8Builder_Build -// @desc Build the list of strings into the final composite string from the -// string builder -// @param builder The string builder to build the string from -// @param allocator The allocator to use to build the string -// @return The string if build was successful, empty string if parameters are -// invalid or memory allocation failure. - -struct Dqn_Str8Builder -{ - Dqn_Allocator allocator; ///< Allocator to use to back the string list - Dqn_Str8Link *head; ///< First string in the linked list of strings - Dqn_Str8Link *tail; ///< Last string in the linked list of strings - Dqn_usize string_size; ///< The size in bytes necessary to construct the current string - Dqn_usize count; ///< The number of links in the linked list of strings -}; - -DQN_API bool Dqn_Str8Builder_AppendF (Dqn_Str8Builder *builder, DQN_FMT_ATTRIB char const *fmt, ...); -DQN_API bool Dqn_Str8Builder_AppendFV (Dqn_Str8Builder *builder, DQN_FMT_ATTRIB char const *fmt, va_list args); -DQN_API bool Dqn_Str8Builder_AppendRef (Dqn_Str8Builder *builder, Dqn_Str8 string); -DQN_API bool Dqn_Str8Builder_AppendCopy(Dqn_Str8Builder *builder, Dqn_Str8 string); -DQN_API Dqn_Str8 Dqn_Str8Builder_Build (Dqn_Str8Builder const *builder, Dqn_Allocator allocator); - -// NOTE: [$CHAR] Dqn_Char ========================================================================== -DQN_API bool Dqn_Char_IsAlphabet (char ch); -DQN_API bool Dqn_Char_IsDigit (char ch); -DQN_API bool Dqn_Char_IsAlphaNum (char ch); -DQN_API bool Dqn_Char_IsWhitespace (char ch); -DQN_API bool Dqn_Char_IsHex (char ch); -DQN_API uint8_t Dqn_Char_HexToU8 (char ch); -DQN_API char Dqn_Char_ToHex (char ch); -DQN_API char Dqn_Char_ToHexUnchecked(char ch); -DQN_API char Dqn_Char_ToLower (char ch); - -// NOTE: [$UTFX] Dqn_UTF =========================================================================== -DQN_API int Dqn_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint); -DQN_API int Dqn_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint); - -#if !defined(DQN_NO_FSTR8) -// NOTE: [$FSTR] Dqn_FStr8 ====================================================================== -template Dqn_FStr8 Dqn_FStr8_InitF(DQN_FMT_ATTRIB char const *fmt, ...) -{ - Dqn_FStr8 result = {}; - if (fmt) { - va_list args; - va_start(args, fmt); - Dqn_FStr8_AppendFV(&result, fmt, args); - va_end(args); - } - return result; -} - -template Dqn_usize Dqn_FStr8_Max(Dqn_FStr8 const *) -{ - Dqn_usize result = N; - return result; -} - -template void Dqn_FStr8_Clear(Dqn_FStr8 *string) -{ - *string = {}; -} - -template bool Dqn_FStr8_AppendFV(Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, va_list args) -{ - bool result = false; - if (!string || !fmt) - return result; - - Dqn_usize require = Dqn_CStr8_FVSize(fmt, args) + 1 /*null_terminate*/; - Dqn_usize space = (N + 1) - string->size; - result = require <= space; - string->size += STB_SPRINTF_DECORATE(vsnprintf)(string->data + string->size, DQN_CAST(int)space, fmt, args); - - // NOTE: snprintf returns the required size of the format string - // irrespective of if there's space or not. - string->size = DQN_MIN(string->size, N); - return result; -} - -template bool Dqn_FStr8_AppendF(Dqn_FStr8 *string, DQN_FMT_ATTRIB char const *fmt, ...) -{ - bool result = false; - if (!string || !fmt) - return result; - va_list args; - va_start(args, fmt); - result = Dqn_FStr8_AppendFV(string, fmt, args); - va_end(args); - return result; -} - -template bool Dqn_FStr8_AppendCStr8(Dqn_FStr8 *string, char const *src, Dqn_usize size) -{ - DQN_ASSERT(string->size <= N); - bool result = false; - if (!string || !src || size == 0 || string->size >= N) - return result; - - Dqn_usize space = N - string->size; - result = size <= space; - DQN_MEMCPY(string->data + string->size, src, DQN_MIN(space, size)); - string->size = DQN_MIN(string->size + size, N); - string->data[string->size] = 0; - return result; -} - -template bool Dqn_FStr8_Append(Dqn_FStr8 *string, Dqn_Str8 src) -{ - bool result = Dqn_FStr8_AppendCStr8(string, src.data, src.size); - return result; -} - -template Dqn_Str8 Dqn_FStr8_ToStr8(Dqn_FStr8 const *string) -{ - Dqn_Str8 result = {}; - if (!string || string->size <= 0) - return result; - - result.data = DQN_CAST(char *)string->data; - result.size = string->size; - return result; -} - -template bool Dqn_FStr8_Eq(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, eq_case); - return result; -} - -template bool Dqn_FStr8_EqStr8(Dqn_FStr8 const *lhs, Dqn_Str8 rhs, Dqn_Str8EqCase eq_case) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs, eq_case); - return result; -} - -template bool Dqn_FStr8_EqInsensitive(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, Dqn_Str8EqCase_Insensitive); - return result; -} - -template bool Dqn_FStr8_EqStr8Insensitive(Dqn_FStr8 const *lhs, Dqn_Str8 rhs) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs, Dqn_Str8EqCase_Insensitive); - return result; -} - -template bool Dqn_FStr8_EqFStr8(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs, Dqn_Str8EqCase eq_case) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, eq_case); - return result; -} - -template bool Dqn_FStr8_EqFStr8Insensitive(Dqn_FStr8 const *lhs, Dqn_FStr8 const *rhs) -{ - Dqn_Str8 lhs_s8 = Dqn_FStr8_ToStr8(lhs); - Dqn_Str8 rhs_s8 = Dqn_FStr8_ToStr8(rhs); - bool result = Dqn_Str8_Eq(lhs_s8, rhs_s8, Dqn_Str8EqCase_Insensitive); - return result; -} -#endif // !defined(DQN_NO_FSTR8) diff --git a/dqn_thread_context.cpp b/dqn_thread_context.cpp new file mode 100644 index 0000000..a695db1 --- /dev/null +++ b/dqn_thread_context.cpp @@ -0,0 +1,107 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$$\ $$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ +// \__$$ __|$$ | $$ |$$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ | $$ |$$ | $$ |$$ | $$ / $$ |$$ | $$ | +// $$ | $$$$$$$$ |$$$$$$$ |$$$$$\ $$$$$$$$ |$$ | $$ | +// $$ | $$ __$$ |$$ __$$< $$ __| $$ __$$ |$$ | $$ | +// $$ | $$ | $$ |$$ | $$ |$$ | $$ | $$ |$$ | $$ | +// $$ | $$ | $$ |$$ | $$ |$$$$$$$$\ $$ | $$ |$$$$$$$ | +// \__| \__| \__|\__| \__|\________|\__| \__|\_______/ +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$\ $$\ $$$$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ _____|$$ | $$ |\__$$ __| +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ | \$$\ $$ | $$ | +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$\ \$$$$ / $$ | +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __| $$ $$< $$ | +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ /\$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$$$$$$$\ $$ / $$ | $$ | +// \______/ \______/ \__| \__| \__| \________|\__| \__| \__| +// +// dqn_thread_context.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +DQN_THREAD_LOCAL Dqn_ThreadContext g_dqn_thread_context; + +// NOTE: [$TCTX] Dqn_ThreadContext ///////////////////////////////////////////////////////////////// +Dqn_Scratch::Dqn_Scratch(Dqn_ThreadContext *context, uint8_t context_index) +{ + arena = context->scratch_arenas[context_index]; + temp_mem = Dqn_Arena_TempMemBegin(arena); + destructed = false; +} + +Dqn_Scratch::~Dqn_Scratch() +{ + DQN_ASSERT(destructed == false); + Dqn_Arena_TempMemEnd(temp_mem); + destructed = true; +} + +DQN_API bool Dqn_Thread_ContextIsInit() +{ + bool result = g_dqn_thread_context.init; + return result; +} + +DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get() +{ + Dqn_ThreadContext *result = &g_dqn_thread_context; + if (result->init) + return result; + + result->init = true; + Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog; + DQN_HARD_ASSERTF(g_dqn_library && g_dqn_library->lib_init, "Library must be initialised by calling Dqn_Library_Init()"); + + // NOTE: Setup scratch arenas + DQN_FOR_UINDEX (index, DQN_ARRAY_UCOUNT(result->scratch_arenas)) { + + // NOTE: We allocate arenas so that they all come from the memory + // allocated from the address space of this library. This allows the + // library to be used across DLL boundaries for example as long as + // across the boundaries the same g_dqn_library instance is shared. + // + // On unload of the DLL, the address space is deallocated. If we stored + // these as TLS stack variables, these arenas would persist and point to + // invalid memory addresses. + Dqn_FStr8<128> label = Dqn_FStr8_InitF<128>("T%05u Scratch %zu", Dqn_OS_ThreadID(), index); + + // NOTE: Hence here we search for the arena. If it already exists then + // we are in that DLL boundary situation where the TLS data has been + // reinitialised and zero-ed out. We will try and find the matching + // arena in the catalog and re-use it. + // + // NOTE: This operation is so infrequent and the number of arenas one + // has in their program should be low that a string look-up should be + // cheap and fine. + Dqn_ArenaCatalogItem *catalog_item = Dqn_ArenaCatalog_Find(catalog, Dqn_FStr8_ToStr8(&label)); + if (catalog_item == &catalog->sentinel) { + Dqn_Arena *scratch = Dqn_ArenaCatalog_AllocLabelCopy(catalog, 0, 0, Dqn_ArenaFlag_AllocCanLeak, Dqn_FStr8_ToStr8(&label)); + result->scratch_arenas[index] = scratch; + } else { + // NOTE: Reuse the arena + result->scratch_arenas[index] = catalog_item->arena; + } + } + return result; +} + +// TODO: Is there a way to handle conflict arenas without the user needing to +// manually pass it in? +DQN_API Dqn_Scratch Dqn_Scratch_Get(void const *conflict_arena) +{ + Dqn_ThreadContext *context = Dqn_ThreadContext_Get(); + uint8_t context_index = (uint8_t)-1; + for (uint8_t index = 0; index < DQN_ARRAY_UCOUNT(context->scratch_arenas); index++) { + Dqn_Arena *arena = context->scratch_arenas[index]; + if (!conflict_arena || arena != conflict_arena) { + context_index = index; + break; + } + } + + DQN_ASSERT(context_index != (uint8_t)-1); + return Dqn_Scratch(context, context_index); +} diff --git a/dqn_thread_context.h b/dqn_thread_context.h new file mode 100644 index 0000000..5e801ea --- /dev/null +++ b/dqn_thread_context.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$$\ $$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ +// \__$$ __|$$ | $$ |$$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ | $$ |$$ | $$ |$$ | $$ / $$ |$$ | $$ | +// $$ | $$$$$$$$ |$$$$$$$ |$$$$$\ $$$$$$$$ |$$ | $$ | +// $$ | $$ __$$ |$$ __$$< $$ __| $$ __$$ |$$ | $$ | +// $$ | $$ | $$ |$$ | $$ |$$ | $$ | $$ |$$ | $$ | +// $$ | $$ | $$ |$$ | $$ |$$$$$$$$\ $$ | $$ |$$$$$$$ | +// \__| \__| \__|\__| \__|\________|\__| \__|\_______/ +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$\ $$\ $$$$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ _____|$$ | $$ |\__$$ __| +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ | \$$\ $$ | $$ | +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$\ \$$$$ / $$ | +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __| $$ $$< $$ | +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ /\$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$$$$$$$\ $$ / $$ | $$ | +// \______/ \______/ \__| \__| \__| \________|\__| \__| \__| +// +// dqn_thread_context.h -- Per thread data (e.g. scratch arenas) +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct Dqn_ThreadContext +{ + Dqn_b32 init; + Dqn_Arena *scratch_arenas[2]; +}; + +struct Dqn_Scratch +{ + Dqn_Scratch(Dqn_ThreadContext *context, uint8_t context_index); + ~Dqn_Scratch(); + + Dqn_Arena *arena; + Dqn_b32 destructed; + Dqn_ArenaTempMem temp_mem; +}; + +DQN_API bool Dqn_ThreadContext_IsInit(); +DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get(); +DQN_API Dqn_Scratch Dqn_Scratch_Get(void const *conflict_arena); diff --git a/dqn_type_info.h b/dqn_type_info.h new file mode 100644 index 0000000..a3d09f0 --- /dev/null +++ b/dqn_type_info.h @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$$\ $$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ +// \__$$ __|\$$\ $$ |$$ __$$\ $$ _____| \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ +// $$ | \$$\ $$ / $$ | $$ |$$ | $$ | $$$$\ $$ |$$ | $$ / $$ | +// $$ | \$$$$ / $$$$$$$ |$$$$$\ $$ | $$ $$\$$ |$$$$$\ $$ | $$ | +// $$ | \$$ / $$ ____/ $$ __| $$ | $$ \$$$$ |$$ __| $$ | $$ | +// $$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ | +// $$ | $$ | $$ | $$$$$$$$\ $$$$$$\ $$ | \$$ |$$ | $$$$$$ | +// \__| \__| \__| \________| \______|\__| \__|\__| \______/ +// +// dqn_type_info.h -- C++ type introspection +// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct Dqn_TypeEnumField +{ + uint16_t index; + Dqn_Str8 name; + Dqn_isize value; +}; + +struct Dqn_TypeStructField +{ + uint16_t index; + Dqn_Str8 name; + struct Dqn_TypeInfo const *type; + bool is_pointer; + Dqn_TypeStructField const *array_count; + uint16_t array_static_count; +}; + +enum Dqn_TypeInfoKind +{ + Dqn_TypeInfoKind_Basic, + Dqn_TypeInfoKind_Enum, + Dqn_TypeInfoKind_Struct, +}; + +struct Dqn_TypeInfo +{ + Dqn_Str8 name; + Dqn_TypeInfoKind kind; + Dqn_TypeStructField const *struct_field; + uint16_t struct_field_count; + Dqn_TypeEnumField const *enum_field; + uint16_t enum_field_count; + Dqn_isize enum_min; + Dqn_isize enum_max; +}; diff --git a/Misc/dqn_unit_tests.cpp b/dqn_unit_tests.cpp similarity index 84% rename from Misc/dqn_unit_tests.cpp rename to dqn_unit_tests.cpp index 83f9b5c..80da22e 100644 --- a/Misc/dqn_unit_tests.cpp +++ b/dqn_unit_tests.cpp @@ -1,23 +1,3 @@ -// NOTE: Preprocessor Config ======================================================================= -// #define DQN_TEST_WITH_MAIN Define this to enable the main function and allow standalone compiling -// and running of the file. -// #define DQN_TEST_WITH_KECCAK Define this to enable the main function and allow standalone compiling -// and running of the file. - -#if defined(DQN_TEST_WITH_MAIN) - #define DQN_ASAN_POISON 1 - #define DQN_ASAN_VET_POISON 1 - #define DQN_NO_CHECK_BREAK - #define DQN_IMPLEMENTATION - #include "dqn.h" -#endif - -#if defined(DQN_TEST_WITH_KECCAK) - #define DQN_KECCAK_IMPLEMENTATION - #include "dqn_keccak.h" - #include "dqn_tests_helpers.cpp" -#endif - #define DQN_UTEST_IMPLEMENTATION #include "dqn_utest.h" @@ -25,28 +5,22 @@ static Dqn_UTest Dqn_Test_Arena() { Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Arena") { - DQN_UTEST_TEST("Grow arena, reserve 4k, commit 1k (e.g. different sizes)") { - Dqn_Arena arena = {}; - Dqn_Arena_Grow(&arena, DQN_KILOBYTES(4), DQN_KILOBYTES(1), /*flags*/ 0); - Dqn_Arena_Free(&arena); - } - DQN_UTEST_TEST("Reused memory is zeroed out") { - - uint8_t alignment = 1; - Dqn_usize alloc_size = DQN_KILOBYTES(128); - Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(nullptr, alloc_size, alignment, Dqn_MemBlockFlag_Nil); - Dqn_Arena arena = {}; - Dqn_Arena_Grow(&arena, size_required.block_size, /*commit*/ size_required.block_size, /*flags*/ 0); + uint8_t alignment = 1; + Dqn_usize alloc_size = DQN_KILOBYTES(128); + Dqn_Arena arena = {}; + DQN_DEFER { + Dqn_Arena_Deinit(&arena); + }; // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena uintptr_t first_ptr_address = 0; { - Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena); + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(&arena); void *ptr = Dqn_Arena_Alloc(&arena, alloc_size, alignment, Dqn_ZeroMem_Yes); first_ptr_address = DQN_CAST(uintptr_t)ptr; DQN_MEMSET(ptr, 'z', alloc_size); - Dqn_Arena_EndTempMemory(temp_mem, false /*cancel*/); + Dqn_Arena_TempMemEnd(temp_mem); } // NOTE: Reallocate 128 kilobytes @@ -58,99 +32,60 @@ static Dqn_UTest Dqn_Test_Arena() // NOTE: Check that the bytes are set to 0 for (Dqn_usize i = 0; i < alloc_size; i++) DQN_UTEST_ASSERT(&test, ptr[i] == 0); - Dqn_Arena_Free(&arena); } DQN_UTEST_TEST("Test arena grows naturally, 1mb + 4mb") { - Dqn_Arena arena = {}; - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); + Dqn_Arena arena = Dqn_Arena_InitSize(DQN_MEGABYTES(2), DQN_MEGABYTES(2), Dqn_ArenaFlag_Nil); + DQN_DEFER { + Dqn_Arena_Deinit(&arena); + }; + + char *ptr_1mb = Dqn_Arena_NewArray(&arena, char, DQN_MEGABYTES(1), Dqn_ZeroMem_Yes); + char *ptr_4mb = Dqn_Arena_NewArray(&arena, char, DQN_MEGABYTES(4), Dqn_ZeroMem_Yes); DQN_UTEST_ASSERT(&test, ptr_1mb); DQN_UTEST_ASSERT(&test, ptr_4mb); - Dqn_MemBlock const *block_1mb = arena.head; - char const *block_1mb_begin = DQN_CAST(char *)block_1mb->data; - char const *block_1mb_end = DQN_CAST(char *)block_1mb->data + block_1mb->size; + Dqn_ArenaBlock const *block_4mb_begin = arena.curr; + char const *block_4mb_end = DQN_CAST(char *)block_4mb_begin + block_4mb_begin->reserve; - Dqn_MemBlock const *block_4mb = arena.curr; - char const *block_4mb_begin = DQN_CAST(char *)block_4mb->data; - char const *block_4mb_end = DQN_CAST(char *)block_4mb->data + block_4mb->size; + Dqn_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; + DQN_UTEST_ASSERTF(&test, block_1mb_begin, "New block should have been allocated"); + char const *block_1mb_end = DQN_CAST(char *)block_1mb_begin + block_1mb_begin->reserve; - DQN_UTEST_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked"); - DQN_UTEST_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); - DQN_UTEST_ASSERT (&test, arena.curr != arena.head); - - Dqn_Arena_Free(&arena); + DQN_UTEST_ASSERTF(&test, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DQN_UTEST_ASSERTF(&test, ptr_1mb >= DQN_CAST(char *)block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DQN_UTEST_ASSERTF(&test, ptr_4mb >= DQN_CAST(char *)block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); } DQN_UTEST_TEST("Test arena grows naturally, 1mb, temp memory 4mb") { - Dqn_Arena arena = {}; + Dqn_Arena arena = Dqn_Arena_InitSize(DQN_MEGABYTES(2), DQN_MEGABYTES(2), Dqn_ArenaFlag_Nil); + DQN_DEFER { + Dqn_Arena_Deinit(&arena); + }; // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); DQN_UTEST_ASSERT(&test, ptr_1mb); - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); + Dqn_ArenaTempMem temp_memory = Dqn_Arena_TempMemBegin(&arena); { - char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes); + char *ptr_4mb = Dqn_Arena_NewArray(&arena, char, DQN_MEGABYTES(4), Dqn_ZeroMem_Yes); DQN_UTEST_ASSERT(&test, ptr_4mb); - Dqn_MemBlock const *block_1mb = arena.head; - char const *block_1mb_begin = DQN_CAST(char *)block_1mb->data; - char const *block_1mb_end = DQN_CAST(char *)block_1mb->data + block_1mb->size; + Dqn_ArenaBlock const *block_4mb_begin = arena.curr; + char const *block_4mb_end = DQN_CAST(char *) block_4mb_begin + block_4mb_begin->reserve; - Dqn_MemBlock const *block_4mb = arena.curr; - char const *block_4mb_begin = DQN_CAST(char *)block_4mb->data; - char const *block_4mb_end = DQN_CAST(char *)block_4mb->data + block_4mb->size; + Dqn_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; + char const *block_1mb_end = DQN_CAST(char *) block_1mb_begin + block_1mb_begin->reserve; - DQN_UTEST_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked"); - DQN_UTEST_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); - DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); - DQN_UTEST_ASSERT (&test, arena.curr != arena.head); + DQN_UTEST_ASSERTF(&test, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DQN_UTEST_ASSERTF(&test, ptr_1mb >= DQN_CAST(char *)block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DQN_UTEST_ASSERTF(&test, ptr_4mb >= DQN_CAST(char *)block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); } - Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); - - DQN_UTEST_ASSERT (&test, arena.curr == arena.head); - DQN_UTEST_ASSERT (&test, arena.curr == arena.tail); - DQN_UTEST_ASSERT (&test, arena.curr->next == nullptr); - DQN_UTEST_ASSERTF(&test, arena.curr->size >= DQN_MEGABYTES(1), - "size=%zuMiB (%zuB), expect=%zuB", (arena.curr->size / 1024 / 1024), arena.curr->size, DQN_MEGABYTES(1)); - - Dqn_Arena_Free(&arena); - } - - DQN_UTEST_TEST("Init arena, temp region then free inside regions") { - Dqn_Arena arena = {}; - Dqn_Arena_Grow(&arena, DQN_KILOBYTES(1), 0, Dqn_ZeroMem_No); - - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); - { - char *ptr = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, ptr); - Dqn_Arena_Free(&arena); - } - Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); - Dqn_Arena_Free(&arena); - } - - DQN_UTEST_TEST("Init arena, allocate, temp region then free inside region") { - Dqn_Arena arena = {}; - char *outside = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, outside); - - Dqn_ArenaTempMemory temp_memory = Dqn_Arena_BeginTempMemory(&arena); - { - char *inside = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, DQN_MEGABYTES(2), 1 /*align*/, Dqn_ZeroMem_Yes); - DQN_UTEST_ASSERT(&test, inside); - Dqn_Arena_Free(&arena); - } - Dqn_Arena_EndTempMemory(temp_memory, false /*cancel*/); - Dqn_Arena_Free(&arena); + Dqn_Arena_TempMemEnd(temp_memory); + DQN_UTEST_ASSERT (&test, arena.curr->prev == nullptr); + DQN_UTEST_ASSERTF(&test, arena.curr->reserve >= DQN_MEGABYTES(1), "size=%zuMiB (%zuB), expect=%zuB", (arena.curr->reserve / 1024 / 1024), arena.curr->reserve, DQN_MEGABYTES(1)); } } return test; @@ -158,7 +93,7 @@ static Dqn_UTest Dqn_Test_Arena() static Dqn_UTest Dqn_Test_Bin() { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Bin") { DQN_UTEST_TEST("Convert 0x123") { @@ -553,11 +488,12 @@ static Dqn_UTest Dqn_Test_DSMap() { Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_DSMap") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); { - uint32_t const MAP_SIZE = 64; - Dqn_DSMap map = Dqn_DSMap_Init(MAP_SIZE); - DQN_DEFER { Dqn_DSMap_Deinit(&map); }; + Dqn_Arena arena = {}; + uint32_t const MAP_SIZE = 64; + Dqn_DSMap map = Dqn_DSMap_Init(&arena, MAP_SIZE); + DQN_DEFER { Dqn_DSMap_Deinit(&map, Dqn_ZeroMem_Yes); }; DQN_UTEST_TEST("Find non-existent value") { uint64_t *value = Dqn_DSMap_FindKeyStr8(&map, DQN_STR8("Foo")).value; @@ -597,10 +533,11 @@ static Dqn_UTest Dqn_Test_DSMap() case DSMapTestType_MakeSlot: prefix = DQN_STR8("Make slot"); break; } - Dqn_Arena_TempMemoryScope(scratch.arena); - uint32_t const MAP_SIZE = 64; - Dqn_DSMap map = Dqn_DSMap_Init(MAP_SIZE); - DQN_DEFER { Dqn_DSMap_Deinit(&map); }; + Dqn_ArenaTempMemScope temp_mem_scope = Dqn_ArenaTempMemScope(scratch.arena); + Dqn_Arena arena = {}; + uint32_t const MAP_SIZE = 64; + Dqn_DSMap map = Dqn_DSMap_Init(&arena, MAP_SIZE); + DQN_DEFER { Dqn_DSMap_Deinit(&map, Dqn_ZeroMem_Yes); }; DQN_UTEST_TEST("%.*s: Test growing", DQN_STR_FMT(prefix)) { uint64_t map_start_size = map.size; @@ -761,51 +698,51 @@ static Dqn_UTest Dqn_Test_Fs() Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Fs") { DQN_UTEST_TEST("Make directory recursive \"abcd/efgh\"") { - DQN_UTEST_ASSERTF(&test, Dqn_Fs_MakeDir(DQN_STR8("abcd/efgh")), "Failed to make directory"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_DirExists(DQN_STR8("abcd")), "Directory was not made"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_DirExists(DQN_STR8("abcd/efgh")), "Subdirectory was not made"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_Exists(DQN_STR8("abcd")) == false, "This function should only return true for files"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_Exists(DQN_STR8("abcd/efgh")) == false, "This function should only return true for files"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_Delete(DQN_STR8("abcd/efgh")), "Failed to delete directory"); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_Delete(DQN_STR8("abcd")), "Failed to cleanup directory"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_DirMake(DQN_STR8("abcd/efgh")), "Failed to make directory"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_DirExists(DQN_STR8("abcd")), "Directory was not made"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_DirExists(DQN_STR8("abcd/efgh")), "Subdirectory was not made"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(DQN_STR8("abcd")) == false, "This function should only return true for files"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(DQN_STR8("abcd/efgh")) == false, "This function should only return true for files"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_PathDelete(DQN_STR8("abcd/efgh")), "Failed to delete directory"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_PathDelete(DQN_STR8("abcd")), "Failed to cleanup directory"); } - DQN_UTEST_TEST("Write file, read it, copy it, move it and delete it") { + DQN_UTEST_TEST("File write, read, copy, move and delete") { // NOTE: Write step - Dqn_Str8 const SRC_FILE = DQN_STR8("dqn_test_file"); - Dqn_b32 write_result = Dqn_Fs_WriteCStr8(SRC_FILE.data, SRC_FILE.size, "test", 4); + Dqn_Str8 const SRC_FILE = DQN_STR8("dqn_test_file"); + Dqn_b32 write_result = Dqn_OS_WriteAll(SRC_FILE, DQN_STR8("test")); DQN_UTEST_ASSERT(&test, write_result); - DQN_UTEST_ASSERT(&test, Dqn_Fs_Exists(SRC_FILE)); + DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(SRC_FILE)); // NOTE: Read step - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 read_file = Dqn_Fs_Read(SRC_FILE, scratch.allocator); - DQN_UTEST_ASSERTF(&test, Dqn_Str8_IsValid(read_file), "Failed to load file"); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 read_file = Dqn_OS_ReadAll(SRC_FILE, scratch.arena); + DQN_UTEST_ASSERTF(&test, Dqn_Str8_HasData(read_file), "Failed to load file"); DQN_UTEST_ASSERTF(&test, read_file.size == 4, "File read wrong amount of bytes"); DQN_UTEST_ASSERTF(&test, Dqn_Str8_Eq(read_file, DQN_STR8("test")), "read(%zu): %.*s", read_file.size, DQN_STR_FMT(read_file)); // NOTE: Copy step - Dqn_Str8 const COPY_FILE = DQN_STR8("dqn_test_file_copy"); - Dqn_b32 copy_result = Dqn_Fs_Copy(SRC_FILE, COPY_FILE, true /*overwrite*/); + Dqn_Str8 const COPY_FILE = DQN_STR8("dqn_test_file_copy"); + Dqn_b32 copy_result = Dqn_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/); DQN_UTEST_ASSERT(&test, copy_result); - DQN_UTEST_ASSERT(&test, Dqn_Fs_Exists(COPY_FILE)); + DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(COPY_FILE)); // NOTE: Move step - Dqn_Str8 const MOVE_FILE = DQN_STR8("dqn_test_file_move"); - Dqn_b32 move_result = Dqn_Fs_Move(COPY_FILE, MOVE_FILE, true /*overwrite*/); + Dqn_Str8 const MOVE_FILE = DQN_STR8("dqn_test_file_move"); + Dqn_b32 move_result = Dqn_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/); DQN_UTEST_ASSERT(&test, move_result); - DQN_UTEST_ASSERT(&test, Dqn_Fs_Exists(MOVE_FILE)); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_Exists(COPY_FILE) == false, "Moving a file should remove the original"); + DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(MOVE_FILE)); + DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original"); // NOTE: Delete step - Dqn_b32 delete_src_file = Dqn_Fs_Delete(SRC_FILE); - Dqn_b32 delete_moved_file = Dqn_Fs_Delete(MOVE_FILE); + Dqn_b32 delete_src_file = Dqn_OS_PathDelete(SRC_FILE); + Dqn_b32 delete_moved_file = Dqn_OS_PathDelete(MOVE_FILE); DQN_UTEST_ASSERT(&test, delete_src_file); DQN_UTEST_ASSERT(&test, delete_moved_file); // NOTE: Deleting non-existent file fails - Dqn_b32 delete_non_existent_src_file = Dqn_Fs_Delete(SRC_FILE); - Dqn_b32 delete_non_existent_moved_file = Dqn_Fs_Delete(MOVE_FILE); + Dqn_b32 delete_non_existent_src_file = Dqn_OS_PathDelete(SRC_FILE); + Dqn_b32 delete_non_existent_moved_file = Dqn_OS_PathDelete(MOVE_FILE); DQN_UTEST_ASSERT(&test, delete_non_existent_moved_file == false); DQN_UTEST_ASSERT(&test, delete_non_existent_src_file == false); } @@ -966,7 +903,7 @@ Dqn_Str8 const DQN_UTEST_HASH_STRING_[] = void Dqn_Test_KeccakDispatch_(Dqn_UTest *test, int hash_type, Dqn_Str8 input) { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 input_hex = Dqn_Hex_BytesToStr8Arena(scratch.arena, input.data, input.size); switch(hash_type) @@ -1203,10 +1140,10 @@ static Dqn_UTest Dqn_Test_OS() } DQN_UTEST_TEST("Query executable directory") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 result = Dqn_OS_EXEDir(scratch.arena); - DQN_UTEST_ASSERT(&test, Dqn_Str8_IsValid(result)); - DQN_UTEST_ASSERTF(&test, Dqn_Fs_DirExists(result), "result(%zu): %.*s", result.size, DQN_STR_FMT(result)); + DQN_UTEST_ASSERT(&test, Dqn_Str8_HasData(result)); + DQN_UTEST_ASSERTF(&test, Dqn_OS_DirExists(result), "result(%zu): %.*s", result.size, DQN_STR_FMT(result)); } DQN_UTEST_TEST("Dqn_OS_PerfCounterNow") { @@ -1221,15 +1158,15 @@ static Dqn_UTest Dqn_Test_OS() } DQN_UTEST_TEST("Ticks to time are a correct order of magnitude") { - uint64_t a = Dqn_OS_PerfCounterNow(); - uint64_t b = Dqn_OS_PerfCounterNow(); - Dqn_f64 s = Dqn_OS_PerfCounterS(a, b); - Dqn_f64 ms = Dqn_OS_PerfCounterMs(a, b); - Dqn_f64 micro_s = Dqn_OS_PerfCounterMicroS(a, b); - Dqn_f64 ns = Dqn_OS_PerfCounterNs(a, b); - DQN_UTEST_ASSERTF(&test, s <= ms, "s: %f, ms: %f", s, ms); - DQN_UTEST_ASSERTF(&test, ms <= micro_s, "ms: %f, micro_s: %f", ms, micro_s); - DQN_UTEST_ASSERTF(&test, micro_s <= ns, "micro_s: %f, ns: %f", micro_s, ns); + uint64_t a = Dqn_OS_PerfCounterNow(); + uint64_t b = Dqn_OS_PerfCounterNow(); + Dqn_f64 s = Dqn_OS_PerfCounterS(a, b); + Dqn_f64 ms = Dqn_OS_PerfCounterMs(a, b); + Dqn_f64 us = Dqn_OS_PerfCounterUs(a, b); + Dqn_f64 ns = Dqn_OS_PerfCounterNs(a, b); + DQN_UTEST_ASSERTF(&test, s <= ms, "s: %f, ms: %f", s, ms); + DQN_UTEST_ASSERTF(&test, ms <= us, "ms: %f, us: %f", ms, us); + DQN_UTEST_ASSERTF(&test, us <= ns, "us: %f, ns: %f", us, ns); } } @@ -1376,8 +1313,8 @@ static Dqn_UTest Dqn_Test_Str8() } DQN_UTEST_TEST("Initialise with format string") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 string = Dqn_Str8_InitF(scratch.allocator, "%s", "AB"); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_Str8_InitF(scratch.arena, "%s", "AB"); DQN_UTEST_ASSERTF(&test, string.size == 2, "size: %I64u", string.size); DQN_UTEST_ASSERTF(&test, string.data[0] == 'A', "string[0]: %c", string.data[0]); DQN_UTEST_ASSERTF(&test, string.data[1] == 'B', "string[1]: %c", string.data[1]); @@ -1385,9 +1322,9 @@ static Dqn_UTest Dqn_Test_Str8() } DQN_UTEST_TEST("Copy string") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str8 string = DQN_STR8("AB"); - Dqn_Str8 copy = Dqn_Str8_Copy(scratch.allocator, string); + Dqn_Str8 copy = Dqn_Str8_Copy(scratch.arena, string); DQN_UTEST_ASSERTF(&test, copy.size == 2, "size: %I64u", copy.size); DQN_UTEST_ASSERTF(&test, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]); DQN_UTEST_ASSERTF(&test, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]); @@ -1400,8 +1337,8 @@ static Dqn_UTest Dqn_Test_Str8() } DQN_UTEST_TEST("Allocate string from arena") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 string = Dqn_Str8_Allocate(scratch.allocator, 2, Dqn_ZeroMem_No); + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 string = Dqn_Str8_Alloc(scratch.arena, 2, Dqn_ZeroMem_No); DQN_UTEST_ASSERTF(&test, string.size == 2, "size: %I64u", string.size); } @@ -1431,8 +1368,7 @@ static Dqn_UTest Dqn_Test_Str8() DQN_UTEST_ASSERTF(&test, Dqn_Str8_Eq(result, input), "%.*s", DQN_STR_FMT(result)); } - // NOTE: Dqn_Str8_IsAllDigits - // --------------------------------------------------------------------------------------------- + // NOTE: Dqn_Str8_IsAllDigits ////////////////////////////////////////////////////////////// DQN_UTEST_TEST("Is all digits fails on non-digit string") { Dqn_b32 result = Dqn_Str8_IsAll(DQN_STR8("@123string"), Dqn_Str8IsAll_Digits); DQN_UTEST_ASSERT(&test, result == false); @@ -1448,10 +1384,10 @@ static Dqn_UTest Dqn_Test_Str8() DQN_UTEST_ASSERT(&test, result == false); } - DQN_UTEST_TEST("Is all digits succeeds on string w/ 0 size") { + DQN_UTEST_TEST("Is all digits fails on string w/ 0 size") { char const buf[] = "@123string"; Dqn_b32 result = Dqn_Str8_IsAll(Dqn_Str8_Init(buf, 0), Dqn_Str8IsAll_Digits); - DQN_UTEST_ASSERT(&test, result); + DQN_UTEST_ASSERT(&test, !result); } DQN_UTEST_TEST("Is all digits success") { @@ -1503,17 +1439,16 @@ static Dqn_UTest Dqn_Test_Str8() } } - // NOTE: Dqn_Str8_ToI64 - // ========================================================================================= + // NOTE: Dqn_Str8_ToI64 //////////////////////////////////////////////////////////////////// DQN_UTEST_TEST("To I64: Convert null string") { Dqn_Str8ToI64Result result = Dqn_Str8_ToI64(Dqn_Str8_Init(nullptr, 5), 0); - DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.success); DQN_UTEST_ASSERT(&test, result.value == 0); } DQN_UTEST_TEST("To I64: Convert empty string") { Dqn_Str8ToI64Result result = Dqn_Str8_ToI64(DQN_STR8(""), 0); - DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.success); DQN_UTEST_ASSERT(&test, result.value == 0); } @@ -1563,13 +1498,13 @@ static Dqn_UTest Dqn_Test_Str8() // --------------------------------------------------------------------------------------------- DQN_UTEST_TEST("To U64: Convert nullptr") { Dqn_Str8ToU64Result result = Dqn_Str8_ToU64(Dqn_Str8_Init(nullptr, 5), 0); - DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.success); DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } DQN_UTEST_TEST("To U64: Convert empty string") { Dqn_Str8ToU64Result result = Dqn_Str8_ToU64(DQN_STR8(""), 0); - DQN_UTEST_ASSERT(&test, !result.success); + DQN_UTEST_ASSERT(&test, result.success); DQN_UTEST_ASSERTF(&test, result.value == 0, "result: %I64u", result.value); } @@ -1700,8 +1635,10 @@ static Dqn_UTest Dqn_Test_VArray() Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_VArray") { { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_VArray array = Dqn_VArray_InitByteSize(scratch.arena, DQN_KILOBYTES(64)); + Dqn_VArray array = Dqn_VArray_InitByteSize(DQN_KILOBYTES(64), 0); + DQN_DEFER { + Dqn_VArray_Deinit(&array); + }; DQN_UTEST_TEST("Test adding an array of items to the array") { uint32_t array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; @@ -1805,8 +1742,10 @@ static Dqn_UTest Dqn_Test_VArray() }; DQN_MSVC_WARNING_POP - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_VArray array = Dqn_VArray_InitByteSize(scratch.arena, DQN_KILOBYTES(64)); + Dqn_VArray array = Dqn_VArray_InitByteSize(DQN_KILOBYTES(64), 0); + DQN_DEFER { + Dqn_VArray_Deinit(&array); + }; // NOTE: Verify that the items returned from the data array are // contiguous in memory. @@ -1840,54 +1779,29 @@ static Dqn_UTest Dqn_Test_VArray() static Dqn_UTest Dqn_Test_Win() { Dqn_UTest test = {}; - DQN_UTEST_GROUP(test, "Dqn_Win") { - DQN_UTEST_TEST("Str8 to Str16 size required") { - int result = Dqn_Win_Str8ToStr16Buffer(DQN_STR8("a"), nullptr, 0); - DQN_UTEST_ASSERTF(&test, result == 1, "Size returned: %d. This size should not include the null-terminator", result); - } - - DQN_UTEST_TEST("Str16 to Str8 size required") { - int result = Dqn_Win_Str16ToStr8Buffer(DQN_STR16(L"a"), nullptr, 0); - DQN_UTEST_ASSERTF(&test, result == 1, "Size returned: %d. This size should not include the null-terminator", result); - } - - DQN_UTEST_TEST("Str8 to Str16 size required") { - int result = Dqn_Win_Str8ToStr16Buffer(DQN_STR8("String"), nullptr, 0); - DQN_UTEST_ASSERTF(&test, result == 6, "Size returned: %d. This size should not include the null-terminator", result); - } - - DQN_UTEST_TEST("Str16 to Str8 size required") { - int result = Dqn_Win_Str16ToStr8Buffer(DQN_STR16(L"String"), nullptr, 0); - DQN_UTEST_ASSERTF(&test, result == 6, "Size returned: %d. This size should not include the null-terminator", result); - } + DQN_UTEST_GROUP(test, "OS Win32") { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 input8 = DQN_STR8("String"); + Dqn_Str16 input16 = Dqn_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; DQN_UTEST_TEST("Str8 to Str16") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 const INPUT = DQN_STR8("String"); - int size_required = Dqn_Win_Str8ToStr16Buffer(INPUT, nullptr, 0); - wchar_t *string = Dqn_Arena_NewArray(scratch.arena, wchar_t, size_required + 1, Dqn_ZeroMem_No); - - // Fill the string with error sentinels, which ensures the string is zero terminated - DQN_MEMSET(string, 'Z', size_required + 1); - - int size_returned = Dqn_Win_Str8ToStr16Buffer(INPUT, string, size_required + 1); - wchar_t const EXPECTED[] = {L'S', L't', L'r', L'i', L'n', L'g', 0}; - - DQN_UTEST_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned); - DQN_UTEST_ASSERTF(&test, size_returned == DQN_ARRAY_UCOUNT(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DQN_ARRAY_UCOUNT(EXPECTED) - 1); - DQN_UTEST_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0); + Dqn_Str16 result = Dqn_Win_Str8ToStr16(scratch.arena, input8); + DQN_UTEST_ASSERT(&test, result == input16); } - DQN_UTEST_TEST("Str16 to Str8: No null-terminate") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 INPUT = DQN_STR16(L"String"); - int size_required = Dqn_Win_Str16ToStr8Buffer(INPUT, nullptr, 0); - char *string = Dqn_Arena_NewArray(scratch.arena, char, size_required + 1, Dqn_ZeroMem_No); + DQN_UTEST_TEST("Str16 to Str8") { + Dqn_Str8 result = Dqn_Win_Str16ToStr8(scratch.arena, input16); + DQN_UTEST_ASSERT(&test, result == input8); + } - // Fill the string with error sentinels, which ensures the string is zero terminated + DQN_UTEST_TEST("Str16 to Str8: Null terminates string") { + int size_required = Dqn_Win_Str16ToStr8Buffer(input16, nullptr, 0); + char *string = Dqn_Arena_NewArray(scratch.arena, char, size_required + 1, Dqn_ZeroMem_No); + + // Fill the string with error sentinels DQN_MEMSET(string, 'Z', size_required + 1); - int size_returned = Dqn_Win_Str16ToStr8Buffer(INPUT, string, size_required + 1); + int size_returned = Dqn_Win_Str16ToStr8Buffer(input16, string, size_required + 1); char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; DQN_UTEST_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned); @@ -1895,49 +1809,21 @@ static Dqn_UTest Dqn_Test_Win() DQN_UTEST_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0); } - DQN_UTEST_TEST("Str8 to Str16 arena") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 const INPUT = DQN_STR8("String"); - Dqn_Str16 string16 = Dqn_Win_Str8ToStr16(scratch.arena, INPUT); - - int size_returned = Dqn_Win_Str8ToStr16Buffer(INPUT, nullptr, 0); - wchar_t const EXPECTED[] = {L'S', L't', L'r', L'i', L'n', L'g', 0}; - - DQN_UTEST_ASSERTF(&test, DQN_CAST(int)string16.size == size_returned, "string_size: %d, result: %d", DQN_CAST(int)string16.size, size_returned); - DQN_UTEST_ASSERTF(&test, DQN_CAST(int)string16.size == DQN_ARRAY_UCOUNT(EXPECTED) - 1, "string_size: %d, expected: %zu", DQN_CAST(int)string16.size, DQN_ARRAY_UCOUNT(EXPECTED) - 1); - DQN_UTEST_ASSERT(&test, DQN_MEMCMP(EXPECTED, string16.data, sizeof(EXPECTED)) == 0); - } - - DQN_UTEST_TEST("Str16 to Str8: No null-terminate arena") { - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str16 INPUT = DQN_STR16(L"String"); - Dqn_Str8 string8 = Dqn_Win_Str16ToStr8(scratch.arena, INPUT); - - int size_returned = Dqn_Win_Str16ToStr8Buffer(INPUT, nullptr, 0); - char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + DQN_UTEST_TEST("Str16 to Str8: Arena null terminates string") { + Dqn_Str8 string8 = Dqn_Win_Str16ToStr8(scratch.arena, input16); + int size_returned = Dqn_Win_Str16ToStr8Buffer(input16, nullptr, 0); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; DQN_UTEST_ASSERTF(&test, DQN_CAST(int)string8.size == size_returned, "string_size: %d, result: %d", DQN_CAST(int)string8.size, size_returned); DQN_UTEST_ASSERTF(&test, DQN_CAST(int)string8.size == DQN_ARRAY_UCOUNT(EXPECTED) - 1, "string_size: %d, expected: %zu", DQN_CAST(int)string8.size, DQN_ARRAY_UCOUNT(EXPECTED) - 1); - DQN_UTEST_ASSERT(&test, DQN_MEMCMP(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); + DQN_UTEST_ASSERT (&test, DQN_MEMCMP(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); } } return test; } -static void Dqn_Test_CustomLogProc(Dqn_Str8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args) +void Dqn_Test_RunSuite() { - (void)user_data; - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_Str8 log = Dqn_Log_MakeStr8(scratch.allocator, true /*colour*/, type, log_type, call_site, fmt, args); - DQN_UTEST_LOG("%.*s", DQN_STR_FMT(log)); -} - -static void Dqn_Test_RunSuite() -{ - Dqn_Library *dqn_library = Dqn_Library_Init(Dqn_LibraryOnInit_LogFeatures); - auto *prev_log_callback = dqn_library->log_callback; - dqn_library->log_callback = Dqn_Test_CustomLogProc; - Dqn_UTest tests[] = { Dqn_Test_Arena(), @@ -1968,7 +1854,6 @@ static void Dqn_Test_RunSuite() } fprintf(stdout, "Summary: %d/%d tests succeeded\n", total_good_tests, total_tests); - dqn_library->log_callback = prev_log_callback; } #if defined(DQN_TEST_WITH_MAIN) diff --git a/Misc/dqn_utest.h b/dqn_utest.h similarity index 85% rename from Misc/dqn_utest.h rename to dqn_utest.h index f4b356e..2a3fcdd 100644 --- a/Misc/dqn_utest.h +++ b/dqn_utest.h @@ -1,11 +1,25 @@ #if !defined(DQN_UTEST_H) #define DQN_UTEST_H + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$$\ +// $$ | $$ |\__$$ __|$$ _____|$$ __$$\\__$$ __| +// $$ | $$ | $$ | $$ | $$ / \__| $$ | +// $$ | $$ | $$ | $$$$$\ \$$$$$$\ $$ | +// $$ | $$ | $$ | $$ __| \____$$\ $$ | +// $$ | $$ | $$ | $$ | $$\ $$ | $$ | +// \$$$$$$ | $$ | $$$$$$$$\ \$$$$$$ | $$ | +// \______/ \__| \________| \______/ \__| +// +// dqn_utest.h -- Extremely minimal unit testing framework +// +//////////////////////////////////////////////////////////////////////////////////////////////////// // -// NOTE: Overview ================================================================================== // A super minimal testing framework, most of the logic here is the pretty // printing of test results. - -// NOTE: Configuration ============================================================================= +// +// NOTE: Configuration ///////////////////////////////////////////////////////////////////////////// // // #define DQN_UTEST_IMPLEMENTATION // Define this in one and only one C++ file to enable the implementation @@ -32,11 +46,11 @@ // Define this to a terminal color code to specify what color sucess will be // presented as. -// NOTE: Macros ==================================================================================== +// NOTE: Macros //////////////////////////////////////////////////////////////////////////////////// #include #include #include -#include +#include #if !defined(DQN_UTEST_RESULT_LPAD) #define DQN_UTEST_RESULT_LPAD 90 @@ -90,7 +104,7 @@ "%*sAssertion Triggered\n" \ "%*sFile: %s:%d\n" \ "%*sExpression: [" #expr "]\n" \ - "%*sReason: " fmt "\n", \ + "%*sReason: " fmt "\n\n", \ DQN_UTEST_SPACING * 2, \ "", \ DQN_UTEST_SPACING * 3, \ @@ -111,7 +125,7 @@ (test)->state = Dqn_UTestState_TestFailed; \ fprintf(stderr, \ "%*sFile: %s:%d\n" \ - "%*sExpression: [" #expr "]\n", \ + "%*sExpression: [" #expr "]\n\n", \ DQN_UTEST_SPACING * 2, \ "", \ file, \ @@ -121,7 +135,7 @@ } \ } while (0) -// NOTE: Header ==================================================================================== +// NOTE: Header //////////////////////////////////////////////////////////////////////////////////// typedef enum Dqn_UTestState { Dqn_UTestState_Nil, Dqn_UTestState_TestBegun, @@ -141,8 +155,9 @@ void Dqn_UTest_PrintStats(Dqn_UTest *test); void Dqn_UTest_BeginV(Dqn_UTest *test, char const *fmt, va_list args); void Dqn_UTest_Begin(Dqn_UTest *test, char const *fmt, ...); void Dqn_UTest_End(Dqn_UTest *test); +#endif // DQN_UTEST_H -// NOTE: Implementation ============================================================================ +// NOTE: Implementation //////////////////////////////////////////////////////////////////////////// #if defined(DQN_UTEST_IMPLEMENTATION) void Dqn_UTest_PrintStats(Dqn_UTest *test) { @@ -209,4 +224,3 @@ void Dqn_UTest_End(Dqn_UTest *test) test->state = Dqn_UTestState_Nil; } #endif // DQN_UTEST_IMPLEMENTATION -#endif // DQN_UTEST_H diff --git a/dqn_win32.h b/dqn_win32.h index 1df46f2..63b2622 100644 --- a/dqn_win32.h +++ b/dqn_win32.h @@ -1,23 +1,36 @@ -#if defined(DQN_OS_WIN32) -#pragma comment(lib, "bcrypt") -#pragma comment(lib, "wininet") -#pragma comment(lib, "dbghelp") +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \__/ \__|\______|\__| \__| \______/ \________| +// +// dqn_win32.h -- Windows replacement header +// +//////////////////////////////////////////////////////////////////////////////////////////////////// -#if defined(DQN_NO_WIN32_MIN_HEADER) +#if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL) + #pragma comment(lib, "bcrypt") + #pragma comment(lib, "winhttp") + #pragma comment(lib, "dbghelp") +#endif + +#if defined(DQN_NO_WIN32_MIN_HEADER) || defined(_INC_WINDOWS) + #define WIN32_LEAN_AND_MEAN + #include // LONG #include // Dqn_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc #include // Dqn_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc + #include // WinHttp* #include - #if !defined(DQN_NO_WINNET) - DQN_MSVC_WARNING_PUSH - DQN_MSVC_WARNING_DISABLE(6553) // wininet.h|940 warning| The annotation for function 'InternetConnectA' on _Param_(8) does not apply to a value type. - #include // Dqn_Win_Net -> InternetConnect ... etc - DQN_MSVC_WARNING_POP - #endif // DQN_NO_WINNET -#elif !defined(_INC_WINDOWS) +#else DQN_MSVC_WARNING_PUSH DQN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union - // NOTE: basetsd.h ============================================================================= + // NOTE: basetsd.h ///////////////////////////////////////////////////////////////////////////// typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; typedef ULONG_PTR SIZE_T, *PSIZE_T; typedef __int64 LONG_PTR, *PLONG_PTR; @@ -25,7 +38,7 @@ typedef unsigned __int64 ULONG64, *PULONG64; typedef unsigned __int64 DWORD64, *PDWORD64; - // NOTE: shared/minwindef.h ==================================================================== + // NOTE: shared/minwindef.h //////////////////////////////////////////////////////////////////// struct HINSTANCE__ { int unused; }; @@ -40,6 +53,8 @@ typedef unsigned char BYTE; typedef unsigned char UCHAR; typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ + typedef void * HANDLE; + typedef HANDLE HLOCAL; #define MAX_PATH 260 @@ -48,15 +63,15 @@ DWORD dwHighDateTime; } FILETIME, *PFILETIME, *LPFILETIME; - // NOTE: shared/winerror.h ===================================================================== + // NOTE: shared/winerror.h ///////////////////////////////////////////////////////////////////// // NOTE: GetModuleFileNameW #define ERROR_INSUFFICIENT_BUFFER 122L // dderror - // NOTE: um/winnls.h =========================================================================== + // NOTE: um/winnls.h /////////////////////////////////////////////////////////////////////////// // NOTE: MultiByteToWideChar #define CP_UTF8 65001 // UTF-8 translation - // NOTE: um/winnt.h ============================================================================ + // NOTE: um/winnt.h //////////////////////////////////////////////////////////////////////////// typedef void VOID; typedef __int64 LONGLONG; typedef unsigned __int64 ULONGLONG; @@ -66,6 +81,7 @@ typedef long LONG; typedef wchar_t WCHAR; // wc, 16-bit UNICODE character typedef CHAR * NPSTR, *LPSTR, *PSTR; + typedef WCHAR * NWPSTR, *LPWSTR, *PWSTR; // NOTE: VirtualAlloc: Allocation Type #define MEM_RESERVE 0x00002000 @@ -93,6 +109,10 @@ #define FILE_APPEND_DATA (0x0004) // file // NOTE: CreateFile/FindFirstFile + #define FILE_SHARE_READ 0x00000001 + #define FILE_SHARE_WRITE 0x00000002 + #define FILE_SHARE_DELETE 0x00000004 + #define FILE_ATTRIBUTE_READONLY 0x00000001 #define FILE_ATTRIBUTE_HIDDEN 0x00000002 #define FILE_ATTRIBUTE_SYSTEM 0x00000004 @@ -102,6 +122,11 @@ // NOTE: STACKFRAME64 #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) + // NOTE: WaitForSingleObject + #define WAIT_TIMEOUT 258L // dderror + #define STATUS_WAIT_0 ((DWORD )0x00000000L) + #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) + typedef union _ULARGE_INTEGER { struct { DWORD LowPart; @@ -230,16 +255,71 @@ DWORD64 LastExceptionFromRip; } CONTEXT; + typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; + } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; + + typedef struct _RTL_CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + struct _RTL_CRITICAL_SECTION *CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD Flags; + WORD CreatorBackTraceIndexHigh; + WORD Identifier; + } RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG; + + #pragma pack(push, 8) + typedef struct _RTL_CRITICAL_SECTION { + PRTL_CRITICAL_SECTION_DEBUG DebugInfo; + + // + // The following three fields control entering and exiting the critical + // section for the resource + // + + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; // from the thread's ClientId->UniqueThread + HANDLE LockSemaphore; + ULONG_PTR SpinCount; // force size on 64-bit systems when packed + } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; + #pragma pack(pop) + + typedef struct _MODLOAD_DATA { + DWORD ssize; // size of this struct + DWORD ssig; // signature identifying the passed data + VOID *data; // pointer to passed data + DWORD size; // size of passed data + DWORD flags; // options + } MODLOAD_DATA, *PMODLOAD_DATA; + + #define SLMFLAG_VIRTUAL 0x1 + #define SLMFLAG_ALT_INDEX 0x2 + #define SLMFLAG_NO_SYMBOLS 0x4 + extern "C" { - __declspec(dllimport) VOID __stdcall RtlCaptureContext(CONTEXT *ContextRecord); - __declspec(dllimport) HANDLE __stdcall GetCurrentProcess(void); - __declspec(dllimport) HANDLE __stdcall GetCurrentThread(void); - __declspec(dllimport) DWORD __stdcall SymSetOptions(DWORD SymOptions); - __declspec(dllimport) BOOL __stdcall SymInitialize(HANDLE hProcess, const CHAR* UserSearchPath, BOOL fInvadeProcess); + __declspec(dllimport) VOID __stdcall RtlCaptureContext(CONTEXT *ContextRecord); + __declspec(dllimport) HANDLE __stdcall GetCurrentProcess(void); + __declspec(dllimport) HANDLE __stdcall GetCurrentThread(void); + __declspec(dllimport) DWORD __stdcall SymSetOptions(DWORD SymOptions); + __declspec(dllimport) BOOL __stdcall SymInitialize(HANDLE hProcess, const CHAR* UserSearchPath, BOOL fInvadeProcess); + __declspec(dllimport) DWORD64 __stdcall SymLoadModuleEx(HANDLE hProcess, HANDLE hFile, CHAR const *ImageName, CHAR const *ModuleName, DWORD64 BaseOfDll, DWORD DllSize, MODLOAD_DATA *Data, DWORD Flags); + __declspec(dllimport) BOOL __stdcall SymUnloadModule64(HANDLE hProcess, DWORD64 BaseOfDll); } - // NOTE: handleapi.h =========================================================================== + // NOTE: shared/windef.h //////////////////////////////////////////////////////////////////// + typedef struct tagPOINT + { + LONG x; + LONG y; + } POINT, *PPOINT, *NPPOINT, *LPPOINT; + + // NOTE: handleapi.h /////////////////////////////////////////////////////////////////////////// #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) extern "C" @@ -247,7 +327,7 @@ __declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE hObject); } - // NOTE: consoleapi.h =========================================================================== + // NOTE: consoleapi.h /////////////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) BOOL __stdcall WriteConsoleA(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved); @@ -257,11 +337,17 @@ __declspec(dllimport) BOOL __stdcall GetConsoleMode(HANDLE hConsoleHandle, DWORD *lpMode); } - // NOTE: um/minwinbase.h ======================================================================= + // NOTE: um/minwinbase.h /////////////////////////////////////////////////////////////////////// // NOTE: FindFirstFile #define FIND_FIRST_EX_CASE_SENSITIVE 0x00000001 #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 + // NOTE: WaitFor.. + #define WAIT_FAILED ((DWORD)0xFFFFFFFF) + #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED_0 ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + typedef enum _GET_FILEEX_INFO_LEVELS { GetFileExInfoStandard, GetFileExMaxInfoLevel @@ -329,7 +415,9 @@ HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; - // NOTE: um/winbase.h ========================================================================== + typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; + + // NOTE: um/winbase.h ////////////////////////////////////////////////////////////////////////// #define WAIT_FAILED ((DWORD)0xFFFFFFFF) #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) @@ -344,9 +432,10 @@ #define MOVEFILE_COPY_ALLOWED 0x00000002 // NOTE: FormatMessageA - #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 - #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 - #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 + #define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 + #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 + #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 + #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 // NOTE: CreateProcessW #define STARTF_USESTDHANDLES 0x00000100 @@ -356,17 +445,18 @@ __declspec(dllimport) BOOL __stdcall MoveFileExW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, DWORD dwFlags); __declspec(dllimport) BOOL __stdcall CopyFileW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, BOOL bFailIfExists); __declspec(dllimport) HANDLE __stdcall CreateSemaphoreA(SECURITY_ATTRIBUTES *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, const CHAR *lpName); - __declspec(dllimport) DWORD __stdcall FormatMessageA (DWORD dwFlags, const VOID *lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, DWORD nSize, va_list *Arguments); + __declspec(dllimport) DWORD __stdcall FormatMessageW (DWORD dwFlags, VOID const *lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments); + __declspec(dllimport) HLOCAL __stdcall LocalFree (HLOCAL hMem); } - // NOTE: um/stringapiset.h ===================================================================== + // NOTE: um/stringapiset.h ///////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, const CHAR *lpMultiByteStr, int cbMultiByte, WCHAR *lpWideCharStr, int cchWideChar); __declspec(dllimport) int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, const WCHAR *lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, const CHAR *lpDefaultChar, BOOL *lpUsedDefaultChar); } - // NOTE: um/fileapi.h ========================================================================== + // NOTE: um/fileapi.h ////////////////////////////////////////////////////////////////////////// #define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF) #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) @@ -401,14 +491,14 @@ } - // NOTE: um/processenv.h ======================================================================= + // NOTE: um/processenv.h /////////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) DWORD __stdcall GetCurrentDirectoryW(DWORD nBufferLength, WCHAR *lpBuffer); __declspec(dllimport) HANDLE __stdcall GetStdHandle(DWORD nStdHandle); } - // NOTE: um/sysinfoapi.h ======================================================================= + // NOTE: um/sysinfoapi.h /////////////////////////////////////////////////////////////////////// typedef struct _SYSTEM_INFO { union { DWORD dwOemId; // Obsolete field...do not use @@ -436,7 +526,7 @@ __declspec(dllimport) VOID __stdcall GetLocalTime(SYSTEMTIME *lpSystemTime); } - // NOTE: shared/windef.h ======================================================================= + // NOTE: shared/windef.h /////////////////////////////////////////////////////////////////////// typedef struct tagRECT { LONG left; LONG top; @@ -463,7 +553,20 @@ #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) - // NOTE: um/winuser.h ========================================================================== + // NOTE: um/winuser.h ////////////////////////////////////////////////////////////////////////// + typedef struct tagWINDOWPLACEMENT { + UINT length; + UINT flags; + UINT showCmd; + POINT ptMinPosition; + POINT ptMaxPosition; + RECT rcNormalPosition; + #ifdef _MAC + RECT rcDevice; + #endif + } WINDOWPLACEMENT; + typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT; + #define SW_HIDE 0 #define SW_NORMAL 1 #define SW_MAXIMIZE 3 @@ -477,88 +580,262 @@ __declspec(dllimport) BOOL __stdcall SetWindowPos (HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); __declspec(dllimport) UINT __stdcall GetWindowModuleFileNameA(HWND hwnd, LPSTR pszFileName, UINT cchFileNameMax); __declspec(dllimport) BOOL __stdcall ShowWindow (HWND hWnd, int nCmdShow); + __declspec(dllimport) BOOL __stdcall GetWindowPlacement (HWND hWnd, WINDOWPLACEMENT *lpwndpl); + } - // NOTE: um/wininet.h ========================================================================== + // NOTE: um/wininet.h ////////////////////////////////////////////////////////////////////////// typedef WORD INTERNET_PORT; typedef VOID *HINTERNET; - #define INTERNET_OPEN_TYPE_PRECONFIG 0 // use registry configuration - #define INTERNET_INVALID_PORT_NUMBER 0 // use the protocol-specific default - #define INTERNET_DEFAULT_FTP_PORT 21 // default for FTP servers - #define INTERNET_DEFAULT_HTTP_PORT 80 // " " HTTP " - #define INTERNET_DEFAULT_HTTPS_PORT 443 // " " HTTPS " - #define INTERNET_SERVICE_HTTP 3 + // NOTE: um/winhttp.h ////////////////////////////////////////////////////////////////////////// + #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 + #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 + #define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 + #define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 - #define INTERNET_OPTION_USERNAME 28 - #define INTERNET_OPTION_PASSWORD 29 - #define INTERNET_OPTION_USER_AGENT 41 + #define INTERNET_DEFAULT_PORT 0 // use the protocol-specific default + #define INTERNET_DEFAULT_HTTP_PORT 80 // " " HTTP " + #define INTERNET_DEFAULT_HTTPS_PORT 443 // " " HTTPS " - #define INTERNET_FLAG_NO_AUTH 0x00040000 // no automatic authentication handling - #define INTERNET_FLAG_SECURE 0x00800000 // use PCT/SSL if applicable (HTTP) + // NOTE: WinHttpOpen + #define WINHTTP_FLAG_ASYNC 0x10000000 // this session is asynchronous (where supported) + #define WINHTTP_FLAG_SECURE_DEFAULTS 0x30000000 // note that this flag also forces async - #define HTTP_QUERY_RAW_HEADERS 21 // special: all headers as ASCIIZ - #define HTTP_QUERY_RAW_HEADERS_CRLF 22 // special: all headers + // NOTE: WinHttpOpenRequest + #define WINHTTP_FLAG_SECURE 0x00800000 // use SSL if applicable (HTTPS) + #define WINHTTP_FLAG_ESCAPE_PERCENT 0x00000004 // if escaping enabled, escape percent as well + #define WINHTTP_FLAG_NULL_CODEPAGE 0x00000008 // assume all symbols are ASCII, use fast convertion + #define WINHTTP_FLAG_ESCAPE_DISABLE 0x00000040 // disable escaping + #define WINHTTP_FLAG_ESCAPE_DISABLE_QUERY 0x00000080 // if escaping enabled escape path part, but do not escape query + #define WINHTTP_FLAG_BYPASS_PROXY_CACHE 0x00000100 // add "pragma: no-cache" request header + #define WINHTTP_FLAG_REFRESH WINHTTP_FLAG_BYPASS_PROXY_CACHE + #define WINHTTP_FLAG_AUTOMATIC_CHUNKING 0x00000200 // Send request without content-length header or chunked TE - #define HTTP_ADDREQ_FLAG_ADD_IF_NEW 0x10000000 - #define HTTP_ADDREQ_FLAG_ADD 0x20000000 - #define HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA 0x40000000 - #define HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON 0x01000000 - #define HTTP_ADDREQ_FLAG_COALESCE HTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA - #define HTTP_ADDREQ_FLAG_REPLACE 0x80000000 + #define WINHTTP_NO_PROXY_NAME NULL + #define WINHTTP_NO_PROXY_BYPASS NULL - typedef enum { - INTERNET_SCHEME_PARTIAL = -2, - INTERNET_SCHEME_UNKNOWN = -1, - INTERNET_SCHEME_DEFAULT = 0, - INTERNET_SCHEME_FTP, - INTERNET_SCHEME_GOPHER, - INTERNET_SCHEME_HTTP, - INTERNET_SCHEME_HTTPS, - INTERNET_SCHEME_FILE, - INTERNET_SCHEME_NEWS, - INTERNET_SCHEME_MAILTO, - INTERNET_SCHEME_SOCKS, - INTERNET_SCHEME_JAVASCRIPT, - INTERNET_SCHEME_VBSCRIPT, - INTERNET_SCHEME_RES, - INTERNET_SCHEME_FIRST = INTERNET_SCHEME_FTP, - INTERNET_SCHEME_LAST = INTERNET_SCHEME_RES - } INTERNET_SCHEME, * LPINTERNET_SCHEME; + // + // WINHTTP_QUERY_FLAG_NUMBER - if this bit is set in the dwInfoLevel parameter of + // HttpQueryHeader(), then the value of the header will be converted to a number + // before being returned to the caller, if applicable + // + #define WINHTTP_QUERY_FLAG_NUMBER 0x20000000 - typedef struct { - DWORD dwStructSize; // size of this structure. Used in version check - LPSTR lpszScheme; // pointer to scheme name - DWORD dwSchemeLength; // length of scheme name - INTERNET_SCHEME nScheme; // enumerated scheme type (if known) - LPSTR lpszHostName; // pointer to host name - DWORD dwHostNameLength; // length of host name - INTERNET_PORT nPort; // converted port number - LPSTR lpszUserName; // pointer to user name - DWORD dwUserNameLength; // length of user name - LPSTR lpszPassword; // pointer to password - DWORD dwPasswordLength; // length of password - LPSTR lpszUrlPath; // pointer to URL-path - DWORD dwUrlPathLength; // length of URL-path - LPSTR lpszExtraInfo; // pointer to extra information (e.g. ?foo or #foo) - DWORD dwExtraInfoLength; // length of extra information - } URL_COMPONENTSA, * LPURL_COMPONENTSA; + #define WINHTTP_QUERY_MIME_VERSION 0 + #define WINHTTP_QUERY_CONTENT_TYPE 1 + #define WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING 2 + #define WINHTTP_QUERY_CONTENT_ID 3 + #define WINHTTP_QUERY_CONTENT_DESCRIPTION 4 + #define WINHTTP_QUERY_CONTENT_LENGTH 5 + #define WINHTTP_QUERY_CONTENT_LANGUAGE 6 + #define WINHTTP_QUERY_ALLOW 7 + #define WINHTTP_QUERY_PUBLIC 8 + #define WINHTTP_QUERY_DATE 9 + #define WINHTTP_QUERY_EXPIRES 10 + #define WINHTTP_QUERY_LAST_MODIFIED 11 + #define WINHTTP_QUERY_MESSAGE_ID 12 + #define WINHTTP_QUERY_URI 13 + #define WINHTTP_QUERY_DERIVED_FROM 14 + #define WINHTTP_QUERY_COST 15 + #define WINHTTP_QUERY_LINK 16 + #define WINHTTP_QUERY_PRAGMA 17 + #define WINHTTP_QUERY_VERSION 18 // special: part of status line + #define WINHTTP_QUERY_STATUS_CODE 19 // special: part of status line + #define WINHTTP_QUERY_STATUS_TEXT 20 // special: part of status line + #define WINHTTP_QUERY_RAW_HEADERS 21 // special: all headers as ASCIIZ + #define WINHTTP_QUERY_RAW_HEADERS_CRLF 22 // special: all headers + #define WINHTTP_QUERY_CONNECTION 23 + #define WINHTTP_QUERY_ACCEPT 24 + #define WINHTTP_QUERY_ACCEPT_CHARSET 25 + #define WINHTTP_QUERY_ACCEPT_ENCODING 26 + #define WINHTTP_QUERY_ACCEPT_LANGUAGE 27 + #define WINHTTP_QUERY_AUTHORIZATION 28 + #define WINHTTP_QUERY_CONTENT_ENCODING 29 + #define WINHTTP_QUERY_FORWARDED 30 + #define WINHTTP_QUERY_FROM 31 + #define WINHTTP_QUERY_IF_MODIFIED_SINCE 32 + #define WINHTTP_QUERY_LOCATION 33 + #define WINHTTP_QUERY_ORIG_URI 34 + #define WINHTTP_QUERY_REFERER 35 + #define WINHTTP_QUERY_RETRY_AFTER 36 + #define WINHTTP_QUERY_SERVER 37 + #define WINHTTP_QUERY_TITLE 38 + #define WINHTTP_QUERY_USER_AGENT 39 + #define WINHTTP_QUERY_WWW_AUTHENTICATE 40 + #define WINHTTP_QUERY_PROXY_AUTHENTICATE 41 + #define WINHTTP_QUERY_ACCEPT_RANGES 42 + #define WINHTTP_QUERY_SET_COOKIE 43 + #define WINHTTP_QUERY_COOKIE 44 + #define WINHTTP_QUERY_REQUEST_METHOD 45 // special: GET/POST etc. + #define WINHTTP_QUERY_REFRESH 46 + #define WINHTTP_QUERY_CONTENT_DISPOSITION 47 + + // NOTE: WinHttpQueryHeaders prettifiers for optional parameters. + #define WINHTTP_HEADER_NAME_BY_INDEX NULL + #define WINHTTP_NO_OUTPUT_BUFFER NULL + #define WINHTTP_NO_HEADER_INDEX NULL + + // NOTE: Http Response Status Codes + #define HTTP_STATUS_CONTINUE 100 // OK to continue with request + #define HTTP_STATUS_SWITCH_PROTOCOLS 101 // server has switched protocols in upgrade header + + #define HTTP_STATUS_OK 200 // request completed + #define HTTP_STATUS_CREATED 201 // object created, reason = new URI + #define HTTP_STATUS_ACCEPTED 202 // async completion (TBS) + #define HTTP_STATUS_PARTIAL 203 // partial completion + #define HTTP_STATUS_NO_CONTENT 204 // no info to return + #define HTTP_STATUS_RESET_CONTENT 205 // request completed, but clear form + #define HTTP_STATUS_PARTIAL_CONTENT 206 // partial GET fulfilled + #define HTTP_STATUS_WEBDAV_MULTI_STATUS 207 // WebDAV Multi-Status + + #define HTTP_STATUS_AMBIGUOUS 300 // server couldn't decide what to return + #define HTTP_STATUS_MOVED 301 // object permanently moved + #define HTTP_STATUS_REDIRECT 302 // object temporarily moved + #define HTTP_STATUS_REDIRECT_METHOD 303 // redirection w/ new access method + #define HTTP_STATUS_NOT_MODIFIED 304 // if-modified-since was not modified + #define HTTP_STATUS_USE_PROXY 305 // redirection to proxy, location header specifies proxy to use + #define HTTP_STATUS_REDIRECT_KEEP_VERB 307 // HTTP/1.1: keep same verb + #define HTTP_STATUS_PERMANENT_REDIRECT 308 // Object permanently moved keep verb + + #define HTTP_STATUS_BAD_REQUEST 400 // invalid syntax + #define HTTP_STATUS_DENIED 401 // access denied + #define HTTP_STATUS_PAYMENT_REQ 402 // payment required + #define HTTP_STATUS_FORBIDDEN 403 // request forbidden + #define HTTP_STATUS_NOT_FOUND 404 // object not found + #define HTTP_STATUS_BAD_METHOD 405 // method is not allowed + #define HTTP_STATUS_NONE_ACCEPTABLE 406 // no response acceptable to client found + #define HTTP_STATUS_PROXY_AUTH_REQ 407 // proxy authentication required + #define HTTP_STATUS_REQUEST_TIMEOUT 408 // server timed out waiting for request + #define HTTP_STATUS_CONFLICT 409 // user should resubmit with more info + #define HTTP_STATUS_GONE 410 // the resource is no longer available + #define HTTP_STATUS_LENGTH_REQUIRED 411 // the server refused to accept request w/o a length + #define HTTP_STATUS_PRECOND_FAILED 412 // precondition given in request failed + #define HTTP_STATUS_REQUEST_TOO_LARGE 413 // request entity was too large + #define HTTP_STATUS_URI_TOO_LONG 414 // request URI too long + #define HTTP_STATUS_UNSUPPORTED_MEDIA 415 // unsupported media type + #define HTTP_STATUS_RETRY_WITH 449 // retry after doing the appropriate action. + + #define HTTP_STATUS_SERVER_ERROR 500 // internal server error + #define HTTP_STATUS_NOT_SUPPORTED 501 // required not supported + #define HTTP_STATUS_BAD_GATEWAY 502 // error response received from gateway + #define HTTP_STATUS_SERVICE_UNAVAIL 503 // temporarily overloaded + #define HTTP_STATUS_GATEWAY_TIMEOUT 504 // timed out waiting for gateway + #define HTTP_STATUS_VERSION_NOT_SUP 505 // HTTP version not supported + + #define HTTP_STATUS_FIRST HTTP_STATUS_CONTINUE + #define HTTP_STATUS_LAST HTTP_STATUS_VERSION_NOT_SUP + + #define WINHTTP_CALLBACK_STATUS_RESOLVING_NAME 0x00000001 + #define WINHTTP_CALLBACK_STATUS_NAME_RESOLVED 0x00000002 + #define WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER 0x00000004 + #define WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER 0x00000008 + #define WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 0x00000010 + #define WINHTTP_CALLBACK_STATUS_REQUEST_SENT 0x00000020 + #define WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE 0x00000040 + #define WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED 0x00000080 + #define WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION 0x00000100 + #define WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED 0x00000200 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CREATED 0x00000400 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING 0x00000800 + #define WINHTTP_CALLBACK_STATUS_DETECTING_PROXY 0x00001000 + #define WINHTTP_CALLBACK_STATUS_REDIRECT 0x00004000 + #define WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE 0x00008000 + #define WINHTTP_CALLBACK_STATUS_SECURE_FAILURE 0x00010000 + #define WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE 0x00020000 + #define WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE 0x00040000 + #define WINHTTP_CALLBACK_STATUS_READ_COMPLETE 0x00080000 + #define WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE 0x00100000 + #define WINHTTP_CALLBACK_STATUS_REQUEST_ERROR 0x00200000 + #define WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 0x00400000 + + #define WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE 0x01000000 + #define WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE 0x02000000 + #define WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE 0x04000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE 0x10000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE 0x20000000 + + #define WINHTTP_CALLBACK_FLAG_RESOLVE_NAME (WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) + #define WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER (WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) + #define WINHTTP_CALLBACK_FLAG_SEND_REQUEST (WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | WINHTTP_CALLBACK_STATUS_REQUEST_SENT) + #define WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE (WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) + #define WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION (WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) + #define WINHTTP_CALLBACK_FLAG_HANDLES (WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) + #define WINHTTP_CALLBACK_FLAG_DETECTING_PROXY WINHTTP_CALLBACK_STATUS_DETECTING_PROXY + #define WINHTTP_CALLBACK_FLAG_REDIRECT WINHTTP_CALLBACK_STATUS_REDIRECT + #define WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE + #define WINHTTP_CALLBACK_FLAG_SECURE_FAILURE WINHTTP_CALLBACK_STATUS_SECURE_FAILURE + #define WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE + #define WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_READ_COMPLETE WINHTTP_CALLBACK_STATUS_READ_COMPLETE + #define WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE + #define WINHTTP_CALLBACK_FLAG_REQUEST_ERROR WINHTTP_CALLBACK_STATUS_REQUEST_ERROR + + #define WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE + + #define WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS (WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_READ_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR \ + | WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE) + + #define WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 0xffffffff + #define WINHTTP_INVALID_STATUS_CALLBACK ((WINHTTP_STATUS_CALLBACK)(-1L)) + + typedef struct _WINHTTP_EXTENDED_HEADER + { + union + { + CHAR const *pwszName; + WCHAR const *pszName; + }; + union + { + WCHAR const *pwszValue; + CHAR const *pszValue; + }; + } WINHTTP_EXTENDED_HEADER, *PWINHTTP_EXTENDED_HEADER; + + typedef struct _WINHTTP_ASYNC_RESULT + { + DWORD *dwResult; // indicates which async API has encountered an error + DWORD dwError; // the error code if the API failed + } WINHTTP_ASYNC_RESULT, *LPWINHTTP_ASYNC_RESULT, *PWINHTTP_ASYNC_RESULT; + + typedef + VOID + (*WINHTTP_STATUS_CALLBACK)( + HINTERNET hInternet, + DWORD *dwContext, + DWORD dwInternetStatus, + VOID *lpvStatusInformation, + DWORD dwStatusInformationLength + ); extern "C" { - __declspec(dllimport) BOOL __stdcall InternetCrackUrlA (CHAR const *lpszUrl, DWORD dwUrlLength, DWORD dwFlags, URL_COMPONENTSA *lpUrlComponents); - __declspec(dllimport) HINTERNET __stdcall InternetOpenA (CHAR const *lpszAgent, DWORD dwAccessType, CHAR const *lpszProxy, CHAR const *lpszProxyBypass, DWORD dwFlags); - __declspec(dllimport) HINTERNET __stdcall InternetConnectA (HINTERNET hInternet, CHAR const *lpszServerName, INTERNET_PORT nServerPort, CHAR const *lpszUserName, CHAR const *lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext); - __declspec(dllimport) BOOL __stdcall InternetSetOptionA (HINTERNET hInternet, DWORD dwOption, VOID *lpBuffer, DWORD dwBufferLength); - __declspec(dllimport) BOOL __stdcall InternetReadFile (HINTERNET hFile, VOID *lpBuffer, DWORD dwNumberOfBytesToRead, DWORD *lpdwNumberOfBytesRead); - __declspec(dllimport) BOOL __stdcall InternetCloseHandle (HINTERNET hInternet); - __declspec(dllimport) HINTERNET __stdcall HttpOpenRequestA (HINTERNET hConnect, CHAR const *lpszVerb, CHAR const *lpszObjectName, CHAR const *lpszVersion, CHAR const *lpszReferrer, CHAR const *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext); - __declspec(dllimport) BOOL __stdcall HttpSendRequestA (HINTERNET hRequest, CHAR const *lpszHeaders, DWORD dwHeadersLength, VOID *lpOptional, DWORD dwOptionalLength); - __declspec(dllimport) BOOL __stdcall HttpAddRequestHeadersA(HINTERNET hRequest, CHAR const *lpszHeaders, DWORD dwHeadersLength, DWORD dwModifiers); - __declspec(dllimport) BOOL __stdcall HttpQueryInfoA (HINTERNET hRequest, DWORD dwInfoLevel, VOID *lpBuffer, DWORD *lpdwBufferLength, DWORD *lpdwIndex); + __declspec(dllimport) HINTERNET __stdcall WinHttpOpen(WCHAR const *pszAgentW, DWORD dwAccessType, WCHAR const *pszProxyW, WCHAR const *pszProxyBypassW, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpCloseHandle(HINTERNET hInternet); + __declspec(dllimport) HINTERNET __stdcall WinHttpConnect(HINTERNET hSession, WCHAR const *pswzServerName, INTERNET_PORT nServerPort, DWORD dwReserved); + __declspec(dllimport) BOOL __stdcall WinHttpReadData(HINTERNET hRequest, VOID *lpBuffer, DWORD dwNumberOfBytesToRead, DWORD *lpdwNumberOfBytesRead); + __declspec(dllimport) HINTERNET __stdcall WinHttpOpenRequest(HINTERNET hConnect, WCHAR const *pwszVerb, WCHAR const *pwszObjectName, WCHAR const *pwszVersion, WCHAR const *pwszReferrer, WCHAR const *ppwszAcceptTypes, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpSendRequest(HINTERNET hRequest, WCHAR const *lpszHeaders, DWORD dwHeadersLength, VOID *lpOptional, DWORD dwOptionalLength, DWORD dwTotalLength, DWORD_PTR dwContext); + __declspec(dllimport) DWORD __stdcall WinHttpAddRequestHeadersEx(HINTERNET hRequest, DWORD dwModifiers, ULONGLONG ullFlags, ULONGLONG ullExtra, DWORD cHeaders, WINHTTP_EXTENDED_HEADER *pHeaders); + __declspec(dllimport) BOOL __stdcall WinHttpSetCredentials(HINTERNET hRequest, // HINTERNET handle returned by WinHttpOpenRequest. + DWORD AuthTargets, // Only WINHTTP_AUTH_TARGET_SERVER and WINHTTP_AUTH_TARGET_PROXY are supported in this version and they are mutually exclusive + DWORD AuthScheme, // must be one of the supported Auth Schemes returned from WinHttpQueryAuthSchemes() + WCHAR * pwszUserName, // 1) NULL if default creds is to be used, in which case pszPassword will be ignored + WCHAR * pwszPassword, // 1) "" == Blank Password; 2)Parameter ignored if pszUserName is NULL; 3) Invalid to pass in NULL if pszUserName is not NULL + VOID * pAuthParams); + __declspec(dllimport) BOOL __stdcall WinHttpQueryHeaders(HINTERNET hRequest, DWORD dwInfoLevel, WCHAR const *pwszName, VOID *lpBuffer, DWORD *lpdwBufferLength, DWORD *lpdwIndex); + __declspec(dllimport) BOOL __stdcall WinHttpReceiveResponse(HINTERNET hRequest, VOID *lpReserved); + __declspec(dllimport) WINHTTP_STATUS_CALLBACK __stdcall WinHttpSetStatusCallback(HINTERNET hInternet, WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags, DWORD_PTR dwReserved); } - // NOTE: um/DbgHelp.h ========================================================================== + // NOTE: um/DbgHelp.h ////////////////////////////////////////////////////////////////////////// #define SYMOPT_CASE_INSENSITIVE 0x00000001 #define SYMOPT_UNDNAME 0x00000002 #define SYMOPT_DEFERRED_LOADS 0x00000004 @@ -680,15 +957,16 @@ __declspec(dllimport) VOID * __stdcall SymFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase); __declspec(dllimport) BOOL __stdcall SymGetLineFromAddrW64 (HANDLE hProcess, DWORD64 dwAddr, DWORD *pdwDisplacement, IMAGEHLP_LINEW64 *Line); __declspec(dllimport) DWORD64 __stdcall SymGetModuleBase64 (HANDLE hProcess, DWORD64 qwAddr); + __declspec(dllimport) BOOL __stdcall SymRefreshModuleList (HANDLE hProcess); }; - // NOTE: um/errhandlingapi.h =================================================================== + // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) DWORD __stdcall GetLastError(VOID); } - // NOTE: um/libloaderapi.h ===================================================================== + // NOTE: um/libloaderapi.h ///////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) HMODULE __stdcall LoadLibraryA (const CHAR *lpLibFileName); @@ -698,22 +976,30 @@ __declspec(dllimport) DWORD __stdcall GetModuleFileNameW(HMODULE hModule, WCHAR *lpFilename, DWORD nSize); } - // NOTE: um/synchapi.h ========================================================================= + // NOTE: um/synchapi.h ///////////////////////////////////////////////////////////////////////// extern "C" { - __declspec(dllimport) DWORD __stdcall WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); - __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); - __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); + __declspec(dllimport) VOID __stdcall InitializeCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall EnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall LeaveCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionEx (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount, DWORD Flags); + __declspec(dllimport) DWORD __stdcall SetCriticalSectionSpinCount (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall TryEnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall DeleteCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) DWORD __stdcall WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds); + __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); + __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); } - // NOTE: um/profileapi.h ======================================================================= + // NOTE: um/profileapi.h /////////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) BOOL __stdcall QueryPerformanceCounter (LARGE_INTEGER* lpPerformanceCount); __declspec(dllimport) BOOL __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); } - // NOTE: um/processthreadsapi.h ================================================================ + // NOTE: um/processthreadsapi.h //////////////////////////////////////////////////////////////// typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; @@ -757,7 +1043,7 @@ } - // NOTE: um/memoryapi.h ======================================================================== + // NOTE: um/memoryapi.h //////////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) VOID * __stdcall VirtualAlloc (VOID *lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); @@ -765,7 +1051,7 @@ __declspec(dllimport) BOOL __stdcall VirtualFree (VOID *lpAddress, SIZE_T dwSize, DWORD dwFreeType); } - // NOTE: shared/bcrypt.h ======================================================================= + // NOTE: shared/bcrypt.h /////////////////////////////////////////////////////////////////////// typedef VOID *BCRYPT_ALG_HANDLE; typedef LONG NTSTATUS; @@ -775,12 +1061,17 @@ __declspec(dllimport) NTSTATUS __stdcall BCryptGenRandom (BCRYPT_ALG_HANDLE hAlgorithm, UCHAR *pbBuffer, ULONG cbBuffer, ULONG dwFlags); } - // NOTE: um/shellapi.h ========================================================================= + // NOTE: um/shellapi.h ///////////////////////////////////////////////////////////////////////// extern "C" { __declspec(dllimport) HINSTANCE __stdcall ShellExecuteA(HWND hwnd, CHAR const *lpOperation, CHAR const *lpFile, CHAR const *lpParameters, CHAR const *lpDirectory, INT nShowCmd); } + // NOTE: um/debugapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall IsDebuggerPresent(); + } + DQN_MSVC_WARNING_POP #endif // !defined(_INC_WINDOWS) -#endif /// defined(DQN_OS_WIN32)