1094 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1094 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| //   $$$$$$$\   $$$$$$\   $$$$$$\   $$$$$$\
 | |
| //   $$  __$$\ $$  __$$\ $$  __$$\ $$  __$$\
 | |
| //   $$ |  $$ |$$ /  $$ |$$ /  \__|$$ /  \__|
 | |
| //   $$ |  $$ |$$ |  $$ |$$ |      \$$$$$$\
 | |
| //   $$ |  $$ |$$ |  $$ |$$ |       \____$$\
 | |
| //   $$ |  $$ |$$ |  $$ |$$ |  $$\ $$\   $$ |
 | |
| //   $$$$$$$  | $$$$$$  |\$$$$$$  |\$$$$$$  |
 | |
| //   \_______/  \______/  \______/  \______/
 | |
| //
 | |
| //   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<int> map = Dqn_DSMap_Init<int>(&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<int> 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<int> *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<N>
 | |
| 
 | |
|     // 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 '"<key>": <value>' (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.
 | |
|     //
 | |
|     // "<name>": <value>
 | |
|     //
 | |
|     // And the non-named version emit just the 'value' portion
 | |
| 
 | |
|     // NOTE: Dqn_List_Iterate //////////////////////////////////////////////////////////////////////
 | |
|     {
 | |
|         Dqn_Scratch   scratch = Dqn_Scratch_Get(nullptr);
 | |
|         Dqn_List<int> list    = Dqn_List_Init<int>(scratch.arena, /*chunk_size*/ 128);
 | |
|         for (Dqn_ListIterator<int> 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] %llu 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(%llu): %.*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<Dqn_StackTraceFrame> 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<Dqn_Str8> 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<Dqn_Str8> 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<int> array = Dqn_VArray_Init<int>(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 defined(DQN_PLATFORM_WIN32)
 | |
|     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));
 | |
|         }
 | |
|     }
 | |
|     #endif
 | |
| }
 | |
| DQN_MSVC_WARNING_POP
 |