Update quicksort/bsearch to use templates better

This commit is contained in:
Doyle T 2018-07-15 20:09:55 +10:00
parent 9d12c532d1
commit 1c5fdc6ce1
2 changed files with 137 additions and 333 deletions

View File

@ -1882,7 +1882,7 @@ FILE_SCOPE void JobQueueDebugCallbackIncrementCounter(DqnJobQueue *const queue,
(void)userData; (void)userData;
DQN_ASSERT(queue->size == QUEUE_SIZE); DQN_ASSERT(queue->size == QUEUE_SIZE);
{ {
DqnLockGuard guard = globalJobQueueLock.LockGuard(); auto guard = globalJobQueueLock.Guard();
globalDebugCounter++; globalDebugCounter++;
// u32 number = globalDebugCounter; // u32 number = globalDebugCounter;
@ -1915,7 +1915,7 @@ FILE_SCOPE void DqnJobQueue_Test()
DQN_ASSERT(DqnJobQueue_Init(&jobQueue, jobList, QUEUE_SIZE, totalThreads)); DQN_ASSERT(DqnJobQueue_Init(&jobQueue, jobList, QUEUE_SIZE, totalThreads));
const u32 WORK_ENTRIES = 2048; const u32 WORK_ENTRIES = 2048;
DQN_ASSERT(DqnLock_Init(&globalJobQueueLock)); DQN_ASSERT(globalJobQueueLock.Init());
for (u32 i = 0; i < WORK_ENTRIES; i++) for (u32 i = 0; i < WORK_ENTRIES; i++)
{ {
DqnJob job = {}; DqnJob job = {};
@ -1928,7 +1928,7 @@ FILE_SCOPE void DqnJobQueue_Test()
DqnJobQueue_BlockAndCompleteAllJobs(&jobQueue); DqnJobQueue_BlockAndCompleteAllJobs(&jobQueue);
DQN_ASSERT(globalDebugCounter == WORK_ENTRIES); DQN_ASSERT(globalDebugCounter == WORK_ENTRIES);
DqnLock_Delete(&globalJobQueueLock); globalJobQueueLock.Delete();
Log("Final incremented value: %d\n", globalDebugCounter); Log("Final incremented value: %d\n", globalDebugCounter);
} }
@ -1977,7 +1977,7 @@ void DqnQuickSort_Test()
// Time Dqn_QuickSort // Time Dqn_QuickSort
{ {
f64 start = DqnTimer_NowInS(); f64 start = DqnTimer_NowInS();
Dqn_QuickSort(dqnCPPArray, numInts); DqnQuickSort(dqnCPPArray, numInts);
f64 duration = DqnTimer_NowInS() - start; f64 duration = DqnTimer_NowInS() - start;
dqnCPPTimings[timingsIndex] = duration; dqnCPPTimings[timingsIndex] = duration;
@ -2102,33 +2102,22 @@ void DqnHashTable_Test()
Log(Status::Ok, "HashTable"); Log(Status::Ok, "HashTable");
} }
void Dqn_BSearch_Test() void DqnBSearch_Test()
{ {
LOG_HEADER(); LOG_HEADER();
if (1) if (1)
{ {
auto IsLessThan = [](const u32 &a, const u32 &b) -> bool {
bool result = a < b;
return result;
};
auto Equals = [](const u32 &a, const u32 &b) -> bool {
bool result = (a == b);
return result;
};
u32 array[] = {1, 2, 3}; u32 array[] = {1, 2, 3};
i64 result = Dqn_BSearch<u32>(array, DQN_ARRAY_COUNT(array), 1, Equals, IsLessThan); i64 result = DqnBSearch<u32>(array, DQN_ARRAY_COUNT(array), 1);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch<u32>(array, DQN_ARRAY_COUNT(array), 2, Equals, IsLessThan); result = DqnBSearch<u32>(array, DQN_ARRAY_COUNT(array), 2);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch<u32>(array, DQN_ARRAY_COUNT(array), 3, Equals, IsLessThan); result = DqnBSearch<u32>(array, DQN_ARRAY_COUNT(array), 3);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch<u32>(array, DQN_ARRAY_COUNT(array), 4, Equals, IsLessThan); result = DqnBSearch<u32>(array, DQN_ARRAY_COUNT(array), 4);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
Log(Status::Ok, "With odd sized array and custom compare"); Log(Status::Ok, "With odd sized array and custom compare");
} }
@ -2136,19 +2125,19 @@ void Dqn_BSearch_Test()
if (1) if (1)
{ {
i64 array[] = {1, 2, 3, 4}; i64 array[] = {1, 2, 3, 4};
i64 result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 1); i64 result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 1);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 2); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 2);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 3); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 3);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 4); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 4);
DQN_ASSERT(result == 3); DQN_ASSERT(result == 3);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 5); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 5);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
Log(Status::Ok, "With even sized array"); Log(Status::Ok, "With even sized array");
} }
@ -2156,22 +2145,22 @@ void Dqn_BSearch_Test()
if (1) if (1)
{ {
i64 array[] = {1, 2, 3}; i64 array[] = {1, 2, 3};
i64 result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BSearchBound_Lower); i64 result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 0, DqnBSearchType::MinusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 1, DqnBSearchType::MinusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 2, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 3, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 4, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 5, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
Log(Status::Ok, "Lower bound with odd sized array"); Log(Status::Ok, "Lower bound with odd sized array");
} }
@ -2180,25 +2169,25 @@ void Dqn_BSearch_Test()
{ {
i64 array[] = {1, 2, 3, 4}; i64 array[] = {1, 2, 3, 4};
i64 result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BSearchBound_Lower); i64 result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 0, DqnBSearchType::MinusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 1, DqnBSearchType::MinusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 2, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 3, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 4, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 5, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 3); DQN_ASSERT(result == 3);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 6, Dqn_BSearchBound_Lower); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 6, DqnBSearchType::MinusOne);
DQN_ASSERT(result == 3); DQN_ASSERT(result == 3);
Log(Status::Ok, "Lower bound with even sized array"); Log(Status::Ok, "Lower bound with even sized array");
} }
@ -2206,22 +2195,22 @@ void Dqn_BSearch_Test()
if (1) if (1)
{ {
i64 array[] = {1, 2, 3}; i64 array[] = {1, 2, 3};
i64 result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BSearchBound_Higher); i64 result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 0, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 1, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 2, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 3, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 4, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 5, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
Log(Status::Ok, "Higher bound with odd sized array"); Log(Status::Ok, "Higher bound with odd sized array");
} }
@ -2230,25 +2219,25 @@ void Dqn_BSearch_Test()
{ {
i64 array[] = {1, 2, 3, 4}; i64 array[] = {1, 2, 3, 4};
i64 result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BSearchBound_Higher); i64 result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 0, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 0); DQN_ASSERT(result == 0);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 1, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 1); DQN_ASSERT(result == 1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 2, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 2); DQN_ASSERT(result == 2);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 3, DqnBSearchType::PlusOne);
DQN_ASSERT(result == 3); DQN_ASSERT(result == 3);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 4, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 5, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
result = Dqn_BSearch(array, DQN_ARRAY_COUNT(array), 6, Dqn_BSearchBound_Higher); result = DqnBSearch(array, DQN_ARRAY_COUNT(array), 6, DqnBSearchType::PlusOne);
DQN_ASSERT(result == -1); DQN_ASSERT(result == -1);
Log(Status::Ok, "Higher bound with even sized array"); Log(Status::Ok, "Higher bound with even sized array");
} }
@ -2896,7 +2885,7 @@ int main(void)
DqnArray_Test(); DqnArray_Test();
DqnQuickSort_Test(); DqnQuickSort_Test();
DqnHashTable_Test(); DqnHashTable_Test();
Dqn_BSearch_Test(); DqnBSearch_Test();
DqnMemSet_Test(); DqnMemSet_Test();
DqnFixedString_Test(); DqnFixedString_Test();
DqnJson_Test(); DqnJson_Test();

369
dqn.h
View File

@ -898,16 +898,25 @@ DQN_FILE_SCOPE inline u32 Dqn_BitSet (u32 bits, u32 flag);
DQN_FILE_SCOPE inline u32 Dqn_BitUnset (u32 bits, u32 flag); DQN_FILE_SCOPE inline u32 Dqn_BitUnset (u32 bits, u32 flag);
DQN_FILE_SCOPE inline u32 Dqn_BitToggle(u32 bits, u32 flag); DQN_FILE_SCOPE inline u32 Dqn_BitToggle(u32 bits, u32 flag);
template <typename T> template <typename T> using DqnQuickSort_LessThanProc = bool (*) (T const &a, T const &b, void *userContext);
using Dqn_QuickSortLessThanCallback = bool (*)(T const *, T const *, void *); #define DQN_QUICK_SORT_LESS_THAN_PROC(name) template <typename T> inline bool name(T const &a, T const &b, void *userContext)
DQN_QUICK_SORT_LESS_THAN_PROC(DqnQuickSort_DefaultLessThan)
DQN_FILE_SCOPE inline bool Dqn_QuickSortDqnStringSorter(struct DqnString const *a, struct DqnString const *b, void *);
template <typename T>
DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size,
Dqn_QuickSortLessThanCallback<T> IsLessThan, void *userContext = nullptr)
{ {
if (!array || size <= 1 || !IsLessThan) return; (void)userContext;
bool result = a < b;
return result;
}
DQN_QUICK_SORT_LESS_THAN_PROC(DqnQuickSort_StringLessThan)
{
bool result = DqnString::Cmp(a, b);
return result;
}
template <typename T, DqnQuickSort_LessThanProc<T> IsLessThan = DqnQuickSort_DefaultLessThan<T>>
DQN_FILE_SCOPE void DqnQuickSort(T *array, isize size, void *userContext = nullptr)
{
if (!array || size <= 1) return;
#if 1 #if 1
// Insertion Sort, under 24->32 is an optimal amount // Insertion Sort, under 24->32 is an optimal amount
@ -919,7 +928,7 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size,
{ {
for (i32 checkIndex = 0; checkIndex < itemToInsertIndex; checkIndex++) for (i32 checkIndex = 0; checkIndex < itemToInsertIndex; checkIndex++)
{ {
if (!IsLessThan(&array[checkIndex], &array[itemToInsertIndex], userContext)) if (!IsLessThan(array[checkIndex], array[itemToInsertIndex], userContext))
{ {
T itemToInsert = array[itemToInsertIndex]; T itemToInsert = array[itemToInsertIndex];
for (i32 i = itemToInsertIndex; i > checkIndex; i--) for (i32 i = itemToInsertIndex; i > checkIndex; i--)
@ -948,7 +957,7 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size,
pivotIndex = lastIndex; pivotIndex = lastIndex;
// 4^, 8, 7, 5, 2, 3, 6 // 4^, 8, 7, 5, 2, 3, 6
if (IsLessThan(&array[startIndex], &array[pivotIndex], userContext)) partitionIndex++; if (IsLessThan(array[startIndex], array[pivotIndex], userContext)) partitionIndex++;
startIndex++; startIndex++;
// 4, |8, 7, 5^, 2, 3, 6* // 4, |8, 7, 5^, 2, 3, 6*
@ -957,7 +966,7 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size,
// 4, 5, 2, 3, |7, 8, ^6* // 4, 5, 2, 3, |7, 8, ^6*
for (auto checkIndex = startIndex; checkIndex < lastIndex; checkIndex++) for (auto checkIndex = startIndex; checkIndex < lastIndex; checkIndex++)
{ {
if (IsLessThan(&array[checkIndex], &array[pivotIndex], userContext)) if (IsLessThan(array[checkIndex], array[pivotIndex], userContext))
{ {
DQN_SWAP(T, array[partitionIndex], array[checkIndex]); DQN_SWAP(T, array[partitionIndex], array[checkIndex]);
partitionIndex++; partitionIndex++;
@ -967,128 +976,61 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size,
// Move pivot to right of partition // Move pivot to right of partition
// 4, 5, 2, 3, |6, 8, ^7* // 4, 5, 2, 3, |6, 8, ^7*
DQN_SWAP(T, array[partitionIndex], array[pivotIndex]); DQN_SWAP(T, array[partitionIndex], array[pivotIndex]);
Dqn_QuickSort(array, partitionIndex, IsLessThan, userContext); DqnQuickSort<T, IsLessThan>(array, partitionIndex, userContext);
// Skip the value at partion index since that is guaranteed to be sorted. // Skip the value at partion index since that is guaranteed to be sorted.
// 4, 5, 2, 3, (x), 8, 7 // 4, 5, 2, 3, (x), 8, 7
i32 oneAfterPartitionIndex = partitionIndex + 1; i32 oneAfterPartitionIndex = partitionIndex + 1;
Dqn_QuickSort(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex), IsLessThan, userContext); DqnQuickSort<T, IsLessThan>(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex), userContext);
} }
template <typename T> template <typename T> using DqnBSearch_LessThanProc = bool (*)(const T&, const T&);
DQN_FILE_SCOPE void Dqn_QuickSort(T *array, i64 size) template <typename T> using DqnBSearch_EqualsProc = bool (*)(const T&, const T&);
#define DQN_BSEARCH_LESS_THAN_PROC(name) template <typename T> inline bool name(T const &a, T const &b)
#define DQN_BSEARCH_EQUALS_PROC(name) template <typename T> inline bool name(T const &a, T const &b)
DQN_BSEARCH_LESS_THAN_PROC(DqnBSearch_DefaultLessThan) { return a < b; }
DQN_BSEARCH_EQUALS_PROC (DqnBSearch_DefaultEquals) { return a == b; }
enum struct DqnBSearchType
{ {
if (!array || size <= 1) return; Match, // Return the index of the first item that matches the find value
MinusOne, // Return the index of the first item lower than the find value
PlusOne, // Return the index of the first item higher than the find value
#if 1 MatchOrMinusOne, // Return the index of the matching item if not found the first item lower
// Insertion Sort, under 24->32 is an optimal amount MatchOrPlusOne, // Return the index of the matching item if not found the first item higher
const i32 QUICK_SORT_THRESHOLD = 24;
if (size < QUICK_SORT_THRESHOLD)
{
i32 itemToInsertIndex = 1;
while (itemToInsertIndex < size)
{
for (i32 checkIndex = 0; checkIndex < itemToInsertIndex; checkIndex++)
{
if (!(array[checkIndex] < array[itemToInsertIndex]))
{
T itemToInsert = array[itemToInsertIndex];
for (i32 i = itemToInsertIndex; i > checkIndex; i--)
array[i] = array[i - 1];
array[checkIndex] = itemToInsert;
break;
}
}
itemToInsertIndex++;
}
return;
}
#endif
auto state = DqnRndPCG();
auto lastIndex = size - 1;
auto pivotIndex = (i64)state.Range(0, (i32)lastIndex); // TODO(doyle): RNG 64bit
auto partitionIndex = 0;
auto startIndex = 0;
// Swap pivot with last index, so pivot is always at the end of the array.
// This makes logic much simpler.
DQN_SWAP(T, array[lastIndex], array[pivotIndex]);
pivotIndex = lastIndex;
// 4^, 8, 7, 5, 2, 3, 6
if (array[startIndex] < array[pivotIndex]) partitionIndex++;
startIndex++;
// 4, |8, 7, 5^, 2, 3, 6*
// 4, 5, |7, 8, 2^, 3, 6*
// 4, 5, 2, |8, 7, ^3, 6*
// 4, 5, 2, 3, |7, 8, ^6*
for (auto checkIndex = startIndex; checkIndex < lastIndex; checkIndex++)
{
if (array[checkIndex] < array[pivotIndex])
{
DQN_SWAP(T, array[partitionIndex], array[checkIndex]);
partitionIndex++;
}
}
// Move pivot to right of partition
// 4, 5, 2, 3, |6, 8, ^7*
DQN_SWAP(T, array[partitionIndex], array[pivotIndex]);
Dqn_QuickSort(array, partitionIndex);
// Skip the value at partion index since that is guaranteed to be sorted.
// 4, 5, 2, 3, (x), 8, 7
i32 oneAfterPartitionIndex = partitionIndex + 1;
Dqn_QuickSort(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex));
}
template <typename T>
using Dqn_BSearchLessThanCallback = bool (*)(const T&, const T&);
template <typename T>
using Dqn_BSearchEqualsCallback = bool (*)(const T&, const T&);
enum Dqn_BSearchBound
{
Dqn_BSearchBound_Normal, // Return the index of the first item that matches the find value
Dqn_BSearchBound_Lower, // Return the index of the first item lower than the find value
Dqn_BSearchBound_Higher, // Return the index of the first item higher than the find value
Dqn_BSearchBound_NormalLower, // Return the index of the matching item if not found the first item lower
Dqn_BSearchBound_NormalHigher, // Return the index of the matching item if not found the first item higher
}; };
// bound: The behaviour of the binary search, // type: The matching behaviour of the binary search,
// return: -1 if element not found, otherwise index of the element. // return: -1 if element not found, otherwise index of the element.
// For higher and lower bounds return -1 if there is no element higher/lower than the // For higher and lower bounds return -1 if there is no element higher/lower than the
// find value (i.e. -1 if the 0th element is the find val for lower bound). // find value (i.e. -1 if the 0th element is the find val for lower bound).
template <typename T> template <typename T,
DQN_FILE_SCOPE i64 Dqn_BSearch(T *array, i64 size, T const &find, DqnBSearch_LessThanProc<T> IsLessThan = DqnBSearch_DefaultLessThan<T>,
Dqn_BSearchEqualsCallback<T> Equals, DqnBSearch_EqualsProc<T> Equals = DqnBSearch_DefaultEquals<T>>
Dqn_BSearchLessThanCallback<T> IsLessThan, DQN_FILE_SCOPE i64
Dqn_BSearchBound bound = Dqn_BSearchBound_Normal) DqnBSearch(T const *array, isize size, T const &find, DqnBSearchType type = DqnBSearchType::Match)
{ {
if (size == 0 || !array) return -1; if (size == 0 || !array)
{
return -1;
}
i64 start = 0; isize start = 0;
i64 end = size - 1; isize end = size - 1;
i64 mid = (i64)((start + end) * 0.5f); isize mid = static_cast<isize>((start + end) * 0.5f);
while (start <= end) while (start <= end)
{ {
if (Equals(array[mid], find)) if (Equals(array[mid], find))
{ {
if (bound == Dqn_BSearchBound_Normal || if (type == DqnBSearchType::Match ||
bound == Dqn_BSearchBound_NormalLower || type == DqnBSearchType::MatchOrMinusOne ||
bound == Dqn_BSearchBound_NormalHigher) type == DqnBSearchType::MatchOrPlusOne)
{ {
return mid; return mid;
} }
else if (bound == Dqn_BSearchBound_Lower) else if (type == DqnBSearchType::MinusOne)
{ {
// NOTE: We can always -1 because at worst case, 0 index will go to -1 which is // NOTE: We can always -1 because at worst case, 0 index will go to -1 which is
// correct behaviour. // correct behaviour.
@ -1096,32 +1038,29 @@ DQN_FILE_SCOPE i64 Dqn_BSearch(T *array, i64 size, T const &find,
} }
else else
{ {
if ((mid + 1) >= size) return -1; return ((mid + 1) >= size) ? -1 : mid + 1;
return mid + 1;
} }
} }
else if (IsLessThan(array[mid], find)) start = mid + 1; else if (IsLessThan(array[mid], find)) start = mid + 1;
else end = mid - 1; else end = mid - 1;
mid = (i64)((start + end) * 0.5f); mid = static_cast<isize>((start + end) * 0.5f);
} }
if (bound == Dqn_BSearchBound_Normal) if (type == DqnBSearchType::Match)
{ {
return -1; return -1;
} }
if (bound == Dqn_BSearchBound_Lower || bound == Dqn_BSearchBound_NormalLower) if (type == DqnBSearchType::MinusOne || type == DqnBSearchType::MatchOrMinusOne)
{ {
if (IsLessThan(find, array[mid])) return -1; return (IsLessThan(find, array[mid])) ? -1 : mid;
return mid;
} }
else else
{ {
if (IsLessThan(array[mid], find)) return -1; return (IsLessThan(find, array[mid])) ? mid : -1;
return mid;
} }
} }
DQN_FILE_SCOPE i64 Dqn_BSearch(i64 *array, i64 size, i64 find, Dqn_BSearchBound bound = Dqn_BSearchBound_Normal); DQN_FILE_SCOPE inline i64 DqnBSearch(i64 const *array, i64 size, i64 find, DqnBSearchType type = DqnBSearchType::Match) { return DqnBSearch<i64>(array, size, find, type); }
// #DqnMem API // #DqnMem API
// ================================================================================================= // =================================================================================================
@ -1325,7 +1264,7 @@ struct DqnArray
~DqnArray () { if (this->data && this->memAPI) this->memAPI->Free(data); } ~DqnArray () { if (this->data && this->memAPI) this->memAPI->Free(data); }
void UseMemory (T *data_, isize max_, isize count_ = 0) { this->memAPI = nullptr; this->data = data_; this->max = max_; this->count = count_; } void UseMemory (T *data_, isize max_, isize count_ = 0) { this->memAPI = nullptr; this->data = data_; this->max = max_; this->count = count_; }
void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (data) { count = 0; if (clear == Dqn::ZeroClear::Yes) DqnMem_Clear(data, 0, sizeof(T) * max); } } void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (!data) return; count = 0; if (clear == Dqn::ZeroClear::Yes) DqnMem_Clear(data, 0, sizeof(T) * max); }
void Free () { if (data) { memAPI->Free(data); } *this = {}; } void Free () { if (data) { memAPI->Free(data); } *this = {}; }
T *Front () { DQN_ASSERT(count > 0); return data + 0; } T *Front () { DQN_ASSERT(count > 0); return data + 0; }
T *Back () { DQN_ASSERT(count > 0); return data + (count - 1); } T *Back () { DQN_ASSERT(count > 0); return data + (count - 1); }
@ -1577,24 +1516,6 @@ struct DqnMemStack
void TempRegionKeepChanges(TempRegion region); void TempRegionKeepChanges(TempRegion region);
}; };
inline bool DqnMemStack_Init (DqnMemStack *me, isize size, Dqn::ZeroClear clear, u32 flags_ = 0, DqnMemAPI *api = DQN_DEFAULT_HEAP_ALLOCATOR) { return me->Init(size, clear, flags_, api); }
inline void *DqnMemStack_Push (DqnMemStack *me, isize size, u8 alignment = 4) { return me->Push(size, alignment); }
inline void *DqnMemStack_PushOnTail (DqnMemStack *me, isize size, u8 alignment = 4) { return me->PushOnTail(size, alignment); }
inline void DqnMemStack_Pop (DqnMemStack *me, void *ptr, Dqn::ZeroClear clear = Dqn::ZeroClear::No) { me->Pop(ptr, clear); }
inline void DqnMemStack_PopOnTail (DqnMemStack *me, void *ptr, Dqn::ZeroClear clear = Dqn::ZeroClear::No) { me->PopOnTail(ptr, clear); }
inline void DqnMemStack_Free (DqnMemStack *me) { me->Free(); }
inline bool DqnMemStack_FreeMemBlock (DqnMemStack *me, DqnMemStack::Block *memBlock) { return me->FreeMemBlock(memBlock); }
inline bool DqnMemStack_FreeLastBlock (DqnMemStack *me) { return me->FreeLastBlock(); }
inline void DqnMemStack_Reset (DqnMemStack *me) { me->Reset(); }
inline void DqnMemStack_ResetTail (DqnMemStack *me) { me->ResetTail(); }
inline void DqnMemStack_ClearCurrBlock (DqnMemStack *me, Dqn::ZeroClear clear) { me->ClearCurrBlock(clear); }
inline DqnMemStack::Info DqnMemStack_GetInfo (DqnMemStack *me) { return me->GetInfo(); }
inline DqnMemStack::TempRegion DqnMemStack_TempRegionBegin (DqnMemStack *me) { return me->TempRegionBegin(); }
inline void DqnMemStack_TempRegionEnd (DqnMemStack *me, DqnMemStack::TempRegion region) { me->TempRegionEnd(region); }
inline DqnMemStack::TempRegionGuard_ DqnMemStack_TempRegionGuard (DqnMemStack *me) { return me->TempRegionGuard(); }
inline void DqnMemStack_TempRegionKeepChanges(DqnMemStack *me, DqnMemStack::TempRegion region) { me->TempRegionKeepChanges(region); }
// Implementation taken from Milton, developed by Serge at // Implementation taken from Milton, developed by Serge at
// https://github.com/serge-rgb/milton#license // https://github.com/serge-rgb/milton#license
#if 0 #if 0
@ -1836,14 +1757,14 @@ void DqnArray<T>::EraseStable(isize *indexList, isize numIndexes)
if (numIndexes == 0 || !indexList) return; if (numIndexes == 0 || !indexList) return;
// NOTE: Sort the index list and ensure we only remove indexes up to the size of our array // NOTE: Sort the index list and ensure we only remove indexes up to the size of our array
Dqn_QuickSort<isize>(indexList, numIndexes); DqnQuickSort<isize>(indexList, numIndexes);
auto arrayHighestIndex = this->count - 1; auto arrayHighestIndex = this->count - 1;
auto realCount = numIndexes; auto realCount = numIndexes;
if (indexList[numIndexes - 1] > arrayHighestIndex) if (indexList[numIndexes - 1] > arrayHighestIndex)
{ {
auto realNumIndexes = auto realNumIndexes =
Dqn_BSearch(indexList, numIndexes, arrayHighestIndex, Dqn_BSearchBound_Lower); DqnBSearch(indexList, numIndexes, arrayHighestIndex, DqnBSearchType::MinusOne);
// NOTE: If -1, then there's no index in the indexlist that is within the range of our array // NOTE: If -1, then there's no index in the indexlist that is within the range of our array
// i.e. no index we can remove without out of array bounds access // i.e. no index we can remove without out of array bounds access
if (realNumIndexes == -1) if (realNumIndexes == -1)
@ -2555,8 +2476,8 @@ typename DqnHashTable<T>::Entry *DqnHashTable<T>::Make(char const *const key, i3
// If entry for hashIndex not used yet, mark it down as a used slot. // If entry for hashIndex not used yet, mark it down as a used slot.
if (!this->entries[hashIndex]) if (!this->entries[hashIndex])
{ {
i64 index = Dqn_BSearch(this->usedEntries, this->usedEntriesIndex, hashIndex, i64 index = DqnBSearch(this->usedEntries, this->usedEntriesIndex, hashIndex,
Dqn_BSearchBound_Lower); DqnBSearchType::MinusOne);
i64 indexToEndAt = index; i64 indexToEndAt = index;
if (index == -1) indexToEndAt = 0; if (index == -1) indexToEndAt = 0;
@ -2615,8 +2536,8 @@ void DqnHashTable<T>::Remove(char const *const key, i32 keyLen)
if (entryToFree == entry) if (entryToFree == entry)
{ {
// Unique entry, so remove this index from the used list as well. // Unique entry, so remove this index from the used list as well.
i64 indexToRemove = Dqn_BSearch(this->usedEntries, this->usedEntriesIndex, i64 indexToRemove = DqnBSearch(this->usedEntries, this->usedEntriesIndex,
hashIndex, Dqn_BSearchBound_Lower); hashIndex, DqnBSearchType::MinusOne);
for (i64 i = indexToRemove; i < this->usedEntriesIndex - 1; i++) for (i64 i = indexToRemove; i < this->usedEntriesIndex - 1; i++)
this->usedEntries[i] = this->usedEntries[i + 1]; this->usedEntries[i] = this->usedEntries[i + 1];
@ -2899,13 +2820,13 @@ struct DqnVArray
DqnVArray () = default; DqnVArray () = default;
DqnVArray (isize size) { LazyInit(size); } DqnVArray (isize size) { LazyInit(size); }
void LazyInit (isize size) { if (data) return; count = 0; max = size; data = (T *)DqnOS_VAlloc(max * sizeof(T)); DQN_ALWAYS_ASSERT(data); } void LazyInit (isize size) { if (data) return; count = 0; max = size; data = (T *)DqnOS_VAlloc(max * sizeof(T)); DQN_ALWAYS_ASSERT(data); }
~DqnVArray () { if (data) DqnOS_VFree(data, sizeof(T) * max); } // ~DqnVArray () { if (data) DqnOS_VFree(data, sizeof(T) * max); }
void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (data) { count = 0; if (clear == Dqn::ZeroClear::Yes) DqnMem_Clear(data, 0, sizeof(T) * max); } } void Clear (Dqn::ZeroClear clear = Dqn::ZeroClear::No) { if (data) { count = 0; if (clear == Dqn::ZeroClear::Yes) DqnMem_Clear(data, 0, sizeof(T) * max); } }
void Free () { if (data) { DqnOS_VFree(data, sizeof(T) * max); } *this = {}; } void Free () { if (data) { DqnOS_VFree(data, sizeof(T) * max); } *this = {}; }
T *Front () { return (count > 0) (data + 0) : nullptr; } T *Front () { return (count > 0) (data + 0) : nullptr; }
T *Back () { return (count > 0) (data + (count - 1)) : nullptr; } T *Back () { return (count > 0) (data + (count - 1)) : nullptr; }
T *Make (isize num = 1) { LazyInit(1024); count += num; DQN_ASSERT(count < max); return &data[count - num]; } T *Make (isize num = 1) { LazyInit(1024); count += num; DQN_ASSERT(count <= max); return &data[count - num]; }
T *Push (T const &v) { return Insert(count, &v, 1); } T *Push (T const &v) { return Insert(count, &v, 1); }
T *Push (T const *v, isize numItems = 1) { return Insert(count, v, numItems); } T *Push (T const *v, isize numItems = 1) { return Insert(count, v, numItems); }
void Pop () { if (count > 0) count--; } void Pop () { if (count > 0) count--; }
@ -2927,7 +2848,7 @@ template<typename T> T *DqnVArray<T>::Insert(isize index, T const *v, isize numI
index = DQN_CLAMP(index, 0, count); index = DQN_CLAMP(index, 0, count);
isize const newCount = count + numItems; isize const newCount = count + numItems;
DQN_ASSERT(newCount < max); DQN_ASSERT(newCount <= max);
T *src = data + index; T *src = data + index;
T *dest = src + numItems; T *dest = src + numItems;
@ -3193,7 +3114,7 @@ DQN_FILE_SCOPE f64 DqnTimer_NowInS ();
// XPlatform > #DqnLock API // XPlatform > #DqnLock API
// ================================================================================================= // =================================================================================================
typedef struct DqnLock struct DqnLock
{ {
#if defined(DQN_IS_WIN32) #if defined(DQN_IS_WIN32)
CRITICAL_SECTION win32Handle; CRITICAL_SECTION win32Handle;
@ -3210,31 +3131,18 @@ typedef struct DqnLock
void Release(); void Release();
void Delete (); void Delete ();
// Create a lock guard on the lock this is invoked on. struct Guard_
struct DqnLockGuard LockGuard();
} DqnLock;
// Lock guard automatically acquires a lock on construction and releases the associated lock on
// destruction. If the lock is unable to be acquired, the program blocks at construction until it
// can.
struct DqnLockGuard
{ {
// lock: Takes a pointer to a pre-existing and already initialised lock Guard_(DqnLock *lock_) : lock(lock_) { lock->Acquire(); }
// bool: Pass in (optionally) a pointer to a bool which returns whether a lock was successful. ~Guard_() { lock->Release(); }
// FALSE if lock is nullptr.
DqnLockGuard(DqnLock *const lock_, bool *const succeeded);
~DqnLockGuard();
private: private:
DqnLock *lock; DqnLock *lock;
}; };
// lock: Pass in a pointer to a default DqnLock struct. // Create a lock guard on the lock this is invoked on.
// In Win32, you may optionally change the win32Spincount. Guard_ Guard() { return Guard_(this); }
DQN_FILE_SCOPE bool DqnLock_Init (DqnLock *const lock); };
DQN_FILE_SCOPE void DqnLock_Acquire(DqnLock *const lock);
DQN_FILE_SCOPE void DqnLock_Release(DqnLock *const lock);
DQN_FILE_SCOPE void DqnLock_Delete (DqnLock *const lock);
// XPlatform > #DqnJobQueue API // XPlatform > #DqnJobQueue API
// ================================================================================================= // =================================================================================================
@ -6449,13 +6357,6 @@ DQN_FILE_SCOPE inline u32 Dqn_BitToggle(u32 bits, u32 flag)
return result; return result;
} }
DQN_FILE_SCOPE inline bool Dqn_QuickSortDqnStringSorter(struct DqnString const *a, struct DqnString const *b, void *)
{
bool result = DqnString::Cmp(a, b);
return result;
}
// #DqnString Impleemntation // #DqnString Impleemntation
// ================================================================================================= // =================================================================================================
// TODO(doyle): SSO requires handling assign/copy op when copying strings, we need to reassign the // TODO(doyle): SSO requires handling assign/copy op when copying strings, we need to reassign the
@ -6924,56 +6825,6 @@ i32 Dqn_SplitString(char const *src, i32 srcLen, char splitChar, DqnSlice<char>
return arrayIndex; return arrayIndex;
} }
DQN_FILE_SCOPE i64 Dqn_BSearch(i64 *array, i64 size, i64 find,
Dqn_BSearchBound bound)
{
if (size == 0 || !array) return -1;
i64 start = 0;
i64 end = size - 1;
i64 mid = (i64)((start + end) * 0.5f);
while (start <= end)
{
if (array[mid] == find)
{
if (bound == Dqn_BSearchBound_Normal ||
bound == Dqn_BSearchBound_NormalLower ||
bound == Dqn_BSearchBound_NormalHigher)
{
return mid;
}
else if (bound == Dqn_BSearchBound_Lower)
{
return mid - 1;
}
else
{
if ((mid + 1) >= size) return -1;
return mid + 1;
}
}
else if (array[mid] < find) start = mid + 1;
else end = mid - 1;
mid = (i64)((start + end) * 0.5f);
}
if (bound == Dqn_BSearchBound_Normal)
{
return -1;
}
if (bound == Dqn_BSearchBound_Lower || bound == Dqn_BSearchBound_NormalLower)
{
if (find < array[mid]) return -1;
return mid;
}
else
{
if (find > array[mid]) return -1;
return mid;
}
}
// TODO(doyle): This should maybe be a tokenizer ... // TODO(doyle): This should maybe be a tokenizer ...
DQN_FILE_SCOPE DqnJson DqnJson_Get(char const *buf, i32 bufLen, char const *findProperty, i32 findPropertyLen) DQN_FILE_SCOPE DqnJson DqnJson_Get(char const *buf, i32 bufLen, char const *findProperty, i32 findPropertyLen)
{ {
@ -9609,16 +9460,17 @@ DQN_FILE_SCOPE f64 DqnTimer_NowInS() { return DqnTimer_NowInMs() / 1000.0f; }
// XPlatform > #DqnLock // XPlatform > #DqnLock
// ================================================================================================= // =================================================================================================
bool DqnLock_Init(DqnLock *lock) bool DqnLock::Init()
{ {
if (!lock) return false;
#if defined(DQN_IS_WIN32) #if defined(DQN_IS_WIN32)
if (InitializeCriticalSectionEx(&lock->win32Handle, lock->win32SpinCount, 0)) if (InitializeCriticalSectionEx(&this->win32Handle, this->win32SpinCount, 0))
{
return true; return true;
}
#else #else
// NOTE: Static initialise, pre-empt a lock so that it gets initialised as per documentation // NOTE: Static initialise, pre-empt a lock so that it gets initialised as per documentation
lock->unixHandle = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; this->unixHandle = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
DqnLock_Acquire(lock); DqnLock_Acquire(lock);
DqnLock_Release(lock); DqnLock_Release(lock);
return true; return true;
@ -9627,75 +9479,38 @@ bool DqnLock_Init(DqnLock *lock)
return false; return false;
} }
void DqnLock_Acquire(DqnLock *lock) void DqnLock::Acquire()
{ {
if (!lock) return;
#if defined(DQN_IS_WIN32) #if defined(DQN_IS_WIN32)
EnterCriticalSection(&lock->win32Handle); EnterCriticalSection(&this->win32Handle);
#else #else
// TODO(doyle): Better error handling // TODO(doyle): Better error handling
i32 error = pthread_mutex_lock(&lock->unixHandle); i32 error = pthread_mutex_lock(&this->unixHandle);
DQN_ASSERT(error == 0); DQN_ASSERT(error == 0);
#endif #endif
} }
void DqnLock_Release(DqnLock *lock) void DqnLock::Release()
{ {
if (!lock) return;
#if defined(DQN_IS_WIN32) #if defined(DQN_IS_WIN32)
LeaveCriticalSection(&lock->win32Handle); LeaveCriticalSection(&this->win32Handle);
#else #else
// TODO(doyle): better error handling // TODO(doyle): better error handling
i32 error = pthread_mutex_unlock(&lock->unixHandle); i32 error = pthread_mutex_unlock(&this->unixHandle);
DQN_ASSERT(error == 0); DQN_ASSERT(error == 0);
#endif #endif
} }
void DqnLock_Delete(DqnLock *lock) void DqnLock::Delete()
{ {
if (!lock) return;
#if defined(DQN_IS_WIN32) #if defined(DQN_IS_WIN32)
DeleteCriticalSection(&lock->win32Handle); DeleteCriticalSection(&this->win32Handle);
#else #else
i32 error = pthread_mutex_destroy(&lock->unixHandle); i32 error = pthread_mutex_destroy(&this->unixHandle);
DQN_ASSERT(error == 0); DQN_ASSERT(error == 0);
#endif #endif
} }
bool DqnLock::Init() { return DqnLock_Init (this); }
void DqnLock::Acquire() { DqnLock_Acquire(this); }
void DqnLock::Release() { DqnLock_Release(this); }
void DqnLock::Delete() { DqnLock_Delete (this); }
DqnLockGuard DqnLock::LockGuard()
{
return DqnLockGuard(this, nullptr);
}
DqnLockGuard::DqnLockGuard(DqnLock *lock_, bool *succeeded)
{
if (lock_)
{
this->lock = lock_;
this->lock->Acquire();
if (succeeded) *succeeded = true;
}
else
{
if (succeeded) *succeeded = false;
}
}
DqnLockGuard::~DqnLockGuard()
{
if (this->lock) this->lock->Release();
}
// XPlatform > #DqnJobQueue // XPlatform > #DqnJobQueue
// ================================================================================================= // =================================================================================================
typedef void *DqnThreadCallbackInternal(void *threadParam); typedef void *DqnThreadCallbackInternal(void *threadParam);