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}}
       working-directory: ${{github.workspace}}/Build/Linux_${{matrix.build_type}}_${{matrix.clang_version}}
       run: ctest --output-on-failure --verbose
       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:
   linux-gcc:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     name: Linux GCC
     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
 # 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)
 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)
 include(CMakeDependentOption)
 
 
 # Ability to toggle between the static and DLL versions of the MSVC runtime library
 # 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
 JPH_NAMESPACE_BEGIN
 
 
 /// Underlying data type for ByteBuffer
 /// 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
 /// Simple byte buffer, aligned to a cache line
 class ByteBuffer : public ByteBufferVector
 class ByteBuffer : public ByteBufferVector

+ 0 - 1
Jolt/Core/Core.h

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

+ 1 - 1
Jolt/Core/LinearCurve.cpp

@@ -27,7 +27,7 @@ float LinearCurve::GetValue(float inX) const
 	if (mPoints.empty())
 	if (mPoints.empty())
 		return 0.0f;
 		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())
 	if (i2 == mPoints.begin())
 		return mPoints.front().mY;
 		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);
 	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)
 JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock)
 {
 {
 	free(inBlock);
 	free(inBlock);
@@ -57,6 +62,7 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(AlignedFree)(void *inBlock)
 #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
 #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
 
 
 AllocateFunction Allocate = nullptr;
 AllocateFunction Allocate = nullptr;
+ReallocateFunction Reallocate = nullptr;
 FreeFunction Free = nullptr;
 FreeFunction Free = nullptr;
 AlignedAllocateFunction AlignedAllocate = nullptr;
 AlignedAllocateFunction AlignedAllocate = nullptr;
 AlignedFreeFunction AlignedFree = nullptr;
 AlignedFreeFunction AlignedFree = nullptr;
@@ -64,6 +70,7 @@ AlignedFreeFunction AlignedFree = nullptr;
 void RegisterDefaultAllocator()
 void RegisterDefaultAllocator()
 {
 {
 	Allocate = AllocateImpl;
 	Allocate = AllocateImpl;
+	Reallocate = ReallocateImpl;
 	Free = FreeImpl;
 	Free = FreeImpl;
 	AlignedAllocate = AlignedAllocateImpl;
 	AlignedAllocate = AlignedAllocateImpl;
 	AlignedFree = AlignedFreeImpl;
 	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
 // 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 AllocateFunction = void *(*)(size_t inSize);
+using ReallocateFunction = void *(*)(void *inBlock, size_t inSize);
 using FreeFunction = void (*)(void *inBlock);
 using FreeFunction = void (*)(void *inBlock);
 
 
 // Aligned memory allocation
 // Aligned memory allocation
@@ -18,6 +19,7 @@ using AlignedFreeFunction = void (*)(void *inBlock);
 
 
 // User defined allocation / free functions
 // User defined allocation / free functions
 JPH_EXPORT extern AllocateFunction Allocate;
 JPH_EXPORT extern AllocateFunction Allocate;
+JPH_EXPORT extern ReallocateFunction Reallocate;
 JPH_EXPORT extern FreeFunction Free;
 JPH_EXPORT extern FreeFunction Free;
 JPH_EXPORT extern AlignedAllocateFunction AlignedAllocate;
 JPH_EXPORT extern AlignedAllocateFunction AlignedAllocate;
 JPH_EXPORT extern AlignedFreeFunction AlignedFree;
 JPH_EXPORT extern AlignedFreeFunction AlignedFree;
@@ -40,6 +42,7 @@ JPH_EXPORT void RegisterDefaultAllocator();
 
 
 // Directly define the allocation functions
 // Directly define the allocation functions
 JPH_EXPORT void *Allocate(size_t inSize);
 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 Free(void *inBlock);
 JPH_EXPORT void *AlignedAllocate(size_t inSize, size_t inAlignment);
 JPH_EXPORT void *AlignedAllocate(size_t inSize, size_t inAlignment);
 JPH_EXPORT void AlignedFree(void *inBlock);
 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);
 	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());
 	JPH_ASSERT(i != mThreads.end());
 	mThreads.erase(i);
 	mThreads.erase(i);
 }
 }

+ 12 - 0
Jolt/Core/STLAlignedAllocator.h

@@ -44,6 +44,18 @@ public:
 		return (pointer)AlignedAllocate(inN * sizeof(value_type), N);
 		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
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type)
 	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)));
 			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
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type)
 	inline void				deallocate(pointer inPointer, size_type)
 	{
 	{
@@ -84,7 +102,6 @@ template <typename T> using STLAllocator = std::allocator<T>;
 #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
 #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
 
 
 // Declare STL containers that use our 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 String = std::basic_string<char, std::char_traits<char>, STLAllocator<char>>;
 using IStringStream = std::basic_istringstream<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))));
 		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
 	/// Free memory
 	inline void				deallocate(pointer inPointer, size_type inN)
 	inline void				deallocate(pointer inPointer, size_type inN)
 	{
 	{

+ 2 - 0
Jolt/Core/StaticArray.h

@@ -4,6 +4,8 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <Jolt/Core/HashCombine.h>
+
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
 /// Simple variable length array backed by a fixed size buffer
 /// 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
 	/// 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>
 	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);
 		Read(len);
 		if (!IsEOF() && !IsFailed())
 		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>)
 			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
 				// 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]);
 					Read(outT[i]);
 			}
 			}
 			else
 			else
@@ -73,14 +73,14 @@ public:
 
 
 	/// Read a vector of primitives from the binary stream using a custom function to read the elements
 	/// Read a vector of primitives from the binary stream using a custom function to read the elements
 	template <class T, class A, typename F>
 	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);
 		Read(len);
 		if (!IsEOF() && !IsFailed())
 		if (!IsEOF() && !IsFailed())
 		{
 		{
 			outT.resize(len);
 			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]);
 				inReadElement(*this, outT[i]);
 		}
 		}
 		else
 		else

+ 6 - 6
Jolt/Core/StreamOut.h

@@ -30,16 +30,16 @@ public:
 
 
 	/// Write a vector of primitives to the binary stream
 	/// 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>
 	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);
 		Write(len);
 		if (!IsFailed())
 		if (!IsFailed())
 		{
 		{
 			if constexpr (std::is_same_v<T, Vec3> || std::is_same_v<T, DVec3> || std::is_same_v<T, DMat44>)
 			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
 				// 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]);
 					Write(inT[i]);
 			}
 			}
 			else
 			else
@@ -62,12 +62,12 @@ public:
 
 
 	/// Write a vector of primitives to the binary stream using a custom write function
 	/// Write a vector of primitives to the binary stream using a custom write function
 	template <class T, class A, typename F>
 	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);
 		Write(len);
 		if (!IsFailed())
 		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);
 				inWriteElement(inT[i], *this);
 	}
 	}
 
 

+ 3 - 4
Jolt/Geometry/ConvexHullBuilder.cpp

@@ -225,8 +225,7 @@ bool ConvexHullBuilder::AssignPointToFace(int inPositionIdx, const Faces &inFace
 			else
 			else
 			{
 			{
 				// Not the furthest point, add it as the before last point
 				// 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;
 			return true;
@@ -265,7 +264,7 @@ bool ConvexHullBuilder::ContainsFace(const Array<int> &inIndices) const
 	for (Face *f : mFaces)
 	for (Face *f : mFaces)
 	{
 	{
 		Edge *e = f->mFirstEdge;
 		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())
 		if (index != inIndices.end())
 		{
 		{
 			size_t matches = 0;
 			size_t matches = 0;
@@ -1007,7 +1006,7 @@ void ConvexHullBuilder::MergeCoplanarOrConcaveFaces(Face *inFace, float inCoplan
 
 
 void ConvexHullBuilder::sMarkAffected(Face *inFace, Faces &ioAffectedFaces)
 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);
 		ioAffectedFaces.push_back(inFace);
 }
 }
 
 

+ 1 - 2
Jolt/Geometry/ConvexHullBuilder2D.cpp

@@ -142,8 +142,7 @@ void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge
 		else
 		else
 		{
 		{
 			// Not the furthest point, add it as the before last point
 			// 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/NodeCodec/NodeCodecQuadTreeHalfFloat.h
 	${JOLT_PHYSICS_ROOT}/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
 	${JOLT_PHYSICS_ROOT}/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
 	${JOLT_PHYSICS_ROOT}/Core/ARMNeon.h
 	${JOLT_PHYSICS_ROOT}/Core/ARMNeon.h
+	${JOLT_PHYSICS_ROOT}/Core/Array.h
 	${JOLT_PHYSICS_ROOT}/Core/Atomics.h
 	${JOLT_PHYSICS_ROOT}/Core/Atomics.h
 	${JOLT_PHYSICS_ROOT}/Core/ByteBuffer.h
 	${JOLT_PHYSICS_ROOT}/Core/ByteBuffer.h
 	${JOLT_PHYSICS_ROOT}/Core/Color.cpp
 	${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})
 	target_compile_definitions(Jolt PUBLIC JPH_OBJECT_LAYER_BITS=${OBJECT_LAYER_BITS})
 endif()
 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
 # Setting to periodically trace broadphase stats to help determine if the broadphase layer configuration is optimal
 if (TRACK_BROADPHASE_STATS)
 if (TRACK_BROADPHASE_STATS)
 	target_compile_definitions(Jolt PUBLIC JPH_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/Core.h>
 #include <Jolt/Core/ARMNeon.h>
 #include <Jolt/Core/ARMNeon.h>
 #include <Jolt/Core/Memory.h>
 #include <Jolt/Core/Memory.h>
-#include <Jolt/Core/STLAllocator.h>
 #include <Jolt/Core/IssueReporting.h>
 #include <Jolt/Core/IssueReporting.h>
+#include <Jolt/Core/Array.h>
 #include <Jolt/Math/Math.h>
 #include <Jolt/Math/Math.h>
 #include <Jolt/Math/Vec4.h>
 #include <Jolt/Math/Vec4.h>
 #include <Jolt/Math/Mat44.h>
 #include <Jolt/Math/Mat44.h>

+ 94 - 83
Jolt/Jolt.natvis

@@ -1,86 +1,97 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
 <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>
 </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;
 	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);
 	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
 JPH_NAMESPACE_END

+ 1 - 1
Jolt/Math/Vec3.h

@@ -104,7 +104,7 @@ public:
 	static JPH_INLINE Vec3		sUnitSpherical(float inTheta, float inPhi);
 	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
 	/// 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
 	/// Get random unit vector
 	template <class Random>
 	template <class Random>

+ 2 - 2
Jolt/ObjectStream/GetPrimitiveTypeOfType.h

@@ -33,8 +33,8 @@ const RTTI *GetPrimitiveTypeOfType(RefConst<T> *)
 	return GetRTTIOfType((T *)nullptr);
 	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);
 	return GetPrimitiveTypeOfType((T *)nullptr);
 }
 }

+ 8 - 8
Jolt/ObjectStream/ObjectStream.h

@@ -119,8 +119,8 @@ public:
 #include <Jolt/ObjectStream/ObjectStreamTypes.h>
 #include <Jolt/ObjectStream/ObjectStreamTypes.h>
 
 
 // Define serialization templates
 // 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));
 	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
 /// 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;
 	bool continue_reading = true;
 
 
@@ -230,15 +230,15 @@ bool OSReadData(IObjectStreamIn &ioStream, RefConst<T> &inRef)
 }
 }
 
 
 // Define serialization templates for dynamic arrays
 // 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);
 	ioStream.WriteDataType(EOSDataType::Array);
 	OSWriteDataType(ioStream, static_cast<T *>(nullptr));
 	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
 	// Write size of array
 	ioStream.HintNextItem();
 	ioStream.HintNextItem();

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

@@ -76,7 +76,7 @@ public:
 	/// Filter function. Returns true if we should collide with inBodyID
 	/// Filter function. Returns true if we should collide with inBodyID
 	virtual bool			ShouldCollide(const BodyID &inBodyID) const override
 	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:
 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
 	// Allocate space for sequence numbers
-	mBodySequenceNumbers.resize(inMaxBodies);
+	mBodySequenceNumbers.resize(inMaxBodies, 0);
 
 
 	// Keep layer interface
 	// Keep layer interface
 	mBroadPhaseLayerInterface = &inLayerInterface;
 	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)
 	// 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());
 	sorted_constraints.resize(ioConstraints.size());
 	for (size_t index = 0; index < sorted_constraints.size(); index++)
 	for (size_t index = 0; index < sorted_constraints.size(); index++)
 		sorted_constraints[index] = &ioConstraints[index];
 		sorted_constraints[index] = &ioConstraints[index];
@@ -525,7 +525,7 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, float inDeltaTime, f
 	outTimeSimulated = 0.0f;
 	outTimeSimulated = 0.0f;
 
 
 	// These are the contacts that we hit previously without moving a significant distance
 	// 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);
 	previous_contacts.resize(mMaxConstraintIterations);
 	int num_previous_contacts = 0;
 	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.
 	// We need to do this before calling MoveShape because it will update mActiveContacts.
 	Vec3 character_velocity = inStepForward / inDeltaTime;
 	Vec3 character_velocity = inStepForward / inDeltaTime;
 	Vec3 horizontal_velocity = character_velocity - character_velocity.Dot(mUp) * mUp;
 	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());
 	steep_slope_normals.reserve(mActiveContacts.size());
 	for (const Contact &c : mActiveContacts)
 	for (const Contact &c : mActiveContacts)
 		if (c.mHadCollision
 		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
 		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>;
 	using ContactList = Array<Contact>;
 
 
 	/// Access to the internal list of contacts that the character has found.
 	/// 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
 		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
 	// A constraint that limits the movement of the character
 	struct Constraint
 	struct Constraint
@@ -357,7 +357,7 @@ private:
 		bool							mIsSteepSlope = false;									///< If this constraint belongs to a steep slope
 		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
 	// Collision collector that collects hits for CollideShape
 	class ContactCollector : public CollideShapeCollector
 	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());
 		JPH_ASSERT(body.IsInBroadPhase());
 
 
 		// Find body id
 		// 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());
 		JPH_ASSERT(it != mBodyIDs.end());
 
 
 		// Remove element
 		// 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 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);
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereTop(verts, cCapsuleDetailLevel);
 	return verts;
 	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);
 	GetTrianglesContextVertexList::sCreateUnitOpenCylinder(verts, cCapsuleDetailLevel);
 	return verts;
 	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);
 	GetTrianglesContextVertexList::sCreateHalfUnitSphereBottom(verts, cCapsuleDetailLevel);
 	return verts;
 	return verts;
 }();
 }();

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

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

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

@@ -132,7 +132,7 @@ protected:
 	virtual void					RestoreBinaryState(StreamIn &inStream) override;
 	virtual void					RestoreBinaryState(StreamIn &inStream) override;
 
 
 	/// Vertex list that forms a unit sphere
 	/// Vertex list that forms a unit sphere
-	static const std::vector<Vec3>	sUnitSphereTriangles;
+	static const Array<Vec3, std::allocator<Vec3>> sUnitSphereTriangles;
 
 
 private:
 private:
 	// Class for GetTrianglesStart/Next
 	// 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)
 	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);
 	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)
 	/// 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::sAxisX(),  Vec3::sAxisY(),  Vec3::sAxisZ(), inDetailLevel);
 		sCreateUnitSphereHelper(ioVertices,  Vec3::sAxisY(), -Vec3::sAxisX(),  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)
 	/// 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::sAxisX(), -Vec3::sAxisY(),  Vec3::sAxisZ(), inDetailLevel);
 		sCreateUnitSphereHelper(ioVertices, -Vec3::sAxisY(),  Vec3::sAxisX(),  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
 	/// 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);
 		const Vec3 bottom_offset(0.0f, -2.0f, 0.0f);
 		int num_verts = 4 * (1 << inDetailLevel);
 		int num_verts = 4 * (1 << inDetailLevel);
@@ -112,7 +112,7 @@ public:
 
 
 private:
 private:
 	/// Recursive helper function for creating a sphere
 	/// 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 center1 = (inV1 + inV2).Normalized();
 		Vec3 center2 = (inV2 + inV3).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),
 	mScale(inScale),
 	mSampleCount(inSampleCount)
 	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)
 	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;
 		mMaterials = inMaterialList;
 	}
 	}
 	else
 	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.
 		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
 		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,
 	// 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)
 	// 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
 	// Now clear the edges that are not active
 	TempAllocatorMalloc allocator;
 	TempAllocatorMalloc allocator;
@@ -357,7 +354,7 @@ void HeightFieldShape::StoreMaterialIndices(const HeightFieldShapeSettings &inSe
 	uint out_count_min_1 = mSampleCount - 1;
 	uint out_count_min_1 = mSampleCount - 1;
 
 
 	mNumBitsPerMaterialIndex = 32 - CountLeadingZeros((uint32)mMaterials.size() - 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 y = 0; y < out_count_min_1; ++y)
 		for (uint x = 0; x < out_count_min_1; ++x)
 		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));
 	JPH_ASSERT(mRangeBlocks.size() == sGridOffsets[ranges.size() - 1] + Square(max_stride));
 
 
 	// Quantize height samples
 	// 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;
 	int sample = 0;
 	for (uint y = 0; y < mSampleCount; ++y)
 	for (uint y = 0; y < mSampleCount; ++y)
 		for (uint x = 0; x < mSampleCount; ++x)
 		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)
 	if (new_bits_per_material_index != mNumBitsPerMaterialIndex)
 	{
 	{
 		// Resize the material indices array
 		// 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
 		// Calculate old and new mask
 		uint16 old_material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);
 		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);
 	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);
 	mStepListeners.push_back(inListener);
 }
 }
 
 
@@ -117,7 +117,7 @@ void PhysicsSystem::RemoveStepListener(PhysicsStepListener *inListener)
 {
 {
 	lock_guard lock(mStepListenersMutex);
 	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());
 	JPH_ASSERT(i != mStepListeners.end());
 	*i = mStepListeners.back();
 	*i = mStepListeners.back();
 	mStepListeners.pop_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)
 		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
 	/// 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
 	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
 #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
 #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
 
 
 	JPH_ASSERT(Factory::sInstance != nullptr, "Need to create a factory first!");
 	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)
 			if (cur == start)
 			{
 			{
 				// Reverse the chains
 				// 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
 				// Mark elements mapped
 				for (int j1 : chain1)
 				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;
 		outGroupedTriangleIndices[t] = t;
 	}
 	}
 
 
-	Array<uint>::iterator triangles_end = outGroupedTriangleIndices.end();
+	Array<uint>::const_iterator triangles_end = outGroupedTriangleIndices.end();
 
 
 	// Sort per batch
 	// Sort per batch
 	for (uint b = 0; b < num_batches - 1; ++b)
 	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();
 						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;
 				*upper = other_val;
 
 
 				// Calculate new furthest distance
 				// 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');
 	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)
 static void FreeHook(void *inBlock)
 {
 {
 	InCustomAllocator ica;
 	InCustomAllocator ica;
@@ -91,6 +97,7 @@ static int MyAllocHook(int nAllocType, void *pvData, size_t nSize, int nBlockUse
 void RegisterCustomMemoryHook()
 void RegisterCustomMemoryHook()
 {
 {
 	Allocate = AllocateHook;
 	Allocate = AllocateHook;
+	Reallocate = ReallocateHook;
 	Free = FreeHook;
 	Free = FreeHook;
 	AlignedAllocate = AlignedAllocateHook;
 	AlignedAllocate = AlignedAllocateHook;
 	AlignedFree = AlignedFreeHook;
 	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:
 		public:
 			bool						ShouldSaveBody(const BodyID &inBodyID) const
 			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
 			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
 # Source files
 set(UNIT_TESTS_SRC_FILES
 set(UNIT_TESTS_SRC_FILES
+	${UNIT_TESTS_ROOT}/Core/ArrayTest.cpp
 	${UNIT_TESTS_ROOT}/Core/FPFlushDenormalsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/FPFlushDenormalsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/InsertionSortTest.cpp
 	${UNIT_TESTS_ROOT}/Core/InsertionSortTest.cpp
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp