STLLocalAllocatorTest.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2025 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include <Jolt/Core/STLLocalAllocator.h>
  6. TEST_SUITE("STLLocalAllocatorTest")
  7. {
  8. /// The number of elements in the local buffer
  9. static constexpr size_t N = 20;
  10. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  11. template <class ArrayType>
  12. static bool sIsLocal(ArrayType &inArray)
  13. {
  14. #ifdef JPH_USE_STD_VECTOR
  15. // Check that the data pointer is within the array.
  16. // Note that when using std::vector we cannot use get_allocator as that makes a copy of the allocator internally
  17. // and we've disabled the copy constructor since our allocator cannot be copied.
  18. const uint8 *data = reinterpret_cast<const uint8 *>(inArray.data());
  19. const uint8 *array = reinterpret_cast<const uint8 *>(&inArray);
  20. return data >= array && data < array + sizeof(inArray);
  21. #else
  22. return inArray.get_allocator().is_local(inArray.data());
  23. #endif
  24. }
  25. #endif
  26. template <class ArrayType, bool NonTrivial>
  27. static void sTestArray()
  28. {
  29. // Allocate so that we will run out of local memory and reallocate from heap at least once
  30. ArrayType arr;
  31. for (int i = 0; i < 64; ++i)
  32. arr.push_back(i);
  33. CHECK(arr.size() == 64);
  34. for (int i = 0; i < 64; ++i)
  35. {
  36. CHECK(arr[i] == i);
  37. #if !defined(JPH_USE_STD_VECTOR) && !defined(JPH_DISABLE_CUSTOM_ALLOCATOR)
  38. // We only have to move elements once we run out of the local buffer, this happens as we resize
  39. // from 16 to 32 elements, we'll reallocate again at 32 and 64
  40. if constexpr (NonTrivial)
  41. CHECK(arr[i].GetNonTriv() == (i < 16? 3 : (i < 32? 2 : 1)));
  42. #endif
  43. }
  44. CHECK(IsAligned(arr.data(), alignof(typename ArrayType::value_type)));
  45. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  46. CHECK(!sIsLocal(arr));
  47. #endif
  48. // Check that we can copy the array to another array
  49. ArrayType arr2;
  50. arr2 = arr;
  51. for (int i = 0; i < 64; ++i)
  52. {
  53. CHECK(arr2[i] == i);
  54. if constexpr (NonTrivial)
  55. CHECK(arr2[i].GetNonTriv() == -999);
  56. }
  57. CHECK(IsAligned(arr2.data(), alignof(typename ArrayType::value_type)));
  58. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  59. CHECK(!sIsLocal(arr2));
  60. #endif
  61. // Clear the array
  62. arr.clear();
  63. arr.shrink_to_fit();
  64. CHECK(arr.size() == 0);
  65. #ifndef JPH_USE_STD_VECTOR // Some implementations of std::vector ignore shrink_to_fit
  66. CHECK(arr.capacity() == 0);
  67. CHECK(arr.data() == nullptr);
  68. #endif
  69. // Allocate so we stay within the local buffer
  70. for (int i = 0; i < 10; ++i)
  71. arr.push_back(i);
  72. CHECK(arr.size() == 10);
  73. for (int i = 0; i < 10; ++i)
  74. {
  75. CHECK(arr[i] == i);
  76. #if !defined(JPH_USE_STD_VECTOR) && !defined(JPH_DISABLE_CUSTOM_ALLOCATOR)
  77. // We never need to move elements as they stay within the local buffer
  78. if constexpr (NonTrivial)
  79. CHECK(arr[i].GetNonTriv() == 1);
  80. #endif
  81. }
  82. CHECK(IsAligned(arr.data(), alignof(typename ArrayType::value_type)));
  83. #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
  84. CHECK(sIsLocal(arr));
  85. #endif
  86. // Check that we can copy the array to the local buffer
  87. ArrayType arr3;
  88. arr3 = arr;
  89. CHECK(arr3.size() == 10);
  90. for (int i = 0; i < 10; ++i)
  91. {
  92. CHECK(arr3[i] == i);
  93. if constexpr (NonTrivial)
  94. CHECK(arr3[i].GetNonTriv() == -999);
  95. }
  96. CHECK(IsAligned(arr3.data(), alignof(typename ArrayType::value_type)));
  97. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  98. CHECK(sIsLocal(arr3));
  99. #endif
  100. // Check that if we reserve the memory, that we can fully fill the array in local memory
  101. ArrayType arr4;
  102. arr4.reserve(N);
  103. for (int i = 0; i < int(N); ++i)
  104. arr4.push_back(i);
  105. CHECK(arr4.size() == N);
  106. CHECK(arr4.capacity() == N);
  107. for (int i = 0; i < int(N); ++i)
  108. {
  109. CHECK(arr4[i] == i);
  110. if constexpr (NonTrivial)
  111. CHECK(arr4[i].GetNonTriv() == 1);
  112. }
  113. CHECK(IsAligned(arr4.data(), alignof(typename ArrayType::value_type)));
  114. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  115. CHECK(sIsLocal(arr4));
  116. #endif
  117. }
  118. TEST_CASE("TestAllocation")
  119. {
  120. using Allocator = STLLocalAllocator<int, N>;
  121. using ArrayType = Array<int, Allocator>;
  122. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  123. static_assert(AllocatorHasReallocate<Allocator>::sValue);
  124. #endif
  125. sTestArray<ArrayType, false>();
  126. }
  127. TEST_CASE("TestAllocationAligned")
  128. {
  129. // Force the need for an aligned allocation
  130. struct alignas(64) Aligned
  131. {
  132. Aligned(int inValue) : mValue(inValue) { }
  133. operator int() const { return mValue; }
  134. private:
  135. int mValue;
  136. };
  137. static_assert(std::is_trivially_copyable<Aligned>());
  138. using Allocator = STLLocalAllocator<Aligned, N>;
  139. using ArrayType = Array<Aligned, Allocator>;
  140. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  141. static_assert(AllocatorHasReallocate<Allocator>::sValue);
  142. #endif
  143. sTestArray<ArrayType, false>();
  144. }
  145. TEST_CASE("TestAllocationNonTrivial")
  146. {
  147. // Force non trivial copy constructor
  148. struct NonTriv
  149. {
  150. NonTriv(int inValue) : mValue(inValue) { }
  151. NonTriv(const NonTriv &inRHS) : mValue(inRHS.mValue), mMakeNonTriv(-999) { }
  152. NonTriv(NonTriv &&inRHS) : mValue(inRHS.mValue), mMakeNonTriv(inRHS.mMakeNonTriv + 1) { }
  153. NonTriv & operator = (const NonTriv &inRHS) { mValue = inRHS.mValue; mMakeNonTriv = -9999; return *this; }
  154. operator int() const { return mValue; }
  155. int GetNonTriv() const { return mMakeNonTriv; }
  156. private:
  157. int mValue;
  158. int mMakeNonTriv = 0;
  159. };
  160. static_assert(!std::is_trivially_copyable<NonTriv>());
  161. using Allocator = STLLocalAllocator<NonTriv, N>;
  162. using ArrayType = Array<NonTriv, Allocator>;
  163. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  164. static_assert(AllocatorHasReallocate<Allocator>::sValue);
  165. #endif
  166. sTestArray<ArrayType, true>();
  167. }
  168. TEST_CASE("TestAllocationAlignedNonTrivial")
  169. {
  170. // Force non trivial copy constructor
  171. struct alignas(64) AlNonTriv
  172. {
  173. AlNonTriv(int inValue) : mValue(inValue) { }
  174. AlNonTriv(const AlNonTriv &inRHS) : mValue(inRHS.mValue), mMakeNonTriv(-999) { }
  175. AlNonTriv(AlNonTriv &&inRHS) : mValue(inRHS.mValue), mMakeNonTriv(inRHS.mMakeNonTriv + 1) { }
  176. AlNonTriv & operator = (const AlNonTriv &inRHS) { mValue = inRHS.mValue; mMakeNonTriv = -9999; return *this; }
  177. operator int() const { return mValue; }
  178. int GetNonTriv() const { return mMakeNonTriv; }
  179. private:
  180. int mValue;
  181. int mMakeNonTriv = 0;
  182. };
  183. static_assert(!std::is_trivially_copyable<AlNonTriv>());
  184. using Allocator = STLLocalAllocator<AlNonTriv, N>;
  185. using ArrayType = Array<AlNonTriv, Allocator>;
  186. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  187. static_assert(AllocatorHasReallocate<Allocator>::sValue);
  188. #endif
  189. sTestArray<ArrayType, true>();
  190. }
  191. }