Browse Source

Replace std::vector with custom implementation (#1089)

std::vector memsets the entire data block to 0 before doing anything else and there doesn't appear to be a reasonable way around it. This was actually costing quite a bit of time when deserializing big meshes/height fields. This change replaces std::vector with a custom Array class, but it can still be turned off by enabling the JPH_USE_STD_VECTOR define (or the USE_STD_VECTOR cmake option).

This change also adds a Reallocate function that needs to be implemented when you override the memory allocators and a reallocate function that you need to implement if you have a custom array allocator. The behavior is the same as the C realloc function. It is used to reallocate a block of memory for simple types instead of always going through a alloc, copy, free cycle.
Jorrit Rouwe 1 year ago
parent
commit
bdc1695a64
44 changed files with 1446 additions and 162 deletions
  1. 21 0
      .github/workflows/build.yml
  2. 3 0
      Build/CMakeLists.txt
  3. 567 0
      Jolt/Core/Array.h
  4. 1 1
      Jolt/Core/ByteBuffer.h
  5. 0 1
      Jolt/Core/Core.h
  6. 1 1
      Jolt/Core/LinearCurve.cpp
  7. 7 0
      Jolt/Core/Memory.cpp
  8. 3 0
      Jolt/Core/Memory.h
  9. 1 1
      Jolt/Core/Profiler.cpp
  10. 12 0
      Jolt/Core/STLAlignedAllocator.h
  11. 18 1
      Jolt/Core/STLAllocator.h
  12. 12 0
      Jolt/Core/STLTempAllocator.h
  13. 2 0
      Jolt/Core/StaticArray.h
  14. 6 6
      Jolt/Core/StreamIn.h
  15. 6 6
      Jolt/Core/StreamOut.h
  16. 3 4
      Jolt/Geometry/ConvexHullBuilder.cpp
  17. 1 2
      Jolt/Geometry/ConvexHullBuilder2D.cpp
  18. 5 0
      Jolt/Jolt.cmake
  19. 1 1
      Jolt/Jolt.h
  20. 94 83
      Jolt/Jolt.natvis
  21. 4 2
      Jolt/Math/Vec3.cpp
  22. 1 1
      Jolt/Math/Vec3.h
  23. 2 2
      Jolt/ObjectStream/GetPrimitiveTypeOfType.h
  24. 8 8
      Jolt/ObjectStream/ObjectStream.h
  25. 1 1
      Jolt/Physics/Body/BodyFilter.h
  26. 1 1
      Jolt/Physics/Body/BodyManager.cpp
  27. 3 3
      Jolt/Physics/Character/CharacterVirtual.cpp
  28. 3 3
      Jolt/Physics/Character/CharacterVirtual.h
  29. 1 1
      Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp
  30. 6 6
      Jolt/Physics/Collision/Shape/CapsuleShape.cpp
  31. 2 2
      Jolt/Physics/Collision/Shape/ConvexShape.cpp
  32. 1 1
      Jolt/Physics/Collision/Shape/ConvexShape.h
  33. 2 2
      Jolt/Physics/Collision/Shape/CylinderShape.cpp
  34. 4 4
      Jolt/Physics/Collision/Shape/GetTrianglesContext.h
  35. 6 9
      Jolt/Physics/Collision/Shape/HeightFieldShape.cpp
  36. 2 2
      Jolt/Physics/PhysicsSystem.cpp
  37. 1 1
      Jolt/Physics/PhysicsUpdateContext.h
  38. 1 1
      Jolt/RegisterTypes.cpp
  39. 2 2
      Jolt/Skeleton/SkeletonMapper.cpp
  40. 2 2
      Jolt/TriangleGrouper/TriangleGrouperClosestCentroid.cpp
  41. 7 0
      TestFramework/Utils/CustomMemoryHook.cpp
  42. 620 0
      UnitTests/Core/ArrayTest.cpp
  43. 1 1
      UnitTests/Physics/PhysicsTests.cpp
  44. 1 0
      UnitTests/UnitTests.cmake

+ 21 - 0
.github/workflows/build.yml

@@ -78,6 +78,27 @@ jobs:
       working-directory: ${{github.workspace}}/Build/Linux_${{matrix.build_type}}_${{matrix.clang_version}}
       run: ctest --output-on-failure --verbose
 
+  linux-clang-use-std-vector:
+    runs-on: ubuntu-latest
+    name: Linux Clang using std::vector
+    strategy:
+        fail-fast: false
+        matrix:
+            build_type: [Debug, ReleaseASAN]
+            clang_version: [clang++-14]
+            double_precision: [Yes]
+
+    steps:
+    - name: Checkout Code
+      uses: actions/checkout@v4
+    - name: Configure CMake
+      run: cmake -B ${{github.workspace}}/Build/Linux_${{matrix.build_type}}_${{matrix.clang_version}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_CXX_COMPILER=${{matrix.clang_version}} -DDOUBLE_PRECISION=${{matrix.double_precision}} -DUSE_STD_VECTOR=ON Build
+    - name: Build
+      run: cmake --build ${{github.workspace}}/Build/Linux_${{matrix.build_type}}_${{matrix.clang_version}} -j 2
+    - name: Test
+      working-directory: ${{github.workspace}}/Build/Linux_${{matrix.build_type}}_${{matrix.clang_version}}
+      run: ctest --output-on-failure --verbose
+
   linux-gcc:
     runs-on: ubuntu-latest
     name: Linux GCC

+ 3 - 0
Build/CMakeLists.txt

@@ -73,6 +73,9 @@ option(PROFILER_IN_DISTRIBUTION "Enable the profiler in all builds" OFF)
 # Setting this option will force the library to use malloc/free instead of allowing the user to override the memory allocator
 option(DISABLE_CUSTOM_ALLOCATOR "Disable support for a custom memory allocator" OFF)
 
+# Setting this option will force the library to use the STL vector instead of the custom Array class
+option(USE_STD_VECTOR "Use std::vector instead of own Array class" OFF)
+
 include(CMakeDependentOption)
 
 # Ability to toggle between the static and DLL versions of the MSVC runtime library

+ 567 - 0
Jolt/Core/Array.h

@@ -0,0 +1,567 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Core/STLAllocator.h>
+#include <Jolt/Core/HashCombine.h>
+
+#ifdef JPH_USE_STD_VECTOR
+
+JPH_SUPPRESS_WARNINGS_STD_BEGIN
+#include <vector>
+JPH_SUPPRESS_WARNINGS_STD_END
+
+JPH_NAMESPACE_BEGIN
+
+template <class T, class Allocator = STLAllocator<T>> using Array = std::vector<T, Allocator>;
+
+JPH_NAMESPACE_END
+
+#else
+
+JPH_NAMESPACE_BEGIN
+
+/// Simple replacement for std::vector
+///
+/// Major differences:
+/// - Memory is not initialized to zero (this was causing a lot of page faults when deserializing large MeshShapes / HeightFieldShapes)
+/// - Iterators are simple pointers (for now)
+/// - No exception safety
+/// - Not all functions have been implemented
+template <class T, class Allocator = STLAllocator<T>>
+class [[nodiscard]] Array : private Allocator
+{
+public:
+	using value_type = T;
+	using size_type = size_t;
+	using pointer = T *;
+	using const_pointer = const T *;
+	using reference = T &;
+	using const_reference = const T &;
+
+	using const_iterator = const T *;
+	using iterator = T *;
+
+private:
+	/// Move elements from one location to another
+	inline void				move(pointer inDestination, pointer inSource, size_type inCount)
+	{
+		if constexpr (std::is_trivially_copyable<T>())
+			memmove(inDestination, inSource, inCount * sizeof(T));
+		else
+		{
+			if (inDestination < inSource)
+			{
+				for (T *destination_end = inDestination + inCount; inDestination < destination_end; ++inDestination, ++inSource)
+				{
+					::new (inDestination) T(std::move(*inSource));
+					inSource->~T();
+				}
+			}
+			else
+			{
+				for (T *destination = inDestination + inCount - 1, *source = inSource + inCount - 1; destination >= inDestination; --destination, --source)
+				{
+					::new (destination) T(std::move(*source));
+					source->~T();
+				}
+			}
+		}
+	}
+
+	/// Destruct elements [inStart, inEnd - 1]
+	inline void				destruct(size_type inStart, size_type inEnd)
+	{
+		if constexpr (!is_trivially_destructible<T>())
+			if (inStart < inEnd)
+				for (T *element = mElements + inStart, *element_end = mElements + inEnd; element < element_end; ++element)
+					element->~T();
+	}
+
+public:
+	/// Reserve array space
+	inline void				reserve(size_type inNewSize)
+	{
+		if (mCapacity < inNewSize)
+		{
+			pointer pointer;
+			if constexpr (std::is_trivially_copyable<T>() && !std::is_same<Allocator, std::allocator<T>>())
+			{
+				pointer = get_allocator().reallocate(mElements, mCapacity, inNewSize);
+			}
+			else
+			{
+				pointer = get_allocator().allocate(inNewSize);
+				if (mElements != nullptr)
+				{
+					move(pointer, mElements, mSize);
+					get_allocator().deallocate(mElements, mCapacity);
+				}
+			}
+			mElements = pointer;
+			mCapacity = inNewSize;
+		}
+	}
+
+	/// Resize array to new length
+	inline void				resize(size_type inNewSize)
+	{
+		destruct(inNewSize, mSize);
+		reserve(inNewSize);
+
+		if constexpr (!is_trivially_constructible<T>())
+			for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
+				::new (element) T;
+		mSize = inNewSize;
+	}
+
+	/// Resize array to new length and initialize all elements with inValue
+	inline void				resize(size_type inNewSize, const T &inValue)
+	{
+		JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to resize");
+
+		destruct(inNewSize, mSize);
+		reserve(inNewSize);
+
+		for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
+			::new (element) T(inValue);
+		mSize = inNewSize;
+	}
+
+	/// Destruct all elements and set length to zero
+	inline void				clear()
+	{
+		destruct(0, mSize);
+		mSize = 0;
+	}
+
+private:
+	/// Grow the array by at least inAmount elements
+	inline void				grow(size_type inAmount = 1)
+	{
+		size_type min_size = mSize + inAmount;
+		if (min_size > mCapacity)
+		{
+			size_type new_capacity = max(min_size, mCapacity * 2);
+			reserve(new_capacity);
+		}
+	}
+
+	/// Destroy all elements and free memory
+	inline void				destroy()
+	{
+		if (mElements != nullptr)
+		{
+			clear();
+			get_allocator().deallocate(mElements, mCapacity);
+			mElements = nullptr;
+			mCapacity = 0;
+		}
+	}
+
+public:
+	/// Replace the contents of this array with inBegin .. inEnd
+	template <class Iterator>
+	inline void				assign(Iterator inBegin, Iterator inEnd)
+	{
+		clear();
+		reserve(size_type(std::distance(inBegin, inEnd)));
+
+		for (Iterator element = inBegin; element != inEnd; ++element)
+			::new (&mElements[mSize++]) T(*element);
+	}
+
+	/// Replace the contents of this array with inList
+	inline void				assign(std::initializer_list<T> inList)
+	{
+		clear();
+		reserve(size_type(inList.size()));
+
+		for (typename std::initializer_list<T>::iterator i = inList.begin(); i != inList.end(); ++i)
+			::new (&mElements[mSize++]) T(*i);
+	}
+
+	/// Default constructor
+							Array() = default;
+
+	/// Constructor with allocator
+	explicit inline			Array(const Allocator &inAllocator) :
+		Allocator(inAllocator)
+	{
+	}
+
+	/// Constructor with length
+	explicit inline			Array(size_type inLength, const Allocator &inAllocator = { }) :
+		Allocator(inAllocator)
+	{
+		resize(inLength);
+	}
+
+	/// Constructor with length and value
+	inline					Array(size_type inLength, const T &inValue, const Allocator &inAllocator = { }) :
+		Allocator(inAllocator)
+	{
+		resize(inLength, inValue);
+	}
+
+	/// Constructor from initializer list
+	inline					Array(std::initializer_list<T> inList, const Allocator &inAllocator = { }) :
+		Allocator(inAllocator)
+	{
+		assign(inList);
+	}
+
+	/// Constructor from iterator
+	inline					Array(const_iterator inBegin, const_iterator inEnd, const Allocator &inAllocator = { }) :
+		Allocator(inAllocator)
+	{
+		assign(inBegin, inEnd);
+	}
+
+	/// Copy constructor
+	inline					Array(const Array<T, Allocator> &inRHS) :
+		Allocator(inRHS.get_allocator())
+	{
+		assign(inRHS.begin(), inRHS.end());
+	}
+
+	/// Move constructor
+	inline					Array(Array<T, Allocator> &&inRHS) noexcept
+	{
+		destroy();
+
+		get_allocator() = std::move(inRHS.get_allocator());
+
+		mSize = inRHS.mSize;
+		mCapacity = inRHS.mCapacity;
+		mElements = inRHS.mElements;
+
+		inRHS.mSize = 0;
+		inRHS.mCapacity = 0;
+		inRHS.mElements = nullptr;
+	}
+
+	/// Destruct all elements
+	inline					~Array()
+	{
+		destroy();
+	}
+
+	/// Get the allocator
+	inline Allocator &		get_allocator()
+	{
+		return *this;
+	}
+
+	inline const Allocator &get_allocator() const
+	{
+		return *this;
+	}
+
+	/// Add element to the back of the array
+	inline void				push_back(const T &inValue)
+	{
+		JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to push_back");
+
+		grow();
+
+		T *element = mElements + mSize++;
+		::new (element) T(inValue);
+	}
+
+	inline void				push_back(T &&inValue)
+	{
+		grow();
+
+		T *element = mElements + mSize++;
+		::new (element) T(std::move(inValue));
+	}
+
+	/// Construct element at the back of the array
+	template <class... A>
+	inline T &				emplace_back(A &&... inValue)
+	{
+		grow();
+
+		T *element = mElements + mSize++;
+		::new (element) T(std::forward<A>(inValue)...);
+		return *element;
+	}
+
+	/// Remove element from the back of the array
+	inline void				pop_back()
+	{
+		JPH_ASSERT(mSize > 0);
+		mElements[--mSize].~T();
+	}
+
+	/// Returns true if there are no elements in the array
+	inline bool				empty() const
+	{
+		return mSize == 0;
+	}
+
+	/// Returns amount of elements in the array
+	inline size_type		size() const
+	{
+		return mSize;
+	}
+
+	/// Returns maximum amount of elements the array can hold
+	inline size_type		capacity() const
+	{
+		return mCapacity;
+	}
+
+	/// Reduce the capacity of the array to match its size
+	void					shrink_to_fit()
+	{
+		if (mCapacity > mSize)
+		{
+			pointer pointer = get_allocator().allocate(mSize);
+			move(pointer, mElements, mSize);
+			get_allocator().deallocate(mElements, mCapacity);
+			mElements = pointer;
+			mCapacity = mSize;
+		}
+	}
+
+	/// Swap the contents of two arrays
+	void					swap(Array<T, Allocator> &inRHS) noexcept
+	{
+		std::swap(get_allocator(), inRHS.get_allocator());
+		std::swap(mSize, inRHS.mSize);
+		std::swap(mCapacity, inRHS.mCapacity);
+		std::swap(mElements, inRHS.mElements);
+	}
+
+	template <class Iterator>
+	void					insert(const_iterator inPos, Iterator inBegin, Iterator inEnd)
+	{
+		size_type num_elements = size_type(std::distance(inBegin, inEnd));
+		if (num_elements > 0)
+		{
+			// After grow() inPos may be invalid
+			size_type first_element = inPos - mElements;
+
+			grow(num_elements);
+
+			T *element_begin = mElements + first_element;
+			T *element_end = element_begin + num_elements;
+			move(element_end, element_begin, mSize - first_element);
+
+			for (T *element = element_begin; element < element_end; ++element, ++inBegin)
+				::new (element) T(*inBegin);
+
+			mSize += num_elements;
+		}
+	}
+
+	void					insert(const_iterator inPos, const T &inValue)
+	{
+		JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to insert");
+
+		// After grow() inPos may be invalid
+		size_type first_element = inPos - mElements;
+
+		grow();
+
+		T *element = mElements + first_element;
+		move(element + 1, element, mSize - first_element);
+
+		::new (element) T(inValue);
+		mSize++;
+	}
+
+	/// Remove one element from the array
+	void					erase(const_iterator inIter)
+	{
+		size_type p = size_type(inIter - begin());
+		JPH_ASSERT(p < mSize);
+		mElements[p].~T();
+		if (p + 1 < mSize)
+			move(mElements + p, mElements + p + 1, mSize - p - 1);
+		--mSize;
+	}
+
+	/// Remove multiple element from the array
+	void					erase(const_iterator inBegin, const_iterator inEnd)
+	{
+		size_type p = size_type(inBegin - begin());
+		size_type n = size_type(inEnd - inBegin);
+		JPH_ASSERT(inEnd <= end());
+		destruct(p, p + n);
+		if (p + n < mSize)
+			move(mElements + p, mElements + p + n, mSize - p - n);
+		mSize -= n;
+	}
+
+	/// Iterators
+	inline const_iterator	begin() const
+	{
+		return mElements;
+	}
+
+	inline const_iterator	end() const
+	{
+		return mElements + mSize;
+	}
+
+	inline const_iterator	cbegin() const
+	{
+		return mElements;
+	}
+
+	inline const_iterator	cend() const
+	{
+		return mElements + mSize;
+	}
+
+	inline iterator			begin()
+	{
+		return mElements;
+	}
+
+	inline iterator			end()
+	{
+		return mElements + mSize;
+	}
+
+	inline const T *		data() const
+	{
+		return mElements;
+	}
+
+	inline T *				data()
+	{
+		return mElements;
+	}
+
+	/// Access element
+	inline T &				operator [] (size_type inIdx)
+	{
+		JPH_ASSERT(inIdx < mSize);
+		return mElements[inIdx];
+	}
+
+	inline const T &		operator [] (size_type inIdx) const
+	{
+		JPH_ASSERT(inIdx < mSize);
+		return mElements[inIdx];
+	}
+
+	/// Access element
+	inline T &				at(size_type inIdx)
+	{
+		JPH_ASSERT(inIdx < mSize);
+		return mElements[inIdx];
+	}
+
+	inline const T &		at(size_type inIdx) const
+	{
+		JPH_ASSERT(inIdx < mSize);
+		return mElements[inIdx];
+	}
+
+	/// First element in the array
+	inline const T &		front() const
+	{
+		JPH_ASSERT(mSize > 0);
+		return mElements[0];
+	}
+
+	inline T &				front()
+	{
+		JPH_ASSERT(mSize > 0);
+		return mElements[0];
+	}
+
+	/// Last element in the array
+	inline const T &		back() const
+	{
+		JPH_ASSERT(mSize > 0);
+		return mElements[mSize - 1];
+	}
+
+	inline T &				back()
+	{
+		JPH_ASSERT(mSize > 0);
+		return mElements[mSize - 1];
+	}
+
+	/// Assignment operator
+	Array<T, Allocator> &	operator = (const Array<T, Allocator> &inRHS)
+	{
+		if (static_cast<const void *>(this) != static_cast<const void *>(&inRHS))
+			assign(inRHS.begin(), inRHS.end());
+
+		return *this;
+	}
+
+	/// Assignment operator
+	Array<T, Allocator> &	operator = (std::initializer_list<T> inRHS)
+	{
+		assign(inRHS);
+
+		return *this;
+	}
+
+	/// Comparing arrays
+	bool					operator == (const Array<T, Allocator> &inRHS) const
+	{
+		if (mSize != inRHS.mSize)
+			return false;
+		for (size_type i = 0; i < mSize; ++i)
+			if (!(mElements[i] == inRHS.mElements[i]))
+				return false;
+		return true;
+	}
+
+	bool					operator != (const Array<T, Allocator> &inRHS) const
+	{
+		if (mSize != inRHS.mSize)
+			return true;
+		for (size_type i = 0; i < mSize; ++i)
+			if (mElements[i] != inRHS.mElements[i])
+				return true;
+		return false;
+	}
+
+private:
+	size_type				mSize = 0;
+	size_type				mCapacity = 0;
+	T *						mElements = nullptr;
+};
+
+JPH_NAMESPACE_END
+
+JPH_SUPPRESS_WARNING_PUSH
+JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
+
+namespace std
+{
+	/// Declare std::hash for Array
+	template <class T, class Allocator>
+	struct hash<JPH::Array<T, Allocator>>
+	{
+		size_t operator () (const JPH::Array<T, Allocator> &inRHS) const
+		{
+			std::size_t ret = 0;
+
+			// Hash length first
+            JPH::HashCombine(ret, inRHS.size());
+
+			// Then hash elements
+			for (const T &t : inRHS)
+	            JPH::HashCombine(ret, t);
+
+            return ret;
+		}
+	};
+}
+
+JPH_SUPPRESS_WARNING_POP
+
+#endif // JPH_USE_STD_VECTOR

+ 1 - 1
Jolt/Core/ByteBuffer.h

@@ -9,7 +9,7 @@
 JPH_NAMESPACE_BEGIN
 
 /// Underlying data type for ByteBuffer
-using ByteBufferVector = std::vector<uint8, STLAlignedAllocator<uint8, JPH_CACHE_LINE_SIZE>>;
+using ByteBufferVector = Array<uint8, STLAlignedAllocator<uint8, JPH_CACHE_LINE_SIZE>>;
 
 /// Simple byte buffer, aligned to a cache line
 class ByteBuffer : public ByteBufferVector

+ 0 - 1
Jolt/Core/Core.h

@@ -394,7 +394,6 @@ JPH_SUPPRESS_WARNINGS_STD_BEGIN
 #include <float.h>
 #include <limits.h>
 #include <string.h>
-#include <vector>
 #include <utility>
 #include <cmath>
 #include <sstream>

+ 1 - 1
Jolt/Core/LinearCurve.cpp

@@ -27,7 +27,7 @@ float LinearCurve::GetValue(float inX) const
 	if (mPoints.empty())
 		return 0.0f;
 
-	Points::const_iterator i2 = lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; });
+	Points::const_iterator i2 = std::lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; });
 
 	if (i2 == mPoints.begin())
 		return mPoints.front().mY;

+ 7 - 0
Jolt/Core/Memory.cpp

@@ -24,6 +24,11 @@ JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Allocate)(size_t inSize)
 	return malloc(inSize);
 }
 
+JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Reallocate)(void *inBlock, size_t inSize)
+{
+	return realloc(inBlock, inSize);
+}
+
 JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock)
 {
 	free(inBlock);
@@ -57,6 +62,7 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(AlignedFree)(void *inBlock)
 #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
 
 AllocateFunction Allocate = nullptr;
+ReallocateFunction Reallocate = nullptr;
 FreeFunction Free = nullptr;
 AlignedAllocateFunction AlignedAllocate = nullptr;
 AlignedFreeFunction AlignedFree = nullptr;
@@ -64,6 +70,7 @@ AlignedFreeFunction AlignedFree = nullptr;
 void RegisterDefaultAllocator()
 {
 	Allocate = AllocateImpl;
+	Reallocate = ReallocateImpl;
 	Free = FreeImpl;
 	AlignedAllocate = AlignedAllocateImpl;
 	AlignedFree = AlignedFreeImpl;

+ 3 - 0
Jolt/Core/Memory.h

@@ -10,6 +10,7 @@ JPH_NAMESPACE_BEGIN
 
 // Normal memory allocation, must be at least 8 byte aligned on 32 bit platform and 16 byte aligned on 64 bit platform
 using AllocateFunction = void *(*)(size_t inSize);
+using ReallocateFunction = void *(*)(void *inBlock, size_t inSize);
 using FreeFunction = void (*)(void *inBlock);
 
 // Aligned memory allocation
@@ -18,6 +19,7 @@ using AlignedFreeFunction = void (*)(void *inBlock);
 
 // User defined allocation / free functions
 JPH_EXPORT extern AllocateFunction Allocate;
+JPH_EXPORT extern ReallocateFunction Reallocate;
 JPH_EXPORT extern FreeFunction Free;
 JPH_EXPORT extern AlignedAllocateFunction AlignedAllocate;
 JPH_EXPORT extern AlignedFreeFunction AlignedFree;
@@ -40,6 +42,7 @@ JPH_EXPORT void RegisterDefaultAllocator();
 
 // Directly define the allocation functions
 JPH_EXPORT void *Allocate(size_t inSize);
+JPH_EXPORT void *Reallocate(void *inBlock, size_t inSize);
 JPH_EXPORT void Free(void *inBlock);
 JPH_EXPORT void *AlignedAllocate(size_t inSize, size_t inAlignment);
 JPH_EXPORT void AlignedFree(void *inBlock);

+ 1 - 1
Jolt/Core/Profiler.cpp

@@ -88,7 +88,7 @@ void Profiler::RemoveThread(ProfileThread *inThread)
 {
 	std::lock_guard lock(mLock);
 
-	Array<ProfileThread *>::iterator i = find(mThreads.begin(), mThreads.end(), inThread);
+	Array<ProfileThread *>::iterator i = std::find(mThreads.begin(), mThreads.end(), inThread);
 	JPH_ASSERT(i != mThreads.end());
 	mThreads.erase(i);
 }

+ 12 - 0
Jolt/Core/STLAlignedAllocator.h

@@ -44,6 +44,18 @@ public:
 		return (pointer)AlignedAllocate(inN * sizeof(value_type), N);
 	}
 
+	/// Reallocate memory
+	inline pointer			reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
+	{
+		pointer new_pointer = allocate(inNewSize);
+		if (inOldPointer != nullptr)
+		{
+			memcpy(new_pointer, inOldPointer, inOldSize * sizeof(value_type));
+			deallocate(inOldPointer, inOldSize);
+		}
+		return new_pointer;
+	}
+
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type)
 	{

+ 18 - 1
Jolt/Core/STLAllocator.h

@@ -49,6 +49,24 @@ public:
 			return pointer(Allocate(inN * sizeof(value_type)));
 	}
 
+	/// Reallocate memory
+	inline pointer			reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
+	{
+		if constexpr (alignof(T) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16))
+		{
+			// Can't reallocate aligned blocks
+			pointer new_pointer = allocate(inNewSize);
+			if (inOldPointer != nullptr)
+			{
+				memcpy(new_pointer, inOldPointer, inOldSize * sizeof(value_type));
+				deallocate(inOldPointer, inOldSize);
+			}
+			return new_pointer;
+		}
+		else
+			return pointer(Reallocate(inOldPointer, inNewSize * sizeof(value_type)));
+	}
+
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type)
 	{
@@ -84,7 +102,6 @@ template <typename T> using STLAllocator = std::allocator<T>;
 #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
 
 // Declare STL containers that use our allocator
-template <class T> using Array = std::vector<T, STLAllocator<T>>;
 using String = std::basic_string<char, std::char_traits<char>, STLAllocator<char>>;
 using IStringStream = std::basic_istringstream<char, std::char_traits<char>, STLAllocator<char>>;
 

+ 12 - 0
Jolt/Core/STLTempAllocator.h

@@ -43,6 +43,18 @@ public:
 		return pointer(mAllocator.Allocate(uint(inN * sizeof(value_type))));
 	}
 
+	/// Reallocate memory
+	inline pointer			reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
+	{
+		pointer new_pointer = allocate(inNewSize);
+		if (inOldPointer != nullptr)
+		{
+			memcpy(new_pointer, inOldPointer, inOldSize * sizeof(value_type));
+			deallocate(inOldPointer, inOldSize);
+		}
+		return new_pointer;
+	}
+
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type inN)
 	{

+ 2 - 0
Jolt/Core/StaticArray.h

@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <Jolt/Core/HashCombine.h>
+
 JPH_NAMESPACE_BEGIN
 
 /// Simple variable length array backed by a fixed size buffer

+ 6 - 6
Jolt/Core/StreamIn.h

@@ -33,9 +33,9 @@ public:
 
 	/// Read a vector of primitives from the binary stream
 	template <class T, class A, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
-	void				Read(std::vector<T, A> &outT)
+	void				Read(Array<T, A> &outT)
 	{
-		typename Array<T>::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class
+		typename Array<T, A>::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class
 		Read(len);
 		if (!IsEOF() && !IsFailed())
 		{
@@ -43,7 +43,7 @@ public:
 			if constexpr (std::is_same_v<T, Vec3> || std::is_same_v<T, DVec3> || std::is_same_v<T, DMat44>)
 			{
 				// These types have unused components that we don't want to read
-				for (typename Array<T>::size_type i = 0; i < len; ++i)
+				for (typename Array<T, A>::size_type i = 0; i < len; ++i)
 					Read(outT[i]);
 			}
 			else
@@ -73,14 +73,14 @@ public:
 
 	/// Read a vector of primitives from the binary stream using a custom function to read the elements
 	template <class T, class A, typename F>
-	void				Read(std::vector<T, A> &outT, const F &inReadElement)
+	void				Read(Array<T, A> &outT, const F &inReadElement)
 	{
-		typename Array<T>::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class
+		typename Array<T, A>::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class
 		Read(len);
 		if (!IsEOF() && !IsFailed())
 		{
 			outT.resize(len);
-			for (typename Array<T>::size_type i = 0; i < len; ++i)
+			for (typename Array<T, A>::size_type i = 0; i < len; ++i)
 				inReadElement(*this, outT[i]);
 		}
 		else

+ 6 - 6
Jolt/Core/StreamOut.h

@@ -30,16 +30,16 @@ public:
 
 	/// Write a vector of primitives to the binary stream
 	template <class T, class A, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
-	void				Write(const std::vector<T, A> &inT)
+	void				Write(const Array<T, A> &inT)
 	{
-		typename Array<T>::size_type len = inT.size();
+		typename Array<T, A>::size_type len = inT.size();
 		Write(len);
 		if (!IsFailed())
 		{
 			if constexpr (std::is_same_v<T, Vec3> || std::is_same_v<T, DVec3> || std::is_same_v<T, DMat44>)
 			{
 				// These types have unused components that we don't want to write
-				for (typename Array<T>::size_type i = 0; i < len; ++i)
+				for (typename Array<T, A>::size_type i = 0; i < len; ++i)
 					Write(inT[i]);
 			}
 			else
@@ -62,12 +62,12 @@ public:
 
 	/// Write a vector of primitives to the binary stream using a custom write function
 	template <class T, class A, typename F>
-	void				Write(const std::vector<T, A> &inT, const F &inWriteElement)
+	void				Write(const Array<T, A> &inT, const F &inWriteElement)
 	{
-		typename Array<T>::size_type len = inT.size();
+		typename Array<T, A>::size_type len = inT.size();
 		Write(len);
 		if (!IsFailed())
-			for (typename Array<T>::size_type i = 0; i < len; ++i)
+			for (typename Array<T, A>::size_type i = 0; i < len; ++i)
 				inWriteElement(inT[i], *this);
 	}
 

+ 3 - 4
Jolt/Geometry/ConvexHullBuilder.cpp

@@ -225,8 +225,7 @@ bool ConvexHullBuilder::AssignPointToFace(int inPositionIdx, const Faces &inFace
 			else
 			{
 				// Not the furthest point, add it as the before last point
-				best_face->mConflictList.push_back(best_face->mConflictList.back());
-				best_face->mConflictList[best_face->mConflictList.size() - 2] = inPositionIdx;
+				best_face->mConflictList.insert(best_face->mConflictList.begin() + best_face->mConflictList.size() - 1, inPositionIdx);
 			}
 
 			return true;
@@ -265,7 +264,7 @@ bool ConvexHullBuilder::ContainsFace(const Array<int> &inIndices) const
 	for (Face *f : mFaces)
 	{
 		Edge *e = f->mFirstEdge;
-		Array<int>::const_iterator index = find(inIndices.begin(), inIndices.end(), e->mStartIdx);
+		Array<int>::const_iterator index = std::find(inIndices.begin(), inIndices.end(), e->mStartIdx);
 		if (index != inIndices.end())
 		{
 			size_t matches = 0;
@@ -1007,7 +1006,7 @@ void ConvexHullBuilder::MergeCoplanarOrConcaveFaces(Face *inFace, float inCoplan
 
 void ConvexHullBuilder::sMarkAffected(Face *inFace, Faces &ioAffectedFaces)
 {
-	if (find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())
+	if (std::find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())
 		ioAffectedFaces.push_back(inFace);
 }
 

+ 1 - 2
Jolt/Geometry/ConvexHullBuilder2D.cpp

@@ -142,8 +142,7 @@ void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge
 		else
 		{
 			// Not the furthest point, add it as the before last point
-			best_edge->mConflictList.push_back(best_edge->mConflictList.back());
-			best_edge->mConflictList[best_edge->mConflictList.size() - 2] = inPositionIdx;
+			best_edge->mConflictList.insert(best_edge->mConflictList.begin() + best_edge->mConflictList.size() - 1, inPositionIdx);
 		}
 	}
 }

+ 5 - 0
Jolt/Jolt.cmake

@@ -14,6 +14,7 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
 	${JOLT_PHYSICS_ROOT}/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
 	${JOLT_PHYSICS_ROOT}/Core/ARMNeon.h
+	${JOLT_PHYSICS_ROOT}/Core/Array.h
 	${JOLT_PHYSICS_ROOT}/Core/Atomics.h
 	${JOLT_PHYSICS_ROOT}/Core/ByteBuffer.h
 	${JOLT_PHYSICS_ROOT}/Core/Color.cpp
@@ -527,6 +528,10 @@ if (OBJECT_LAYER_BITS)
 	target_compile_definitions(Jolt PUBLIC JPH_OBJECT_LAYER_BITS=${OBJECT_LAYER_BITS})
 endif()
 
+if (USE_STD_VECTOR)
+	target_compile_definitions(Jolt PUBLIC JPH_USE_STD_VECTOR)
+endif()
+
 # Setting to periodically trace broadphase stats to help determine if the broadphase layer configuration is optimal
 if (TRACK_BROADPHASE_STATS)
 	target_compile_definitions(Jolt PUBLIC JPH_TRACK_BROADPHASE_STATS)

+ 1 - 1
Jolt/Jolt.h

@@ -8,8 +8,8 @@
 #include <Jolt/Core/Core.h>
 #include <Jolt/Core/ARMNeon.h>
 #include <Jolt/Core/Memory.h>
-#include <Jolt/Core/STLAllocator.h>
 #include <Jolt/Core/IssueReporting.h>
+#include <Jolt/Core/Array.h>
 #include <Jolt/Math/Math.h>
 #include <Jolt/Math/Vec4.h>
 #include <Jolt/Math/Mat44.h>

+ 94 - 83
Jolt/Jolt.natvis

@@ -1,86 +1,97 @@
 <?xml version="1.0" encoding="utf-8"?>
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
-  <Type Name="JPH::Color">
-    <DisplayString>r={(int)r}, g={(int)g}, b={(int)b}, a={(int)a}</DisplayString>
-  </Type>
-  <Type Name="JPH::Float2">
-    <DisplayString>{x}, {y}</DisplayString>
-  </Type>
-  <Type Name="JPH::Float3">
-    <DisplayString>{x}, {y}, {z}</DisplayString>
-  </Type>
-  <Type Name="JPH::Float4">
-    <DisplayString>{x}, {y}, {z}, {w}</DisplayString>
-  </Type>
-  <Type Name="JPH::Vec3">
-    <DisplayString>{mF32[0]}, {mF32[1]}, {mF32[2]}, L^2={mF32[0]*mF32[0]+mF32[1]*mF32[1]+mF32[2]*mF32[2]}</DisplayString>
-  </Type>
-  <Type Name="JPH::DVec3">
-    <DisplayString>{mF64[0]}, {mF64[1]}, {mF64[2]}, L^2={mF64[0]*mF64[0]+mF64[1]*mF64[1]+mF64[2]*mF64[2]}</DisplayString>
-  </Type>
-  <Type Name="JPH::Vec4">
-    <DisplayString>{mF32[0]}, {mF32[1]}, {mF32[2]}, {mF32[3]}, L^2={mF32[0]*mF32[0]+mF32[1]*mF32[1]+mF32[2]*mF32[2]+mF32[3]*mF32[3]}</DisplayString>
-  </Type>
-  <Type Name="JPH::UVec4">
-    <DisplayString>{mU32[0]}, {mU32[1]}, {mU32[2]}, {mU32[3]}</DisplayString>
-  </Type>
-  <Type Name="JPH::Quat">
-    <DisplayString>{mValue}</DisplayString>
-  </Type>
-  <Type Name="JPH::Mat44">
-    <DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol[3].mF32[0]} | {mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol[3].mF32[1]} | {mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol[3].mF32[2]}</DisplayString>
-    <Expand>
-      <Synthetic Name="[Row 0]">
-        <DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol[3].mF32[0]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 1]">
-        <DisplayString>{mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol[3].mF32[1]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 2]">
-        <DisplayString>{mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol[3].mF32[2]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 3]">
-        <DisplayString>{mCol[0].mF32[3]}, {mCol[1].mF32[3]}, {mCol[2].mF32[3]}, {mCol[3].mF32[3]}</DisplayString>
-      </Synthetic>
-    </Expand>
-  </Type>
-  <Type Name="JPH::DMat44">
-    <DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol3.mF64[0]} | {mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol3.mF64[1]} | {mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol3.mF64[2]}</DisplayString>
-    <Expand>
-      <Synthetic Name="[Row 0]">
-        <DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol3.mF64[0]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 1]">
-        <DisplayString>{mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol3.mF64[1]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 2]">
-        <DisplayString>{mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol3.mF64[2]}</DisplayString>
-      </Synthetic>
-      <Synthetic Name="[Row 3]">
-        <DisplayString>{mCol[0].mF32[3]}, {mCol[1].mF32[3]}, {mCol[2].mF32[3]}, 1}</DisplayString>
-      </Synthetic>
-    </Expand>
-  </Type>
-  <Type Name="JPH::AABox">
-    <DisplayString>min=({mMin}), max=({mMax})</DisplayString>
-  </Type>
-  <Type Name="JPH::BodyID">
-    <DisplayString>{mID}</DisplayString>
-  </Type>
-  <Type Name="JPH::Body">
-    <DisplayString>{mDebugName}: p=({mPosition.mF32[0],g}, {mPosition.mF32[1],g}, {mPosition.mF32[2],g}), r=({mRotation.mValue.mF32[0],g}, {mRotation.mValue.mF32[1],g}, {mRotation.mValue.mF32[2],g}, {mRotation.mValue.mF32[3],g}), v=({mLinearVelocity.mF32[0],g}, {mLinearVelocity.mF32[1],g}, {mLinearVelocity.mF32[2],g}), w=({mAngularVelocity.mF32[0],g}, {mAngularVelocity.mF32[1],g}, {mAngularVelocity.mF32[2],g})</DisplayString>
-  </Type>
-  <Type Name="JPH::BodyManager">
-    <DisplayString>bodies={mBodies._Mypair._Myval2._Mylast - mBodies._Mypair._Myval2._Myfirst}, active={mActiveBodies._Mypair._Myval2._Mylast - mActiveBodies._Mypair._Myval2._Myfirst}</DisplayString>
-  </Type>
-  <Type Name="JPH::StaticArray&lt;*&gt;">
-    <DisplayString>size={mSize}</DisplayString>
-    <Expand>
-      <Item Name="[size]" ExcludeView="simple">mSize</Item>
-      <ArrayItems>
-        <Size>mSize</Size>
-        <ValuePointer>(value_type *)mElements</ValuePointer>
-      </ArrayItems>
-    </Expand>
-  </Type>
+	<Type Name="JPH::Color">
+		<DisplayString>r={(int)r}, g={(int)g}, b={(int)b}, a={(int)a}</DisplayString>
+	</Type>
+	<Type Name="JPH::Float2">
+		<DisplayString>{x}, {y}</DisplayString>
+	</Type>
+	<Type Name="JPH::Float3">
+		<DisplayString>{x}, {y}, {z}</DisplayString>
+	</Type>
+	<Type Name="JPH::Float4">
+		<DisplayString>{x}, {y}, {z}, {w}</DisplayString>
+	</Type>
+	<Type Name="JPH::Vec3">
+		<DisplayString>{mF32[0]}, {mF32[1]}, {mF32[2]}, L^2={mF32[0]*mF32[0]+mF32[1]*mF32[1]+mF32[2]*mF32[2]}</DisplayString>
+	</Type>
+	<Type Name="JPH::DVec3">
+		<DisplayString>{mF64[0]}, {mF64[1]}, {mF64[2]}, L^2={mF64[0]*mF64[0]+mF64[1]*mF64[1]+mF64[2]*mF64[2]}</DisplayString>
+	</Type>
+	<Type Name="JPH::Vec4">
+		<DisplayString>{mF32[0]}, {mF32[1]}, {mF32[2]}, {mF32[3]}, L^2={mF32[0]*mF32[0]+mF32[1]*mF32[1]+mF32[2]*mF32[2]+mF32[3]*mF32[3]}</DisplayString>
+	</Type>
+	<Type Name="JPH::UVec4">
+		<DisplayString>{mU32[0]}, {mU32[1]}, {mU32[2]}, {mU32[3]}</DisplayString>
+	</Type>
+	<Type Name="JPH::Quat">
+		<DisplayString>{mValue}</DisplayString>
+	</Type>
+	<Type Name="JPH::Mat44">
+		<DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol[3].mF32[0]} | {mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol[3].mF32[1]} | {mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol[3].mF32[2]}</DisplayString>
+		<Expand>
+			<Synthetic Name="[Row 0]">
+				<DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol[3].mF32[0]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 1]">
+				<DisplayString>{mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol[3].mF32[1]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 2]">
+				<DisplayString>{mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol[3].mF32[2]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 3]">
+				<DisplayString>{mCol[0].mF32[3]}, {mCol[1].mF32[3]}, {mCol[2].mF32[3]}, {mCol[3].mF32[3]}</DisplayString>
+			</Synthetic>
+		</Expand>
+	</Type>
+	<Type Name="JPH::DMat44">
+		<DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol3.mF64[0]} | {mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol3.mF64[1]} | {mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol3.mF64[2]}</DisplayString>
+		<Expand>
+			<Synthetic Name="[Row 0]">
+				<DisplayString>{mCol[0].mF32[0]}, {mCol[1].mF32[0]}, {mCol[2].mF32[0]}, {mCol3.mF64[0]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 1]">
+				<DisplayString>{mCol[0].mF32[1]}, {mCol[1].mF32[1]}, {mCol[2].mF32[1]}, {mCol3.mF64[1]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 2]">
+				<DisplayString>{mCol[0].mF32[2]}, {mCol[1].mF32[2]}, {mCol[2].mF32[2]}, {mCol3.mF64[2]}</DisplayString>
+			</Synthetic>
+			<Synthetic Name="[Row 3]">
+				<DisplayString>{mCol[0].mF32[3]}, {mCol[1].mF32[3]}, {mCol[2].mF32[3]}, 1}</DisplayString>
+			</Synthetic>
+		</Expand>
+	</Type>
+	<Type Name="JPH::AABox">
+		<DisplayString>min=({mMin}), max=({mMax})</DisplayString>
+	</Type>
+	<Type Name="JPH::BodyID">
+		<DisplayString>{mID}</DisplayString>
+	</Type>
+	<Type Name="JPH::Body">
+		<DisplayString>{mDebugName}: p=({mPosition.mF32[0],g}, {mPosition.mF32[1],g}, {mPosition.mF32[2],g}), r=({mRotation.mValue.mF32[0],g}, {mRotation.mValue.mF32[1],g}, {mRotation.mValue.mF32[2],g}, {mRotation.mValue.mF32[3],g}), v=({mLinearVelocity.mF32[0],g}, {mLinearVelocity.mF32[1],g}, {mLinearVelocity.mF32[2],g}), w=({mAngularVelocity.mF32[0],g}, {mAngularVelocity.mF32[1],g}, {mAngularVelocity.mF32[2],g})</DisplayString>
+	</Type>
+	<Type Name="JPH::BodyManager">
+		<DisplayString>bodies={mBodies._Mypair._Myval2._Mylast - mBodies._Mypair._Myval2._Myfirst}, active={mActiveBodies._Mypair._Myval2._Mylast - mActiveBodies._Mypair._Myval2._Myfirst}</DisplayString>
+	</Type>
+	<Type Name="JPH::StaticArray&lt;*&gt;">
+		<DisplayString>size={mSize}</DisplayString>
+		<Expand>
+			<Item Name="[size]" ExcludeView="simple">mSize</Item>
+			<ArrayItems>
+				<Size>mSize</Size>
+				<ValuePointer>(value_type *)mElements</ValuePointer>
+			</ArrayItems>
+		</Expand>
+	</Type>
+	<Type Name="JPH::Array&lt;*&gt;">
+		<DisplayString>size={mSize}</DisplayString>
+		<Expand>
+			<Item Name="[size]" ExcludeView="simple">mSize</Item>
+			<Item Name="[capacity]" ExcludeView="simple">mCapacity</Item>
+			<ArrayItems>
+				<Size>mSize</Size>
+				<ValuePointer>mElements</ValuePointer>
+			</ArrayItems>
+		</Expand>
+	</Type>
 </AutoVisualizer>

+ 4 - 2
Jolt/Math/Vec3.cpp

@@ -29,7 +29,7 @@ static void sCreateVertices(std::unordered_set<Vec3> &ioVertices, Vec3Arg inDir1
 	}
 }
 
-const std::vector<Vec3> Vec3::sUnitSphere = []() {
+const Array<Vec3, std::allocator<Vec3>> Vec3::sUnitSphere = []() {
 
 	const int level = 3;
 
@@ -53,7 +53,9 @@ const std::vector<Vec3> Vec3::sUnitSphere = []() {
 	sCreateVertices(verts, Vec3::sAxisX(), -Vec3::sAxisY(), -Vec3::sAxisZ(), level);
 	sCreateVertices(verts, -Vec3::sAxisX(), -Vec3::sAxisY(), -Vec3::sAxisZ(), level);
 
-	return std::vector<Vec3>(verts.begin(), verts.end());
+	Array<Vec3, std::allocator<Vec3>> array;
+	array.assign(verts.begin(), verts.end());
+	return array;
 }();
 
 JPH_NAMESPACE_END

+ 1 - 1
Jolt/Math/Vec3.h

@@ -104,7 +104,7 @@ public:
 	static JPH_INLINE Vec3		sUnitSpherical(float inTheta, float inPhi);
 
 	/// A set of vectors uniformly spanning the surface of a unit sphere, usable for debug purposes
-	JPH_EXPORT static const std::vector<Vec3> sUnitSphere;
+	JPH_EXPORT static const Array<Vec3, std::allocator<Vec3>> sUnitSphere;
 
 	/// Get random unit vector
 	template <class Random>

+ 2 - 2
Jolt/ObjectStream/GetPrimitiveTypeOfType.h

@@ -33,8 +33,8 @@ const RTTI *GetPrimitiveTypeOfType(RefConst<T> *)
 	return GetRTTIOfType((T *)nullptr);
 }
 
-template <class T>
-const RTTI *GetPrimitiveTypeOfType(Array<T> *)
+template <class T, class A>
+const RTTI *GetPrimitiveTypeOfType(Array<T, A> *)
 {
 	return GetPrimitiveTypeOfType((T *)nullptr);
 }

+ 8 - 8
Jolt/ObjectStream/ObjectStream.h

@@ -119,8 +119,8 @@ public:
 #include <Jolt/ObjectStream/ObjectStreamTypes.h>
 
 // Define serialization templates
-template <class T>
-bool OSIsType(Array<T> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)
+template <class T, class A>
+bool OSIsType(Array<T, A> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)
 {
 	return (inArrayDepth > 0 && OSIsType(static_cast<T *>(nullptr), inArrayDepth - 1, inDataType, inClassName));
 }
@@ -150,8 +150,8 @@ bool OSIsType(RefConst<T> *, int inArrayDepth, EOSDataType inDataType, const cha
 }
 
 /// Define serialization templates for dynamic arrays
-template <class T>
-bool OSReadData(IObjectStreamIn &ioStream, Array<T> &inArray)
+template <class T, class A>
+bool OSReadData(IObjectStreamIn &ioStream, Array<T, A> &inArray)
 {
 	bool continue_reading = true;
 
@@ -230,15 +230,15 @@ bool OSReadData(IObjectStreamIn &ioStream, RefConst<T> &inRef)
 }
 
 // Define serialization templates for dynamic arrays
-template <class T>
-void OSWriteDataType(IObjectStreamOut &ioStream, Array<T> *)
+template <class T, class A>
+void OSWriteDataType(IObjectStreamOut &ioStream, Array<T, A> *)
 {
 	ioStream.WriteDataType(EOSDataType::Array);
 	OSWriteDataType(ioStream, static_cast<T *>(nullptr));
 }
 
-template <class T>
-void OSWriteData(IObjectStreamOut &ioStream, const Array<T> &inArray)
+template <class T, class A>
+void OSWriteData(IObjectStreamOut &ioStream, const Array<T, A> &inArray)
 {
 	// Write size of array
 	ioStream.HintNextItem();

+ 1 - 1
Jolt/Physics/Body/BodyFilter.h

@@ -76,7 +76,7 @@ public:
 	/// Filter function. Returns true if we should collide with inBodyID
 	virtual bool			ShouldCollide(const BodyID &inBodyID) const override
 	{
-		return find(mBodyIDs.begin(), mBodyIDs.end(), inBodyID) == mBodyIDs.end();
+		return std::find(mBodyIDs.begin(), mBodyIDs.end(), inBodyID) == mBodyIDs.end();
 	}
 
 private:

+ 1 - 1
Jolt/Physics/Body/BodyManager.cpp

@@ -120,7 +120,7 @@ void BodyManager::Init(uint inMaxBodies, uint inNumBodyMutexes, const BroadPhase
 	}
 
 	// Allocate space for sequence numbers
-	mBodySequenceNumbers.resize(inMaxBodies);
+	mBodySequenceNumbers.resize(inMaxBodies, 0);
 
 	// Keep layer interface
 	mBroadPhaseLayerInterface = &inLayerInterface;

+ 3 - 3
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -509,7 +509,7 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f
 	}
 
 	// Create array that holds the constraints in order of time of impact (sort will happen later)
-	std::vector<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);
+	Array<Constraint *, STLTempAllocator<Constraint *>> sorted_constraints(inAllocator);
 	sorted_constraints.resize(ioConstraints.size());
 	for (size_t index = 0; index < sorted_constraints.size(); index++)
 		sorted_constraints[index] = &ioConstraints[index];
@@ -525,7 +525,7 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f
 	outTimeSimulated = 0.0f;
 
 	// These are the contacts that we hit previously without moving a significant distance
-	std::vector<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);
+	Array<Constraint *, STLTempAllocator<Constraint *>> previous_contacts(inAllocator);
 	previous_contacts.resize(mMaxConstraintIterations);
 	int num_previous_contacts = 0;
 
@@ -1217,7 +1217,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
 	// We need to do this before calling MoveShape because it will update mActiveContacts.
 	Vec3 character_velocity = inStepForward / inDeltaTime;
 	Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;
-	std::vector<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);
+	Array<Vec3, STLTempAllocator<Vec3>> steep_slope_normals(inAllocator);
 	steep_slope_normals.reserve(mActiveContacts.size());
 	for (const Contact &c : mActiveContacts)
 		if (c.mHadCollision

+ 3 - 3
Jolt/Physics/Character/CharacterVirtual.h

@@ -315,7 +315,7 @@ public:
 		bool							mCanPushCharacter = true;								///< When true, the velocity of the contact point can push the character
 	};
 
-	using TempContactList = std::vector<Contact, STLTempAllocator<Contact>>;
+	using TempContactList = Array<Contact, STLTempAllocator<Contact>>;
 	using ContactList = Array<Contact>;
 
 	/// Access to the internal list of contacts that the character has found.
@@ -344,7 +344,7 @@ private:
 		SubShapeID						mSubShapeID;											///< Sub shape of body we're colliding with
 	};
 
-	using IgnoredContactList = std::vector<IgnoredContact, STLTempAllocator<IgnoredContact>>;
+	using IgnoredContactList = Array<IgnoredContact, STLTempAllocator<IgnoredContact>>;
 
 	// A constraint that limits the movement of the character
 	struct Constraint
@@ -357,7 +357,7 @@ private:
 		bool							mIsSteepSlope = false;									///< If this constraint belongs to a steep slope
 	};
 
-	using ConstraintList = std::vector<Constraint, STLTempAllocator<Constraint>>;
+	using ConstraintList = Array<Constraint, STLTempAllocator<Constraint>>;
 
 	// Collision collector that collects hits for CollideShape
 	class ContactCollector : public CollideShapeCollector

+ 1 - 1
Jolt/Physics/Collision/BroadPhase/BroadPhaseBruteForce.cpp

@@ -64,7 +64,7 @@ void BroadPhaseBruteForce::RemoveBodies(BodyID *ioBodies, int inNumber)
 		JPH_ASSERT(body.IsInBroadPhase());
 
 		// Find body id
-		Array<BodyID>::iterator it = lower_bound(mBodyIDs.begin(), mBodyIDs.end(), body.GetID());
+		Array<BodyID>::const_iterator it = std::lower_bound(mBodyIDs.begin(), mBodyIDs.end(), body.GetID());
 		JPH_ASSERT(it != mBodyIDs.end());
 
 		// Remove element

+ 6 - 6
Jolt/Physics/Collision/Shape/CapsuleShape.cpp

@@ -33,20 +33,20 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(CapsuleShapeSettings)
 
 static const int cCapsuleDetailLevel = 2;
 
-static const std::vector<Vec3> sCapsuleTopTriangles = []() {
-	std::vector<Vec3> verts;
+static const Array<Vec3, std::allocator<Vec3>> sCapsuleTopTriangles = []() {
+	Array<Vec3, std::allocator<Vec3>> verts;
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, cCapsuleDetailLevel);
 	return verts;
 }();
 
-static const std::vector<Vec3> sCapsuleMiddleTriangles = []() {
-	std::vector<Vec3> verts;
+static const Array<Vec3, std::allocator<Vec3>> sCapsuleMiddleTriangles = []() {
+	Array<Vec3, std::allocator<Vec3>> verts;
 	GetTrianglesContextVertexList::sCreateUnitOpenCylinder(verts, cCapsuleDetailLevel);
 	return verts;
 }();
 
-static const std::vector<Vec3> sCapsuleBottomTriangles = []() {
-	std::vector<Vec3> verts;
+static const Array<Vec3, std::allocator<Vec3>> sCapsuleBottomTriangles = []() {
+	Array<Vec3, std::allocator<Vec3>> verts;
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, cCapsuleDetailLevel);
 	return verts;
 }();

+ 2 - 2
Jolt/Physics/Collision/Shape/ConvexShape.cpp

@@ -33,10 +33,10 @@ JPH_IMPLEMENT_SERIALIZABLE_ABSTRACT(ConvexShapeSettings)
 	JPH_ADD_ATTRIBUTE(ConvexShapeSettings, mMaterial)
 }
 
-const std::vector<Vec3> ConvexShape::sUnitSphereTriangles = []() {
+const Array<Vec3, std::allocator<Vec3>> ConvexShape::sUnitSphereTriangles = []() {
 	const int level = 2;
 
-	std::vector<Vec3> verts;
+	Array<Vec3, std::allocator<Vec3>> verts;
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, level);
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, level);
 	return verts;

+ 1 - 1
Jolt/Physics/Collision/Shape/ConvexShape.h

@@ -132,7 +132,7 @@ protected:
 	virtual void					RestoreBinaryState(StreamIn &inStream) override;
 
 	/// Vertex list that forms a unit sphere
-	static const std::vector<Vec3>	sUnitSphereTriangles;
+	static const Array<Vec3, std::allocator<Vec3>> sUnitSphereTriangles;
 
 private:
 	// Class for GetTrianglesStart/Next

+ 2 - 2
Jolt/Physics/Collision/Shape/CylinderShape.cpp

@@ -45,8 +45,8 @@ static const Vec3 cTopFace[] =
 	Vec3(-cSin45,	1.0f,	cSin45)
 };
 
-static const std::vector<Vec3> sUnitCylinderTriangles = []() {
-	std::vector<Vec3> verts;
+static const Array<Vec3, std::allocator<Vec3>> sUnitCylinderTriangles = []() {
+	Array<Vec3, std::allocator<Vec3>> verts;
 
 	const Vec3 bottom_offset(0.0f, -2.0f, 0.0f);
 

+ 4 - 4
Jolt/Physics/Collision/Shape/GetTrianglesContext.h

@@ -68,7 +68,7 @@ public:
 	}
 
 	/// Helper function that creates a vertex list of a half unit sphere (top part)
-	static void		sCreateHalfUnitSphereTop(std::vector<Vec3> &ioVertices, int inDetailLevel)
+	static void		sCreateHalfUnitSphereTop(Array<Vec3, std::allocator<Vec3>> &ioVertices, int inDetailLevel)
 	{
 		sCreateUnitSphereHelper(ioVertices,  Vec3::sAxisX(),  Vec3::sAxisY(),  Vec3::sAxisZ(), inDetailLevel);
 		sCreateUnitSphereHelper(ioVertices,  Vec3::sAxisY(), -Vec3::sAxisX(),  Vec3::sAxisZ(), inDetailLevel);
@@ -77,7 +77,7 @@ public:
 	}
 
 	/// Helper function that creates a vertex list of a half unit sphere (bottom part)
-	static void		sCreateHalfUnitSphereBottom(std::vector<Vec3> &ioVertices, int inDetailLevel)
+	static void		sCreateHalfUnitSphereBottom(Array<Vec3, std::allocator<Vec3>> &ioVertices, int inDetailLevel)
 	{
 		sCreateUnitSphereHelper(ioVertices, -Vec3::sAxisX(), -Vec3::sAxisY(),  Vec3::sAxisZ(), inDetailLevel);
 		sCreateUnitSphereHelper(ioVertices, -Vec3::sAxisY(),  Vec3::sAxisX(),  Vec3::sAxisZ(), inDetailLevel);
@@ -86,7 +86,7 @@ public:
 	}
 
 	/// Helper function that creates an open cylinder of half height 1 and radius 1
-	static void		sCreateUnitOpenCylinder(std::vector<Vec3> &ioVertices, int inDetailLevel)
+	static void		sCreateUnitOpenCylinder(Array<Vec3, std::allocator<Vec3>> &ioVertices, int inDetailLevel)
 	{
 		const Vec3 bottom_offset(0.0f, -2.0f, 0.0f);
 		int num_verts = 4 * (1 << inDetailLevel);
@@ -112,7 +112,7 @@ public:
 
 private:
 	/// Recursive helper function for creating a sphere
-	static void		sCreateUnitSphereHelper(std::vector<Vec3> &ioVertices, Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, int inLevel)
+	static void		sCreateUnitSphereHelper(Array<Vec3, std::allocator<Vec3>> &ioVertices, Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, int inLevel)
 	{
 		Vec3 center1 = (inV1 + inV2).Normalized();
 		Vec3 center2 = (inV2 + inV3).Normalized();

+ 6 - 9
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -84,13 +84,11 @@ HeightFieldShapeSettings::HeightFieldShapeSettings(const float *inSamples, Vec3A
 	mScale(inScale),
 	mSampleCount(inSampleCount)
 {
-	mHeightSamples.resize(inSampleCount * inSampleCount);
-	memcpy(&mHeightSamples[0], inSamples, inSampleCount * inSampleCount * sizeof(float));
+	mHeightSamples.assign(inSamples, inSamples + inSampleCount * inSampleCount);
 
 	if (!inMaterialList.empty() && inMaterialIndices != nullptr)
 	{
-		mMaterialIndices.resize(Square(inSampleCount - 1));
-		memcpy(&mMaterialIndices[0], inMaterialIndices, Square(inSampleCount - 1) * sizeof(uint8));
+		mMaterialIndices.assign(inMaterialIndices, inMaterialIndices + Square(inSampleCount - 1));
 		mMaterials = inMaterialList;
 	}
 	else
@@ -339,11 +337,10 @@ void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSe
 		The triangles T1B, T2B, T3B and T4B do not need to be stored, their active edges can be constructed from adjacent triangles.
 		Add 1 byte padding so we can always read 1 uint16 to get the bits that cross an 8 bit boundary
 	*/
-	mActiveEdges.resize((Square(mSampleCount - 1) * 3 + 7) / 8 + 1);
 
 	// Make all edges active (if mSampleCount is bigger than inSettings.mSampleCount we need to fill up the padding,
 	// also edges at x = 0 and y = inSettings.mSampleCount - 1 are not updated)
-	memset(mActiveEdges.data(), 0xff, mActiveEdges.size());
+	mActiveEdges.resize((Square(mSampleCount - 1) * 3 + 7) / 8 + 1, 0xff);
 
 	// Now clear the edges that are not active
 	TempAllocatorMalloc allocator;
@@ -357,7 +354,7 @@ void HeightFieldShape::StoreMaterialIndices(const HeightFieldShapeSettings &inSe
 	uint out_count_min_1 = mSampleCount - 1;
 
 	mNumBitsPerMaterialIndex = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1);
-	mMaterialIndices.resize(((Square(out_count_min_1) * mNumBitsPerMaterialIndex + 7) >> 3) + 1); // Add 1 byte so we don't read out of bounds when reading an uint16
+	mMaterialIndices.resize(((Square(out_count_min_1) * mNumBitsPerMaterialIndex + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint16
 
 	for (uint y = 0; y < out_count_min_1; ++y)
 		for (uint x = 0; x < out_count_min_1; ++x)
@@ -639,7 +636,7 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 	JPH_ASSERT(mRangeBlocks.size() == sGridOffsets[ranges.size() - 1] + Square(max_stride));
 
 	// Quantize height samples
-	mHeightSamples.resize((mSampleCount * mSampleCount * inSettings.mBitsPerSample + 7) / 8 + 1);
+	mHeightSamples.resize((mSampleCount * mSampleCount * inSettings.mBitsPerSample + 7) / 8 + 1, 0);
 	int sample = 0;
 	for (uint y = 0; y < mSampleCount; ++y)
 		for (uint x = 0; x < mSampleCount; ++x)
@@ -1244,7 +1241,7 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 	if (new_bits_per_material_index != mNumBitsPerMaterialIndex)
 	{
 		// Resize the material indices array
-		mMaterialIndices.resize(((Square(count_min_1) * new_bits_per_material_index + 7) >> 3) + 1); // Add 1 byte so we don't read out of bounds when reading an uint16
+		mMaterialIndices.resize(((Square(count_min_1) * new_bits_per_material_index + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint16
 
 		// Calculate old and new mask
 		uint16 old_material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);

+ 2 - 2
Jolt/Physics/PhysicsSystem.cpp

@@ -109,7 +109,7 @@ void PhysicsSystem::AddStepListener(PhysicsStepListener *inListener)
 {
 	lock_guard lock(mStepListenersMutex);
 
-	JPH_ASSERT(find(mStepListeners.begin(), mStepListeners.end(), inListener) == mStepListeners.end());
+	JPH_ASSERT(std::find(mStepListeners.begin(), mStepListeners.end(), inListener) == mStepListeners.end());
 	mStepListeners.push_back(inListener);
 }
 
@@ -117,7 +117,7 @@ void PhysicsSystem::RemoveStepListener(PhysicsStepListener *inListener)
 {
 	lock_guard lock(mStepListenersMutex);
 
-	StepListeners::iterator i = find(mStepListeners.begin(), mStepListeners.end(), inListener);
+	StepListeners::iterator i = std::find(mStepListeners.begin(), mStepListeners.end(), inListener);
 	JPH_ASSERT(i != mStepListeners.end());
 	*i = mStepListeners.back();
 	mStepListeners.pop_back();

+ 1 - 1
Jolt/Physics/PhysicsUpdateContext.h

@@ -142,7 +142,7 @@ public:
 		JobHandle			mStartNextStep;											///< Job that kicks the next step (empty for the last step)
 	};
 
-	using Steps = std::vector<Step, STLTempAllocator<Step>>;
+	using Steps = Array<Step, STLTempAllocator<Step>>;
 
 	/// Maximum amount of concurrent jobs on this machine
 	int						GetMaxConcurrency() const								{ const int max_concurrency = PhysicsUpdateContext::cMaxConcurrency; return min(max_concurrency, mJobSystem->GetMaxConcurrency()); } ///< Need to put max concurrency in temp var as min requires a reference

+ 1 - 1
Jolt/RegisterTypes.cpp

@@ -97,7 +97,7 @@ void RegisterTypesInternal(uint64 inVersionID)
 	}
 
 #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
-	JPH_ASSERT(Allocate != nullptr && Free != nullptr && AlignedAllocate != nullptr && AlignedFree != nullptr, "Need to supply an allocator first or call RegisterDefaultAllocator()");
+	JPH_ASSERT(Allocate != nullptr && Reallocate != nullptr && Free != nullptr && AlignedAllocate != nullptr && AlignedFree != nullptr, "Need to supply an allocator first or call RegisterDefaultAllocator()");
 #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
 
 	JPH_ASSERT(Factory::sInstance != nullptr, "Need to create a factory first!");

+ 2 - 2
Jolt/Skeleton/SkeletonMapper.cpp

@@ -88,8 +88,8 @@ void SkeletonMapper::Initialize(const Skeleton *inSkeleton1, const Mat44 *inNeut
 			if (cur == start)
 			{
 				// Reverse the chains
-				reverse(chain1.begin(), chain1.end());
-				reverse(chain2.begin(), chain2.end());
+				std::reverse(chain1.begin(), chain1.end());
+				std::reverse(chain2.begin(), chain2.end());
 
 				// Mark elements mapped
 				for (int j1 : chain1)

+ 2 - 2
Jolt/TriangleGrouper/TriangleGrouperClosestCentroid.cpp

@@ -29,7 +29,7 @@ void TriangleGrouperClosestCentroid::Group(const VertexList &inVertices, const I
 		outGroupedTriangleIndices[t] = t;
 	}
 
-	Array<uint>::iterator triangles_end = outGroupedTriangleIndices.end();
+	Array<uint>::const_iterator triangles_end = outGroupedTriangleIndices.end();
 
 	// Sort per batch
 	for (uint b = 0; b < num_batches - 1; ++b)
@@ -82,7 +82,7 @@ void TriangleGrouperClosestCentroid::Group(const VertexList &inVertices, const I
 					{
 						return inLHS < (centroids[inRHS] - first_centroid).LengthSq();
 					});
-				copy_backward(upper, batch_end_minus_1, batch_end);
+				std::copy_backward(upper, batch_end_minus_1, batch_end);
 				*upper = other_val;
 
 				// Calculate new furthest distance

+ 7 - 0
TestFramework/Utils/CustomMemoryHook.cpp

@@ -62,6 +62,12 @@ static void *AllocateHook(size_t inSize)
 	return TagAllocation(malloc(inSize + 16), 16, 'U');
 }
 
+static void *ReallocateHook(void *inBlock, size_t inSize)
+{
+	InCustomAllocator ica;
+	return TagAllocation(realloc(UntagAllocation(inBlock, 16, 'U'), inSize + 16), 16, 'U');
+}
+
 static void FreeHook(void *inBlock)
 {
 	InCustomAllocator ica;
@@ -91,6 +97,7 @@ static int MyAllocHook(int nAllocType, void *pvData, size_t nSize, int nBlockUse
 void RegisterCustomMemoryHook()
 {
 	Allocate = AllocateHook;
+	Reallocate = ReallocateHook;
 	Free = FreeHook;
 	AlignedAllocate = AlignedAllocateHook;
 	AlignedFree = AlignedFreeHook;

+ 620 - 0
UnitTests/Core/ArrayTest.cpp

@@ -0,0 +1,620 @@
+// 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);
+
+		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);
+	}
+
+	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);
+	}
+
+	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);
+#ifndef JPH_USE_STD_VECTOR
+		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("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);
+	}
+}

+ 1 - 1
UnitTests/Physics/PhysicsTests.cpp

@@ -1570,7 +1570,7 @@ TEST_SUITE("PhysicsTests")
 		public:
 			bool						ShouldSaveBody(const BodyID &inBodyID) const
 			{
-				return find(mIgnoreBodies.cbegin(), mIgnoreBodies.cend(), inBodyID) == mIgnoreBodies.cend();
+				return std::find(mIgnoreBodies.cbegin(), mIgnoreBodies.cend(), inBodyID) == mIgnoreBodies.cend();
 			}
 
 			virtual bool				ShouldSaveBody(const Body &inBody) const override

+ 1 - 0
UnitTests/UnitTests.cmake

@@ -3,6 +3,7 @@ set(UNIT_TESTS_ROOT ${PHYSICS_REPO_ROOT}/UnitTests)
 
 # Source files
 set(UNIT_TESTS_SRC_FILES
+	${UNIT_TESTS_ROOT}/Core/ArrayTest.cpp
 	${UNIT_TESTS_ROOT}/Core/FPFlushDenormalsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/InsertionSortTest.cpp
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp