Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,726 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
TEST_SUITE("ArrayTest")
{
// A test class that is non-trivially copyable to test if the Array class correctly constructs/destructs/copies and moves elements.
class NonTriv
{
public:
NonTriv() : mValue(0) { ++sNumConstructors; }
explicit NonTriv(int inValue) : mValue(inValue) { ++sNumConstructors; }
NonTriv(const NonTriv &inValue) : mValue(inValue.mValue) { ++sNumCopyConstructors; }
NonTriv(NonTriv &&inValue) : mValue(inValue.mValue) { inValue.mValue = 0; ++sNumMoveConstructors; }
~NonTriv() { ++sNumDestructors; }
NonTriv & operator = (const NonTriv &inRHS) { mValue = inRHS.mValue; return *this; }
bool operator == (const NonTriv &inRHS) const { return mValue == inRHS.mValue; }
bool operator != (const NonTriv &inRHS) const { return mValue != inRHS.mValue; }
int Value() const { return mValue; }
static void sReset() { sNumConstructors = 0; sNumCopyConstructors = 0; sNumMoveConstructors = 0; sNumDestructors = 0; }
static inline int sNumConstructors = 0;
static inline int sNumCopyConstructors = 0;
static inline int sNumMoveConstructors = 0;
static inline int sNumDestructors = 0;
int mValue;
};
TEST_CASE("TestConstructLength")
{
Array<int> arr(55);
CHECK(arr.size() == 55);
}
TEST_CASE("TestConstructLengthNonTriv")
{
NonTriv::sReset();
Array<NonTriv> arr(55);
CHECK(arr.size() == 55);
CHECK(NonTriv::sNumConstructors == 55);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestConstructValue")
{
Array<int> arr(5, 55);
CHECK(arr.size() == 5);
for (int i = 0; i < 5; ++i)
CHECK(arr[i] == 55);
}
TEST_CASE("TestConstructValueNonTriv")
{
NonTriv v(55);
NonTriv::sReset();
Array<NonTriv> arr(5, v);
CHECK(arr.size() == 5);
for (int i = 0; i < 5; ++i)
CHECK(arr[i].Value() == 55);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 5);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestConstructIterator")
{
int values[] = { 1, 2, 3 };
Array<int> arr(values, values + 3);
CHECK(arr.size() == 3);
CHECK(arr[0] == 1);
CHECK(arr[1] == 2);
CHECK(arr[2] == 3);
}
TEST_CASE("TestConstructIteratorNonTriv")
{
NonTriv values[] = { NonTriv(1), NonTriv(2), NonTriv(3) };
NonTriv::sReset();
Array<NonTriv> arr(values, values + 3);
CHECK(arr.size() == 3);
CHECK(arr[0].Value() == 1);
CHECK(arr[1].Value() == 2);
CHECK(arr[2].Value() == 3);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 3);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestConstructInitializerList")
{
Array<int> arr({ 1, 2, 3 });
CHECK(arr.size() == 3);
CHECK(arr[0] == 1);
CHECK(arr[1] == 2);
CHECK(arr[2] == 3);
}
TEST_CASE("TestConstructInitializerListNonTriv")
{
NonTriv::sReset();
Array<NonTriv> arr({ NonTriv(1), NonTriv(2), NonTriv(3) });
CHECK(arr.size() == 3);
CHECK(arr[0].Value() == 1);
CHECK(arr[1].Value() == 2);
CHECK(arr[2].Value() == 3);
CHECK(NonTriv::sNumConstructors == 3); // For the initializer list
CHECK(NonTriv::sNumCopyConstructors == 3); // Initializing the array
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 3); // For the initializer list
}
TEST_CASE("TestConstructFromArray")
{
Array<int> arr = { 1, 2, 3 };
Array<int> arr2(arr);
CHECK(arr2.size() == 3);
CHECK(arr2[0] == 1);
CHECK(arr2[1] == 2);
CHECK(arr2[2] == 3);
}
TEST_CASE("TestConstructFromArrayNonTriv")
{
Array<NonTriv> arr = { NonTriv(1), NonTriv(2), NonTriv(3) };
NonTriv::sReset();
Array<NonTriv> arr2(arr);
CHECK(arr2.size() == 3);
CHECK(arr2[0].Value() == 1);
CHECK(arr2[1].Value() == 2);
CHECK(arr2[2].Value() == 3);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 3);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestMoveFromArray")
{
Array<int> arr = { 1, 2, 3 };
Array<int> arr2(std::move(arr));
CHECK(arr2.size() == 3);
CHECK(arr2[0] == 1);
CHECK(arr2[1] == 2);
CHECK(arr2[2] == 3);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
}
TEST_CASE("TestMoveFromArrayNonTriv")
{
Array<NonTriv> arr = { NonTriv(1), NonTriv(2), NonTriv(3) };
NonTriv::sReset();
Array<NonTriv> arr2(std::move(arr)); // This just updates the mElements pointer so should not call any constructors/destructors etc.
CHECK(arr2.size() == 3);
CHECK(arr2[0].Value() == 1);
CHECK(arr2[1].Value() == 2);
CHECK(arr2[2].Value() == 3);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestClear")
{
Array<int> arr({ 1, 2, 3 });
CHECK(arr.size() == 3);
arr.clear();
CHECK(arr.size() == 0);
}
TEST_CASE("TestClearNonTriv")
{
NonTriv::sReset();
Array<NonTriv> arr({ NonTriv(1), NonTriv(2), NonTriv(3) });
CHECK(arr.size() == 3);
CHECK(NonTriv::sNumConstructors == 3); // For initializer list
CHECK(NonTriv::sNumCopyConstructors == 3); // To move into array
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 3); // For initializer list
NonTriv::sReset();
arr.clear();
CHECK(arr.size() == 0);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 3);
}
TEST_CASE("TestPushBack")
{
Array<int> arr;
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
arr.push_back(1);
CHECK(arr.size() == 1);
CHECK(arr[0] == 1);
arr.push_back(2);
CHECK(arr.size() == 2);
CHECK(arr[0] == 1);
CHECK(arr[1] == 2);
arr.pop_back();
CHECK(arr.size() == 1);
arr.pop_back();
CHECK(arr.size() == 0);
CHECK(arr.empty());
}
TEST_CASE("TestPushBackNonTriv")
{
NonTriv v1(1);
NonTriv v2(2);
NonTriv::sReset();
Array<NonTriv> arr;
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
NonTriv::sReset();
arr.push_back(v1);
CHECK(arr.size() == 1);
CHECK(arr[0].Value() == 1);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 1);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
NonTriv::sReset();
arr.push_back(v2);
CHECK(arr.size() == 2);
CHECK(arr[0].Value() == 1);
CHECK(arr[1].Value() == 2);
#ifndef JPH_USE_STD_VECTOR
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 1);
CHECK(NonTriv::sNumMoveConstructors == 1); // Array resizes from 1 to 2
CHECK(NonTriv::sNumDestructors == 1); // Array resizes from 1 to 2
#endif // JPH_USE_STD_VECTOR
NonTriv::sReset();
arr.pop_back();
CHECK(arr.size() == 1);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 1);
NonTriv::sReset();
arr.pop_back();
CHECK(arr.size() == 0);
CHECK(arr.empty());
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 1);
}
TEST_CASE("TestPushBackMove")
{
Array<Array<int>> arr;
Array<int> arr2 = { 1, 2, 3 };
arr.push_back(std::move(arr2));
CHECK(arr2.size() == 0);
CHECK(arr[0] == Array<int>({ 1, 2, 3 }));
}
TEST_CASE("TestEmplaceBack")
{
struct Test
{
Test(int inA, int inB) : mA(inA), mB(inB) { }
int mA;
int mB;
};
Array<Test> arr;
arr.emplace_back(1, 2);
CHECK(arr.size() == 1);
CHECK(arr[0].mA == 1);
CHECK(arr[0].mB == 2);
}
TEST_CASE("TestReserve")
{
Array<int> arr;
CHECK(arr.capacity() == 0);
arr.reserve(123);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 123);
arr.reserve(456);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 456);
}
TEST_CASE("TestReserveNonTriv")
{
NonTriv::sReset();
Array<NonTriv> arr;
CHECK(arr.capacity() == 0);
arr.reserve(123);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 123);
arr.reserve(456);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 456);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
TEST_CASE("TestResize")
{
Array<int> arr;
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
CHECK(arr.data() == nullptr);
arr.resize(0);
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
CHECK(arr.data() == nullptr);
arr.resize(123);
CHECK(arr.size() == 123);
CHECK(arr.capacity() == 123);
for (int i = 0; i < 123; ++i)
arr[i] = i;
arr.resize(456);
CHECK(arr.size() == 456);
CHECK(arr.capacity() == 456);
for (int i = 0; i < 123; ++i)
CHECK(arr[i] == i);
arr.resize(10);
CHECK(arr.size() == 10);
CHECK(arr.capacity() >= 10);
}
TEST_CASE("TestResizeNonTriv")
{
NonTriv::sReset();
Array<NonTriv> arr;
CHECK(arr.size() == 0);
CHECK(arr.capacity() == 0);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
NonTriv::sReset();
arr.resize(123);
CHECK(arr.size() == 123);
CHECK(arr.capacity() == 123);
CHECK(NonTriv::sNumConstructors == 123);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
for (int i = 0; i < 123; ++i)
arr[i] = NonTriv(i);
NonTriv::sReset();
arr.resize(456);
CHECK(arr.size() == 456);
CHECK(arr.capacity() == 456);
for (int i = 0; i < 123; ++i)
CHECK(arr[i].Value() == i);
#ifndef JPH_USE_STD_VECTOR
CHECK(NonTriv::sNumConstructors == 456 - 123);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 123);
CHECK(NonTriv::sNumDestructors == 123); // Switched to a new block, all old elements are destroyed after being moved
#endif // JPH_USE_STD_VECTOR
NonTriv::sReset();
arr.resize(10);
CHECK(arr.size() == 10);
CHECK(arr.capacity() >= 10);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 456 - 10);
}
TEST_CASE("TestResizeWithValue")
{
Array<int> arr;
arr.resize(10, 55);
CHECK(arr.size() == 10);
CHECK(arr.capacity() == 10);
for (int i = 0; i < 10; ++i)
CHECK(arr[i] == 55);
}
TEST_CASE("TestResizeWithValueNonTriv")
{
NonTriv v(55);
Array<NonTriv> arr;
NonTriv::sReset();
arr.resize(10, v);
CHECK(arr.size() == 10);
CHECK(arr.capacity() == 10);
for (int i = 0; i < 10; ++i)
CHECK(arr[i].Value() == 55);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 10);
CHECK(NonTriv::sNumMoveConstructors == 0);
CHECK(NonTriv::sNumDestructors == 0);
}
#ifndef JPH_USE_STD_VECTOR // std::vector can choose to not shrink the array when calling shrink_to_fit so we can't test this
TEST_CASE("TestShrinkToFit")
{
Array<int> arr;
for (int i = 0; i < 5; ++i)
arr.push_back(i);
CHECK(arr.capacity() > 5);
CHECK(arr.size() == 5);
arr.shrink_to_fit();
CHECK(arr.capacity() == 5);
CHECK(arr.size() == 5);
for (int i = 0; i < 5; ++i)
CHECK(arr[i] == i);
arr.clear();
CHECK(arr.capacity() == 5);
CHECK(arr.size() == 0);
arr.shrink_to_fit();
CHECK(arr.capacity() == 0);
CHECK(arr.data() == nullptr);
}
TEST_CASE("TestShrinkToFitNonTriv")
{
Array<NonTriv> arr;
for (int i = 0; i < 5; ++i)
arr.push_back(NonTriv(i));
CHECK(arr.capacity() > 5);
CHECK(arr.size() == 5);
NonTriv::sReset();
arr.shrink_to_fit();
CHECK(arr.capacity() == 5);
CHECK(arr.size() == 5);
for (int i = 0; i < 5; ++i)
CHECK(arr[i].Value() == i);
CHECK(NonTriv::sNumConstructors == 0);
CHECK(NonTriv::sNumCopyConstructors == 0);
CHECK(NonTriv::sNumMoveConstructors == 5);
CHECK(NonTriv::sNumDestructors == 5); // Switched to a new block, all old elements are destroyed after being moved
}
#endif // JPH_USE_STD_VECTOR
TEST_CASE("TestAssignIterator")
{
int values[] = { 1, 2, 3 };
Array<int> arr({ 4, 5, 6 });
arr.assign(values, values + 3);
CHECK(arr.size() == 3);
CHECK(arr[0] == 1);
CHECK(arr[1] == 2);
CHECK(arr[2] == 3);
}
TEST_CASE("TestAssignInitializerList")
{
Array<int> arr({ 4, 5, 6 });
arr.assign({ 1, 2, 3 });
CHECK(arr.size() == 3);
CHECK(arr[0] == 1);
CHECK(arr[1] == 2);
CHECK(arr[2] == 3);
}
TEST_CASE("TestSwap")
{
Array<int> arr({ 1, 2, 3 });
Array<int> arr2({ 4, 5, 6 });
arr.swap(arr2);
CHECK(arr == Array<int>({ 4, 5, 6 }));
CHECK(arr2 == Array<int>({ 1, 2, 3 }));
}
TEST_CASE("TestInsertBegin")
{
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin(), 4);
CHECK(arr == Array<int>({ 4, 1, 2, 3 }));
}
TEST_CASE("TestInsertMid")
{
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin() + 1, 4);
CHECK(arr == Array<int>({ 1, 4, 2, 3 }));
}
TEST_CASE("TestInsertEnd")
{
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin() + 3, 4);
CHECK(arr == Array<int>({ 1, 2, 3, 4 }));
}
TEST_CASE("TestInsertMultipleBegin")
{
Array<int> values_to_insert = { 4, 5, 6, 7 };
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin(), values_to_insert.begin(), values_to_insert.end());
CHECK(arr == Array<int>({ 4, 5, 6, 7, 1, 2, 3 }));
}
TEST_CASE("TestInsertMultipleMid")
{
Array<int> values_to_insert = { 4, 5, 6, 7 };
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin() + 1, values_to_insert.begin(), values_to_insert.end());
CHECK(arr == Array<int>({ 1, 4, 5, 6, 7, 2, 3 }));
}
TEST_CASE("TestInsertMultipleEnd")
{
Array<int> values_to_insert = { 4, 5, 6, 7 };
Array<int> arr = { 1, 2, 3 };
arr.insert(arr.begin() + 3, values_to_insert.begin(), values_to_insert.end());
CHECK(arr == Array<int>({ 1, 2, 3, 4, 5, 6, 7 }));
}
TEST_CASE("TestFrontBack")
{
Array<int> arr({ 1, 2, 3 });
CHECK(arr.front() == 1);
CHECK(arr.back() == 3);
}
TEST_CASE("TestAssign")
{
Array<int> arr({ 1, 2, 3 });
Array<int> arr2({ 4, 5, 6 });
arr = arr2;
CHECK(arr == Array<int>({ 4, 5, 6 }));
Array<int> &arr3 = arr; // Avoid compiler warning
arr = arr3;
CHECK(arr == Array<int>({ 4, 5, 6 }));
arr = { 7, 8, 9 };
CHECK(arr == Array<int>({ 7, 8, 9 }));
}
TEST_CASE("TestAssignMove")
{
Array<int> arr({ 1, 2, 3 });
Array<int> arr2({ 4, 5, 6 });
arr = std::move(arr2);
CHECK(arr == Array<int>({ 4, 5, 6 }));
CHECK(arr2.empty());
}
TEST_CASE("TestEraseBegin")
{
Array<int> arr({ 1, 2, 3 });
arr.erase(arr.begin());
CHECK(arr == Array<int>({ 2, 3 }));
}
TEST_CASE("TestEraseMid")
{
Array<int> arr({ 1, 2, 3 });
arr.erase(arr.begin() + 1);
CHECK(arr == Array<int>({ 1, 3 }));
}
TEST_CASE("TestEraseEnd")
{
Array<int> arr({ 1, 2, 3 });
arr.erase(arr.begin() + 2);
CHECK(arr == Array<int>({ 1, 2 }));
}
TEST_CASE("TestEraseMultipleBegin")
{
Array<int> arr({ 1, 2, 3, 4, 5 });
arr.erase(arr.begin(), arr.begin() + 2);
CHECK(arr == Array<int>({ 3, 4, 5 }));
}
TEST_CASE("TestEraseMultipleMid")
{
Array<int> arr({ 1, 2, 3, 4, 5 });
arr.erase(arr.begin() + 2, arr.begin() + 4);
CHECK(arr == Array<int>({ 1, 2, 5 }));
}
TEST_CASE("TestEraseMultipleEnd")
{
Array<int> arr({ 1, 2, 3, 4, 5 });
arr.erase(arr.begin() + 3, arr.begin() + 5);
CHECK(arr == Array<int>({ 1, 2, 3 }));
}
TEST_CASE("TestEquals")
{
Array<int> arr({ 1, 2, 3 });
Array<int> arr2({ 4, 5, 6 });
CHECK(arr == arr);
CHECK(!(arr == arr2));
CHECK(!(arr != arr));
CHECK(arr != arr2);
}
TEST_CASE("TestReverseIterator")
{
Array<int> arr({ 1, 2, 3, 4, 5, 6 });
Array<int>::reverse_iterator r = arr.rbegin();
CHECK(*r == 6);
int v = *(++r);
CHECK(v == 5);
CHECK(*r == 5);
v = *(--r);
CHECK(v == 6);
CHECK(*r == 6);
v = *(r++);
CHECK(v == 6);
CHECK(*r == 5);
v = *(r--);
CHECK(v == 5);
CHECK(*r == 6);
v = *(r += 2);
CHECK(v == 4);
CHECK(*r == 4);
v = *(r -= 2);
CHECK(v == 6);
CHECK(*r == 6);
CHECK(r == arr.rbegin());
CHECK(r != arr.rend());
r += 6;
CHECK(r == arr.rend());
CHECK(*(arr.rbegin() + 3) == 3);
CHECK(*(arr.rend() - 3) == 3);
}
TEST_CASE("TestConstReverseIterator")
{
const Array<int> arr({ 1, 2, 3, 4, 5, 6 });
Array<int>::const_reverse_iterator r = arr.rbegin();
CHECK(*r == 6);
int v = *(++r);
CHECK(v == 5);
CHECK(*r == 5);
v = *(--r);
CHECK(v == 6);
CHECK(*r == 6);
v = *(r++);
CHECK(v == 6);
CHECK(*r == 5);
v = *(r--);
CHECK(v == 5);
CHECK(*r == 6);
v = *(r += 2);
CHECK(v == 4);
CHECK(*r == 4);
v = *(r -= 2);
CHECK(v == 6);
CHECK(*r == 6);
CHECK(r == arr.rbegin());
CHECK(r == arr.crbegin());
CHECK(r != arr.rend());
CHECK(r != arr.crend());
r += 6;
CHECK(r == arr.rend());
CHECK(r == arr.crend());
CHECK(*(arr.rbegin() + 3) == 3);
CHECK(*(arr.rend() - 3) == 3);
}
}

View File

@@ -0,0 +1,47 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/BinaryHeap.h>
TEST_SUITE("BinaryHeapTest")
{
TEST_CASE("TestBinaryHeap")
{
// Add some numbers
Array<int> array;
array.reserve(1100);
for (int i = 0; i < 1000; ++i)
array.push_back(i);
// Ensure we have some duplicates
for (int i = 0; i < 1000; i += 10)
array.push_back(i);
// Shuffle the array
UnitTestRandom random(123);
std::shuffle(array.begin(), array.end(), random);
// Add the numbers to the heap
Array<int> heap;
for (int i : array)
{
heap.push_back(i);
BinaryHeapPush(heap.begin(), heap.end(), std::less<int> { });
}
// Check that the heap is sorted
int last = INT_MAX;
int seen[1000] { 0 };
while (!heap.empty())
{
BinaryHeapPop(heap.begin(), heap.end(), std::less<int> { });
int current = heap.back();
CHECK(++seen[current] <= (current % 10 == 0? 2 : 1));
heap.pop_back();
CHECK(current <= last);
last = current;
}
}
}

View File

@@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/FPFlushDenormals.h>
#include <atomic>
#if !defined(JPH_CPU_WASM) && !defined(JPH_CPU_RISCV) && !defined(JPH_CPU_PPC) && !defined(JPH_CPU_LOONGARCH)
// Implemented as a global atomic so the compiler can't optimize it to a constant
extern atomic<float> TestFltMin;
atomic<float> TestFltMin = FLT_MIN;
TEST_SUITE("FlushDenormalsTests")
{
TEST_CASE("TestFlushDenormals")
{
// By default flush denormals should be off
{
float value = TestFltMin * 0.1f;
CHECK(value > 0.0f);
}
// Turn flush denormal on
{
FPFlushDenormals flush_denormals;
float value = TestFltMin * 0.1f;
CHECK(value == 0.0f);
}
// Check if state was properly restored
{
float value = TestFltMin * 0.1f;
CHECK(value > 0.0f);
}
// Update TestFltMin to prevent the compiler from optimizing away TestFltMin and replace all calculations above with 0
TestFltMin = 1.0f;
}
}
#endif // JPH_CPU_WASM

View File

@@ -0,0 +1,51 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/HashCombine.h>
TEST_SUITE("HashCombineTest")
{
TEST_CASE("TestHashBytes")
{
CHECK(HashBytes("This is a test", 14) == 2733878766136413408UL);
}
TEST_CASE("TestHashString")
{
CHECK(HashString("This is a test") == 2733878766136413408UL);
}
TEST_CASE("TestHashStruct")
{
const char *char_test = "This is a test";
CHECK(Hash<const char *> { } (char_test) == 2733878766136413408UL);
std::string_view str_view_test = "This is a test";
CHECK(Hash<std::string_view> { } (str_view_test) == 2733878766136413408UL);
String str_test = "This is a test";
CHECK(Hash<String> { } (str_test) == 2733878766136413408UL);
}
TEST_CASE("TestHashCombine")
{
int val1 = 0;
uint64 val1_hash = Hash<int> { } (val1);
int val2 = 1;
uint64 val2_hash = Hash<int> { } (val2);
// Check non-commutative
uint64 seed1 = val1_hash;
HashCombine(seed1, val2);
uint64 seed2 = val2_hash;
HashCombine(seed2, val1);
CHECK(seed1 != seed2);
// Check that adding a 0 changes the hash
uint64 seed3 = val1_hash;
HashCombine(seed3, val1);
CHECK(seed3 != val1_hash);
}
}

View File

@@ -0,0 +1,96 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/InsertionSort.h>
TEST_SUITE("InsertionSortTest")
{
TEST_CASE("TestOrderedArray")
{
Array<int> array;
for (int i = 0; i < 10; i++)
array.push_back(i);
InsertionSort(array.begin(), array.end());
for (int i = 0; i < 10; i++)
CHECK(array[i] == i);
}
TEST_CASE("TestOrderedArrayComparator")
{
Array<int> array;
for (int i = 0; i < 100; i++)
array.push_back(i);
InsertionSort(array.begin(), array.end(), greater<int> {});
for (int i = 0; i < 100; i++)
CHECK(array[i] == 99 - i);
}
TEST_CASE("TestReversedArray")
{
Array<int> array;
for (int i = 0; i < 10; i++)
array.push_back(9 - i);
InsertionSort(array.begin(), array.end());
for (int i = 0; i < 10; i++)
CHECK(array[i] == i);
}
TEST_CASE("TestRandomArray")
{
UnitTestRandom random;
Array<UnitTestRandom::result_type> array;
for (int i = 0; i < 100; i++)
{
UnitTestRandom::result_type value = random();
// Insert value at beginning
array.insert(array.begin(), value);
// Insert value at end
array.push_back(value);
}
InsertionSort(array.begin(), array.end());
for (Array<int>::size_type i = 0; i < array.size() - 2; i += 2)
{
// We inserted the same value twice so these elements should be the same
CHECK(array[i] == array[i + 1]);
// The next element should be bigger or equal
CHECK(array[i] <= array[i + 2]);
}
}
TEST_CASE("TestEmptyArray")
{
Array<int> array;
InsertionSort(array.begin(), array.end());
CHECK(array.empty());
}
TEST_CASE("Test1ElementArray")
{
Array<int> array { 1 };
InsertionSort(array.begin(), array.end());
CHECK(array[0] == 1);
}
TEST_CASE("Test2ElementArray")
{
Array<int> array { 2, 1 };
InsertionSort(array.begin(), array.end());
CHECK(array[0] == 1);
CHECK(array[1] == 2);
}
}

View File

@@ -0,0 +1,91 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/JobSystemThreadPool.h>
TEST_SUITE("JobSystemTest")
{
TEST_CASE("TestJobSystemRunJobs")
{
// Create job system
const int cMaxJobs = 128;
const int cMaxBarriers = 10;
const int cMaxThreads = 10;
JobSystemThreadPool system(cMaxJobs, cMaxBarriers, cMaxThreads);
// Create array of zeros
atomic<uint32> values[cMaxJobs];
for (int i = 0; i < cMaxJobs; ++i)
values[i] = 0;
// Create a barrier
JobSystem::Barrier *barrier = system.CreateBarrier();
// Create jobs that will increment all values
for (int i = 0; i < cMaxJobs; ++i)
{
JobHandle handle = system.CreateJob("JobTest", Color::sRed, [&values, i] { values[i]++; });
barrier->AddJob(handle);
}
// Wait for the barrier to complete
system.WaitForJobs(barrier);
// Destroy our barrier
system.DestroyBarrier(barrier);
// Test all values are 1
for (int i = 0; i < cMaxJobs; ++i)
CHECK(values[i] == 1);
}
TEST_CASE("TestJobSystemRunChain")
{
// Create job system
const int cMaxJobs = 128;
const int cMaxBarriers = 10;
JobSystemThreadPool system(cMaxJobs, cMaxBarriers);
// Create a barrier
JobSystem::Barrier *barrier = system.CreateBarrier();
// Counter that keeps track of order in which jobs ran
atomic<uint32> counter = 1;
// Create array of zeros
atomic<uint32> values[cMaxJobs];
for (int i = 0; i < cMaxJobs; ++i)
values[i] = 0;
// Create jobs that will set sequence number
JobHandle handles[cMaxJobs];
for (int i = 0; i < cMaxJobs; ++i)
{
handles[i] = system.CreateJob("JobTestChain", Color::sRed, [&values, &counter, &handles, i] {
// Set sequence number
values[i] = counter++;
// Start previous job
if (i > 0)
handles[i - 1].RemoveDependency();
}, 1);
barrier->AddJob(handles[i]);
}
// Start the last job
handles[cMaxJobs - 1].RemoveDependency();
// Wait for the barrier to complete
system.WaitForJobs(barrier);
// Destroy our barrier
system.DestroyBarrier(barrier);
// Test jobs were executed in reverse order
for (int i = cMaxJobs - 1; i >= 0; --i)
CHECK(values[i] == cMaxJobs - i);
}
}

View File

@@ -0,0 +1,57 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/LinearCurve.h>
TEST_SUITE("LinearCurveTest")
{
TEST_CASE("Test0PointCurve")
{
LinearCurve curve;
CHECK(curve.GetValue(1.0f) == 0.0f);
}
TEST_CASE("Test1PointCurve")
{
LinearCurve curve;
curve.AddPoint(1.0f, 20.0f);
CHECK(curve.GetValue(0.9f) == 20.0f);
CHECK(curve.GetValue(1.0f) == 20.0f);
CHECK(curve.GetValue(1.1f) == 20.0f);
}
TEST_CASE("Test2PointCurve")
{
LinearCurve curve;
curve.AddPoint(-1.0f, 40.0f);
curve.AddPoint(-3.0f, 20.0f);
curve.Sort();
CHECK_APPROX_EQUAL(curve.GetValue(-3.1f), 20.0f);
CHECK_APPROX_EQUAL(curve.GetValue(-3.0f), 20.0f);
CHECK_APPROX_EQUAL(curve.GetValue(-2.0f), 30.0f);
CHECK_APPROX_EQUAL(curve.GetValue(-1.0f), 40.0f);
CHECK_APPROX_EQUAL(curve.GetValue(-0.9f), 40.0f);
}
TEST_CASE("Test3PointCurve")
{
LinearCurve curve;
curve.AddPoint(1.0f, 20.0f);
curve.AddPoint(5.0f, 60.0f);
curve.AddPoint(3.0f, 40.0f);
curve.Sort();
CHECK_APPROX_EQUAL(curve.GetValue(0.9f), 20.0f);
CHECK_APPROX_EQUAL(curve.GetValue(1.0f), 20.0f);
CHECK_APPROX_EQUAL(curve.GetValue(2.0f), 30.0f);
CHECK_APPROX_EQUAL(curve.GetValue(3.0f), 40.0f);
CHECK_APPROX_EQUAL(curve.GetValue(4.0f), 50.0f);
CHECK_APPROX_EQUAL(curve.GetValue(5.0f), 60.0f);
CHECK_APPROX_EQUAL(curve.GetValue(5.1f), 60.0f);
}
}

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <atomic>
// Implemented as a global atomic so the compiler can't optimize it to a constant
extern atomic<float> One, OneTenth, Ten;
atomic<float> One = 1.0f, OneTenth = 0.1f /* Actually: 0.100000001f */, Ten = 10.0f;
JPH_PRECISE_MATH_ON
TEST_SUITE("PreciseMathTest")
{
TEST_CASE("CheckNoFMA")
{
// Should not use the FMA instruction and end up being zero
// If FMA is active, a * b will not be rounded to 1.0f so the result will be a small positive number
float a = OneTenth, b = Ten, c = One;
float result = a * b - c;
CHECK(result == 0.0f);
// Compiler should not optimize these variables away
One = OneTenth = Ten = 2.0f;
}
}
JPH_PRECISE_MATH_OFF

View File

@@ -0,0 +1,96 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/QuickSort.h>
TEST_SUITE("QuickSortTest")
{
TEST_CASE("TestOrderedArray")
{
Array<int> array;
for (int i = 0; i < 100; i++)
array.push_back(i);
QuickSort(array.begin(), array.end());
for (int i = 0; i < 100; i++)
CHECK(array[i] == i);
}
TEST_CASE("TestOrderedArrayComparator")
{
Array<int> array;
for (int i = 0; i < 100; i++)
array.push_back(i);
QuickSort(array.begin(), array.end(), greater<int> {});
for (int i = 0; i < 100; i++)
CHECK(array[i] == 99 - i);
}
TEST_CASE("TestReversedArray")
{
Array<int> array;
for (int i = 0; i < 100; i++)
array.push_back(99 - i);
QuickSort(array.begin(), array.end());
for (int i = 0; i < 100; i++)
CHECK(array[i] == i);
}
TEST_CASE("TestRandomArray")
{
UnitTestRandom random;
Array<UnitTestRandom::result_type> array;
for (int i = 0; i < 1000; i++)
{
UnitTestRandom::result_type value = random();
// Insert value at beginning
array.insert(array.begin(), value);
// Insert value at end
array.push_back(value);
}
QuickSort(array.begin(), array.end());
for (Array<int>::size_type i = 0; i < array.size() - 2; i += 2)
{
// We inserted the same value twice so these elements should be the same
CHECK(array[i] == array[i + 1]);
// The next element should be bigger or equal
CHECK(array[i] <= array[i + 2]);
}
}
TEST_CASE("TestEmptyArray")
{
Array<int> array;
QuickSort(array.begin(), array.end());
CHECK(array.empty());
}
TEST_CASE("Test1ElementArray")
{
Array<int> array { 1 };
QuickSort(array.begin(), array.end());
CHECK(array[0] == 1);
}
TEST_CASE("Test2ElementArray")
{
Array<int> array { 2, 1 };
QuickSort(array.begin(), array.end());
CHECK(array[0] == 1);
CHECK(array[1] == 2);
}
}

View File

@@ -0,0 +1,215 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/STLLocalAllocator.h>
TEST_SUITE("STLLocalAllocatorTest")
{
/// The number of elements in the local buffer
static constexpr size_t N = 20;
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
template <class ArrayType>
static bool sIsLocal(ArrayType &inArray)
{
#ifdef JPH_USE_STD_VECTOR
// Check that the data pointer is within the array.
// Note that when using std::vector we cannot use get_allocator as that makes a copy of the allocator internally
// and we've disabled the copy constructor since our allocator cannot be copied.
const uint8 *data = reinterpret_cast<const uint8 *>(inArray.data());
const uint8 *array = reinterpret_cast<const uint8 *>(&inArray);
return data >= array && data < array + sizeof(inArray);
#else
return inArray.get_allocator().is_local(inArray.data());
#endif
}
#endif
template <class ArrayType, bool NonTrivial>
static void sTestArray()
{
// Allocate so that we will run out of local memory and reallocate from heap at least once
ArrayType arr;
for (int i = 0; i < 64; ++i)
arr.push_back(i);
CHECK(arr.size() == 64);
for (int i = 0; i < 64; ++i)
{
CHECK(arr[i] == i);
#if !defined(JPH_USE_STD_VECTOR) && !defined(JPH_DISABLE_CUSTOM_ALLOCATOR)
// We only have to move elements once we run out of the local buffer, this happens as we resize
// from 16 to 32 elements, we'll reallocate again at 32 and 64
if constexpr (NonTrivial)
CHECK(arr[i].GetNonTriv() == (i < 16? 3 : (i < 32? 2 : 1)));
#endif
}
CHECK(IsAligned(arr.data(), alignof(typename ArrayType::value_type)));
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
CHECK(!sIsLocal(arr));
#endif
// Check that we can copy the array to another array
ArrayType arr2;
arr2 = arr;
for (int i = 0; i < 64; ++i)
{
CHECK(arr2[i] == i);
if constexpr (NonTrivial)
CHECK(arr2[i].GetNonTriv() == -999);
}
CHECK(IsAligned(arr2.data(), alignof(typename ArrayType::value_type)));
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
CHECK(!sIsLocal(arr2));
#endif
// Clear the array
arr.clear();
arr.shrink_to_fit();
CHECK(arr.size() == 0);
#ifndef JPH_USE_STD_VECTOR // Some implementations of std::vector ignore shrink_to_fit
CHECK(arr.capacity() == 0);
CHECK(arr.data() == nullptr);
#endif
// Allocate so we stay within the local buffer
for (int i = 0; i < 10; ++i)
arr.push_back(i);
CHECK(arr.size() == 10);
for (int i = 0; i < 10; ++i)
{
CHECK(arr[i] == i);
#if !defined(JPH_USE_STD_VECTOR) && !defined(JPH_DISABLE_CUSTOM_ALLOCATOR)
// We never need to move elements as they stay within the local buffer
if constexpr (NonTrivial)
CHECK(arr[i].GetNonTriv() == 1);
#endif
}
CHECK(IsAligned(arr.data(), alignof(typename ArrayType::value_type)));
#if !defined(JPH_USE_STD_VECTOR) && !defined(JPH_DISABLE_CUSTOM_ALLOCATOR) // Doesn't work with std::vector since it doesn't use the reallocate function and runs out of space
CHECK(sIsLocal(arr));
#endif
// Check that we can copy the array to the local buffer
ArrayType arr3;
arr3 = arr;
CHECK(arr3.size() == 10);
for (int i = 0; i < 10; ++i)
{
CHECK(arr3[i] == i);
if constexpr (NonTrivial)
CHECK(arr3[i].GetNonTriv() == -999);
}
CHECK(IsAligned(arr3.data(), alignof(typename ArrayType::value_type)));
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
CHECK(sIsLocal(arr3));
#endif
// Check that if we reserve the memory, that we can fully fill the array in local memory
ArrayType arr4;
arr4.reserve(N);
for (int i = 0; i < int(N); ++i)
arr4.push_back(i);
CHECK(arr4.size() == N);
CHECK(arr4.capacity() == N);
for (int i = 0; i < int(N); ++i)
{
CHECK(arr4[i] == i);
if constexpr (NonTrivial)
CHECK(arr4[i].GetNonTriv() == 1);
}
CHECK(IsAligned(arr4.data(), alignof(typename ArrayType::value_type)));
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
CHECK(sIsLocal(arr4));
#endif
}
TEST_CASE("TestAllocation")
{
using Allocator = STLLocalAllocator<int, N>;
using ArrayType = Array<int, Allocator>;
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
static_assert(AllocatorHasReallocate<Allocator>::sValue);
#endif
sTestArray<ArrayType, false>();
}
TEST_CASE("TestAllocationAligned")
{
// Force the need for an aligned allocation
struct alignas(64) Aligned
{
Aligned(int inValue) : mValue(inValue) { }
operator int() const { return mValue; }
private:
int mValue;
};
static_assert(std::is_trivially_copyable<Aligned>());
using Allocator = STLLocalAllocator<Aligned, N>;
using ArrayType = Array<Aligned, Allocator>;
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
static_assert(AllocatorHasReallocate<Allocator>::sValue);
#endif
sTestArray<ArrayType, false>();
}
TEST_CASE("TestAllocationNonTrivial")
{
// Force non trivial copy constructor
struct NonTriv
{
NonTriv(int inValue) : mValue(inValue) { }
NonTriv(const NonTriv &inRHS) : mValue(inRHS.mValue), mMakeNonTriv(-999) { }
NonTriv(NonTriv &&inRHS) : mValue(inRHS.mValue), mMakeNonTriv(inRHS.mMakeNonTriv + 1) { }
NonTriv & operator = (const NonTriv &inRHS) { mValue = inRHS.mValue; mMakeNonTriv = -9999; return *this; }
operator int() const { return mValue; }
int GetNonTriv() const { return mMakeNonTriv; }
private:
int mValue;
int mMakeNonTriv = 0;
};
static_assert(!std::is_trivially_copyable<NonTriv>());
using Allocator = STLLocalAllocator<NonTriv, N>;
using ArrayType = Array<NonTriv, Allocator>;
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
static_assert(AllocatorHasReallocate<Allocator>::sValue);
#endif
sTestArray<ArrayType, true>();
}
TEST_CASE("TestAllocationAlignedNonTrivial")
{
// Force non trivial copy constructor
struct alignas(64) AlNonTriv
{
AlNonTriv(int inValue) : mValue(inValue) { }
AlNonTriv(const AlNonTriv &inRHS) : mValue(inRHS.mValue), mMakeNonTriv(-999) { }
AlNonTriv(AlNonTriv &&inRHS) : mValue(inRHS.mValue), mMakeNonTriv(inRHS.mMakeNonTriv + 1) { }
AlNonTriv & operator = (const AlNonTriv &inRHS) { mValue = inRHS.mValue; mMakeNonTriv = -9999; return *this; }
operator int() const { return mValue; }
int GetNonTriv() const { return mMakeNonTriv; }
private:
int mValue;
int mMakeNonTriv = 0;
};
static_assert(!std::is_trivially_copyable<AlNonTriv>());
using Allocator = STLLocalAllocator<AlNonTriv, N>;
using ArrayType = Array<AlNonTriv, Allocator>;
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
static_assert(AllocatorHasReallocate<Allocator>::sValue);
#endif
sTestArray<ArrayType, true>();
}
}

View File

@@ -0,0 +1,46 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/ScopeExit.h>
TEST_SUITE("ScopeExitTest")
{
TEST_CASE("TestScopeExitOrder")
{
int value = 0;
{
// Last created should be first destroyed
JPH_SCOPE_EXIT([&value]{ CHECK(value == 1); value = 2; });
JPH_SCOPE_EXIT([&value]{ CHECK(value == 0); value = 1; });
CHECK(value == 0);
}
CHECK(value == 2);
}
TEST_CASE("TestScopeExitRelease")
{
int value = 0;
{
ScopeExit scope_exit([&value]{ value++; });
CHECK(value == 0);
// Don't call the exit function anymore
scope_exit.Release();
}
CHECK(value == 0);
}
TEST_CASE("TestScopeExitInvoke")
{
int value = 0;
{
ScopeExit scope_exit([&value]{ value++; });
CHECK(value == 0);
scope_exit.Invoke();
CHECK(value == 1);
// Should not call again on exit
}
CHECK(value == 1);
}
}

View File

@@ -0,0 +1,93 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/StringTools.h>
TEST_SUITE("StringToolsTest")
{
TEST_CASE("TestStringFormat")
{
CHECK(StringFormat("Test: %d", 1234) == "Test: 1234");
}
TEST_CASE("TestConvertToString")
{
CHECK(ConvertToString(1234) == "1234");
CHECK(ConvertToString(-1) == "-1");
CHECK(ConvertToString(0x7fffffffffffffffUL) == "9223372036854775807");
}
TEST_CASE("StringReplace")
{
JPH::String value = "Hello this si si a test";
StringReplace(value, "si", "is");
CHECK(value == "Hello this is is a test");
StringReplace(value, "is is", "is");
CHECK(value == "Hello this is a test");
StringReplace(value, "Hello", "Bye");
CHECK(value == "Bye this is a test");
StringReplace(value, "a test", "complete");
CHECK(value == "Bye this is complete");
}
TEST_CASE("StringToVector")
{
Array<JPH::String> value;
StringToVector("", value);
CHECK(value.empty());
StringToVector("a,b,c", value);
CHECK(value[0] == "a");
CHECK(value[1] == "b");
CHECK(value[2] == "c");
StringToVector("a,.b,.c,", value, ".");
CHECK(value[0] == "a,");
CHECK(value[1] == "b,");
CHECK(value[2] == "c,");
}
TEST_CASE("VectorToString")
{
Array<JPH::String> input;
JPH::String value;
VectorToString(input, value);
CHECK(value.empty());
input = { "a", "b", "c" };
VectorToString(input, value);
CHECK(value == "a,b,c");
VectorToString(input, value, ", ");
CHECK(value == "a, b, c");
}
TEST_CASE("ToLower")
{
CHECK(ToLower("123 HeLlO!") == "123 hello!");
}
TEST_CASE("NibbleToBinary")
{
CHECK(strcmp(NibbleToBinary(0b0000), "0000") == 0);
CHECK(strcmp(NibbleToBinary(0b0001), "0001") == 0);
CHECK(strcmp(NibbleToBinary(0b0010), "0010") == 0);
CHECK(strcmp(NibbleToBinary(0b0011), "0011") == 0);
CHECK(strcmp(NibbleToBinary(0b0100), "0100") == 0);
CHECK(strcmp(NibbleToBinary(0b0101), "0101") == 0);
CHECK(strcmp(NibbleToBinary(0b0110), "0110") == 0);
CHECK(strcmp(NibbleToBinary(0b0111), "0111") == 0);
CHECK(strcmp(NibbleToBinary(0b1000), "1000") == 0);
CHECK(strcmp(NibbleToBinary(0b1001), "1001") == 0);
CHECK(strcmp(NibbleToBinary(0b1010), "1010") == 0);
CHECK(strcmp(NibbleToBinary(0b1011), "1011") == 0);
CHECK(strcmp(NibbleToBinary(0b1100), "1100") == 0);
CHECK(strcmp(NibbleToBinary(0b1101), "1101") == 0);
CHECK(strcmp(NibbleToBinary(0b1110), "1110") == 0);
CHECK(strcmp(NibbleToBinary(0b1111), "1111") == 0);
CHECK(strcmp(NibbleToBinary(0xfffffff0), "0000") == 0);
}
}

View File

@@ -0,0 +1,129 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/UnorderedMap.h>
TEST_SUITE("UnorderedMapTest")
{
TEST_CASE("TestUnorderedMap")
{
UnorderedMap<int, int> map;
map.reserve(10);
// Insert some entries
CHECK(map.insert({ 1, 2 }).first->first == 1);
CHECK(map.insert({ 3, 4 }).second);
CHECK(!map.insert({ 3, 5 }).second);
CHECK(map.size() == 2);
CHECK(map.find(1)->second == 2);
CHECK(map.find(3)->second == 4);
CHECK(map.find(5) == map.end());
// Use operator []
map[5] = 6;
CHECK(map.size() == 3);
CHECK(map.find(5)->second == 6);
map[5] = 7;
CHECK(map.size() == 3);
CHECK(map.find(5)->second == 7);
// Validate all elements are visited by a visitor
int count = 0;
bool visited[10] = { false };
for (UnorderedMap<int, int>::const_iterator i = map.begin(); i != map.end(); ++i)
{
visited[i->first] = true;
++count;
}
CHECK(count == 3);
CHECK(visited[1]);
CHECK(visited[3]);
CHECK(visited[5]);
for (UnorderedMap<int, int>::iterator i = map.begin(); i != map.end(); ++i)
{
visited[i->first] = false;
--count;
}
CHECK(count == 0);
CHECK(!visited[1]);
CHECK(!visited[3]);
CHECK(!visited[5]);
// Copy the map
UnorderedMap<int, int> map2;
map2 = map;
CHECK(map2.find(1)->second == 2);
CHECK(map2.find(3)->second == 4);
CHECK(map2.find(5)->second == 7);
CHECK(map2.find(7) == map2.end());
// Try emplace
map.try_emplace(7, 8);
CHECK(map.size() == 4);
CHECK(map.find(7)->second == 8);
// Swap
UnorderedMap<int, int> map3;
map3.swap(map);
CHECK(map3.find(1)->second == 2);
CHECK(map3.find(3)->second == 4);
CHECK(map3.find(5)->second == 7);
CHECK(map3.find(7)->second == 8);
CHECK(map3.find(9) == map3.end());
CHECK(map.empty());
// Move construct
UnorderedMap<int, int> map4(std::move(map3));
CHECK(map4.find(1)->second == 2);
CHECK(map4.find(3)->second == 4);
CHECK(map4.find(5)->second == 7);
CHECK(map4.find(7)->second == 8);
CHECK(map4.find(9) == map4.end());
CHECK(map3.empty());
}
TEST_CASE("TestUnorderedMapGrow")
{
UnorderedMap<int, int> map;
for (int i = 0; i < 10000; ++i)
CHECK(map.try_emplace(i, ~i).second);
CHECK(map.size() == 10000);
for (int i = 0; i < 10000; ++i)
CHECK(map.find(i)->second == ~i);
CHECK(map.find(10001) == map.end());
for (int i = 0; i < 5000; ++i)
CHECK(map.erase(i) == 1);
CHECK(map.size() == 5000);
for (int i = 0; i < 5000; ++i)
CHECK(map.find(i) == map.end());
for (int i = 5000; i < 10000; ++i)
CHECK(map.find(i)->second == ~i);
CHECK(map.find(10001) == map.end());
for (int i = 0; i < 5000; ++i)
CHECK(map.try_emplace(i, i + 1).second);
CHECK(!map.try_emplace(0, 0).second);
CHECK(map.size() == 10000);
for (int i = 0; i < 5000; ++i)
CHECK(map.find(i)->second == i + 1);
for (int i = 5000; i < 10000; ++i)
CHECK(map.find(i)->second == ~i);
CHECK(map.find(10001) == map.end());
}
}

View File

@@ -0,0 +1,333 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "UnitTestFramework.h"
#include <Jolt/Core/UnorderedSet.h>
TEST_SUITE("UnorderedSetTest")
{
TEST_CASE("TestUnorderedSet")
{
UnorderedSet<int> set;
CHECK(set.bucket_count() == 0);
set.reserve(10);
CHECK(set.bucket_count() == 16);
// Check system limits
CHECK(set.max_bucket_count() == 0x80000000);
CHECK(set.max_size() == uint64(0x80000000) * 7 / 8);
// Insert some entries
CHECK(*set.insert(1).first == 1);
CHECK(set.insert(3).second);
CHECK(!set.insert(3).second);
CHECK(set.size() == 2);
CHECK(*set.find(1) == 1);
CHECK(*set.find(3) == 3);
CHECK(set.find(5) == set.cend());
// Validate all elements are visited by a visitor
int count = 0;
bool visited[10] = { false };
for (UnorderedSet<int>::const_iterator i = set.begin(); i != set.end(); ++i)
{
visited[*i] = true;
++count;
}
CHECK(count == 2);
CHECK(visited[1]);
CHECK(visited[3]);
for (UnorderedSet<int>::iterator i = set.begin(); i != set.end(); ++i)
{
visited[*i] = false;
--count;
}
CHECK(count == 0);
CHECK(!visited[1]);
CHECK(!visited[3]);
// Copy the set
UnorderedSet<int> set2;
set2 = set;
CHECK(*set2.find(1) == 1);
CHECK(*set2.find(3) == 3);
CHECK(set2.find(5) == set2.cend());
// Swap
UnorderedSet<int> set3;
set3.swap(set);
CHECK(*set3.find(1) == 1);
CHECK(*set3.find(3) == 3);
CHECK(set3.find(5) == set3.end());
CHECK(set.empty());
// Move construct
UnorderedSet<int> set4(std::move(set3));
CHECK(*set4.find(1) == 1);
CHECK(*set4.find(3) == 3);
CHECK(set4.find(5) == set4.end());
CHECK(set3.empty());
// Move assign
UnorderedSet<int> set5;
set5.insert(999);
CHECK(*set5.find(999) == 999);
set5 = std::move(set4);
CHECK(set5.find(999) == set5.end());
CHECK(*set5.find(1) == 1);
CHECK(*set5.find(3) == 3);
CHECK(set4.empty());
}
TEST_CASE("TestUnorderedSetGrow")
{
UnorderedSet<int> set;
for (int i = 0; i < 10000; ++i)
CHECK(set.insert(i).second);
CHECK(set.size() == 10000);
for (int i = 0; i < 10000; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(10001) == set.cend());
for (int i = 0; i < 5000; ++i)
CHECK(set.erase(i) == 1);
CHECK(set.size() == 5000);
for (int i = 0; i < 5000; ++i)
CHECK(set.find(i) == set.end());
for (int i = 5000; i < 10000; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(10001) == set.cend());
for (int i = 0; i < 5000; ++i)
CHECK(set.insert(i).second);
CHECK(!set.insert(0).second);
CHECK(set.size() == 10000);
for (int i = 0; i < 10000; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(10001) == set.cend());
}
TEST_CASE("TestUnorderedSetHashCollision")
{
// A hash function that's guaranteed to collide
class MyBadHash
{
public:
size_t operator () (int inValue) const
{
return 0;
}
};
UnorderedSet<int, MyBadHash> set;
for (int i = 0; i < 10; ++i)
CHECK(set.insert(i).second);
CHECK(set.size() == 10);
for (int i = 0; i < 10; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(11) == set.cend());
for (int i = 0; i < 5; ++i)
CHECK(set.erase(i) == 1);
CHECK(set.size() == 5);
for (int i = 0; i < 5; ++i)
CHECK(set.find(i) == set.end());
for (int i = 5; i < 10; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(11) == set.cend());
for (int i = 0; i < 5; ++i)
CHECK(set.insert(i).second);
CHECK(!set.insert(0).second);
CHECK(set.size() == 10);
for (int i = 0; i < 10; ++i)
CHECK(*set.find(i) == i);
CHECK(set.find(11) == set.cend());
}
TEST_CASE("TestUnorderedSetAddRemoveCyles")
{
UnorderedSet<int> set;
constexpr int cBucketCount = 64;
set.reserve(int(set.max_load_factor() * cBucketCount));
CHECK(set.bucket_count() == cBucketCount);
// Repeatedly add and remove elements to see if the set cleans up tombstones
constexpr int cNumElements = 64 * 6 / 8; // We make sure that the map is max 6/8 full to ensure that we never grow the map but rehash it instead
int add_counter = 0;
int remove_counter = 0;
for (int i = 0; i < 100; ++i)
{
for (int j = 0; j < cNumElements; ++j)
{
CHECK(set.find(add_counter) == set.end());
CHECK(set.insert(add_counter).second);
CHECK(set.find(add_counter) != set.end());
++add_counter;
}
CHECK(set.size() == cNumElements);
for (int j = 0; j < cNumElements; ++j)
{
CHECK(set.find(remove_counter) != set.end());
CHECK(set.erase(remove_counter) == 1);
CHECK(set.erase(remove_counter) == 0);
CHECK(set.find(remove_counter) == set.end());
++remove_counter;
}
CHECK(set.size() == 0);
CHECK(set.empty());
}
// Test that adding and removing didn't resize the set
CHECK(set.bucket_count() == cBucketCount);
}
TEST_CASE("TestUnorderedSetManyTombStones")
{
// A hash function that makes sure that consecutive ints end up in consecutive buckets starting at bucket 63
class MyBadHash
{
public:
size_t operator () (int inValue) const
{
return (inValue + 63) << 7;
}
};
UnorderedSet<int, MyBadHash> set;
constexpr int cBucketCount = 64;
set.reserve(int(set.max_load_factor() * cBucketCount));
CHECK(set.bucket_count() == cBucketCount);
// Fill 32 buckets
int add_counter = 0;
for (int i = 0; i < 32; ++i)
CHECK(set.insert(add_counter++).second);
// Since we control the hash, we know in which order we'll visit the elements
// The first element was inserted in bucket 63, so we start at 1
int expected = 1;
for (int i : set)
{
CHECK(i == expected);
expected = (expected + 1) & 31;
}
expected = 1;
for (int i : set)
{
CHECK(i == expected);
expected = (expected + 1) & 31;
}
// Remove a bucket in the middle with so that the number of occupied slots
// surrounding the bucket exceed 16 to force creating a tombstone,
// then add one at the end
int remove_counter = 16;
for (int i = 0; i < 100; ++i)
{
CHECK(set.find(remove_counter) != set.end());
CHECK(set.erase(remove_counter) == 1);
CHECK(set.find(remove_counter) == set.end());
CHECK(set.find(add_counter) == set.end());
CHECK(set.insert(add_counter).second);
CHECK(set.find(add_counter) != set.end());
++add_counter;
++remove_counter;
}
// Check that the elements we inserted are still there
CHECK(set.size() == 32);
for (int i = 0; i < 16; ++i)
CHECK(*set.find(i) == i);
for (int i = 0; i < 16; ++i)
CHECK(*set.find(add_counter - 1 - i) == add_counter - 1 - i);
// Test that adding and removing didn't resize the set
CHECK(set.bucket_count() == cBucketCount);
}
static bool sReversedHash = false;
TEST_CASE("TestUnorderedSetRehash")
{
// A hash function for which we can switch the hashing algorithm
class MyBadHash
{
public:
size_t operator () (int inValue) const
{
return (sReversedHash? 127 - inValue : inValue) << 7;
}
};
using Set = UnorderedSet<int, MyBadHash>;
Set set;
constexpr int cBucketCount = 128;
set.reserve(int(set.max_load_factor() * cBucketCount));
CHECK(set.bucket_count() == cBucketCount);
// Fill buckets
sReversedHash = false;
constexpr int cNumElements = 96;
for (int i = 0; i < cNumElements; ++i)
CHECK(set.insert(i).second);
// Check that we get the elements in the expected order
int expected = 0;
for (int i : set)
CHECK(i == expected++);
// Change the hashing algorithm so that a rehash is forced to move elements.
// The test is designed in such a way that it will both need to move elements to empty slots
// and to move elements to slots that currently already have another element.
sReversedHash = true;
set.rehash(0);
// Check that all elements are still there
for (int i = 0; i < cNumElements; ++i)
CHECK(*set.find(i) == i);
// The hash went from filling buckets 0 .. 95 with values 0 .. 95 to bucket 127 .. 31 with values 0 .. 95
// However, we don't move elements if they still fall within the same batch, this means that the first 8
// elements didn't move
Set::const_iterator it = set.begin();
for (int i = 0; i < 8; ++i, ++it)
CHECK(*it == i);
// The rest will have been reversed
for (int i = 95; i > 7; --i, ++it)
CHECK(*it == i);
// Test that adding and removing didn't resize the set
CHECK(set.bucket_count() == cBucketCount);
}
}