|
@@ -0,0 +1,169 @@
|
|
|
|
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
|
|
|
+// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
|
|
|
|
+// SPDX-License-Identifier: MIT
|
|
|
|
+
|
|
|
|
+#pragma once
|
|
|
|
+
|
|
|
|
+#include <Jolt/Core/STLAllocator.h>
|
|
|
|
+
|
|
|
|
+JPH_NAMESPACE_BEGIN
|
|
|
|
+
|
|
|
|
+#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
|
|
|
+
|
|
|
|
+/// STL allocator that keeps N elements in a local buffer before falling back to regular allocations
|
|
|
|
+template <typename T, size_t N>
|
|
|
|
+class STLLocalAllocator : private STLAllocator<T>
|
|
|
|
+{
|
|
|
|
+ using Base = STLAllocator<T>;
|
|
|
|
+
|
|
|
|
+public:
|
|
|
|
+ /// General properties
|
|
|
|
+ using value_type = T;
|
|
|
|
+ using pointer = T *;
|
|
|
|
+ using const_pointer = const T *;
|
|
|
|
+ using reference = T &;
|
|
|
|
+ using const_reference = const T &;
|
|
|
|
+ using size_type = size_t;
|
|
|
|
+ using difference_type = ptrdiff_t;
|
|
|
|
+
|
|
|
|
+ /// The allocator is not stateless (has local buffer)
|
|
|
|
+ using is_always_equal = std::false_type;
|
|
|
|
+
|
|
|
|
+ /// We cannot copy, move or swap allocators
|
|
|
|
+ using propagate_on_container_copy_assignment = std::false_type;
|
|
|
|
+ using propagate_on_container_move_assignment = std::false_type;
|
|
|
|
+ using propagate_on_container_swap = std::false_type;
|
|
|
|
+
|
|
|
|
+ /// Constructor
|
|
|
|
+ STLLocalAllocator() = default;
|
|
|
|
+ STLLocalAllocator(const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
|
|
|
|
+ STLLocalAllocator(STLLocalAllocator &&) = delete; // Can't move an allocator as the buffer is local to the original
|
|
|
|
+ STLLocalAllocator & operator = (const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
|
|
|
|
+
|
|
|
|
+ /// Constructor used when rebinding to another type. This expects the allocator to use the original memory pool from the first allocator,
|
|
|
|
+ /// but in our case we cannot use the local buffer of the original allocator as it has different size and alignment rules.
|
|
|
|
+ /// To solve this we make this allocator fall back to the heap immediately.
|
|
|
|
+ template <class T2> STLLocalAllocator(const STLLocalAllocator<T2, N> &) : mNumElementsUsed(N) { }
|
|
|
|
+
|
|
|
|
+ /// Check if inPointer is in the local buffer
|
|
|
|
+ inline bool is_local(const_pointer inPointer) const
|
|
|
|
+ {
|
|
|
|
+ ptrdiff_t diff = inPointer - reinterpret_cast<const_pointer>(mElements);
|
|
|
|
+ return diff >= 0 && diff < ptrdiff_t(N);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Allocate memory
|
|
|
|
+ inline pointer allocate(size_type inN)
|
|
|
|
+ {
|
|
|
|
+ // If we allocate more than we have, fall back to the heap
|
|
|
|
+ if (mNumElementsUsed + inN > N)
|
|
|
|
+ return Base::allocate(inN);
|
|
|
|
+
|
|
|
|
+ // Allocate from our local buffer
|
|
|
|
+ pointer result = reinterpret_cast<pointer>(mElements) + mNumElementsUsed;
|
|
|
|
+ mNumElementsUsed += inN;
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Always implements a reallocate function as we can often reallocate in place
|
|
|
|
+ static constexpr bool has_reallocate = true;
|
|
|
|
+
|
|
|
|
+ /// Reallocate memory
|
|
|
|
+ inline pointer reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
|
|
|
|
+ {
|
|
|
|
+ JPH_ASSERT(inNewSize > 0); // Reallocating to zero size is implementation dependent, so we don't allow it
|
|
|
|
+
|
|
|
|
+ // If there was no previous allocation, we can go through the regular allocate function
|
|
|
|
+ if (inOldPointer == nullptr)
|
|
|
|
+ return allocate(inNewSize);
|
|
|
|
+
|
|
|
|
+ // If the pointer is outside our local buffer, fall back to the heap
|
|
|
|
+ if (!is_local(inOldPointer))
|
|
|
|
+ {
|
|
|
|
+ if constexpr (AllocatorHasReallocate<Base>::sValue)
|
|
|
|
+ return Base::reallocate(inOldPointer, inOldSize, inNewSize);
|
|
|
|
+ else
|
|
|
|
+ return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If we happen to have space left, we only need to update our bookkeeping
|
|
|
|
+ pointer base_ptr = reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inOldSize;
|
|
|
|
+ if (inOldPointer == base_ptr
|
|
|
|
+ && mNumElementsUsed - inOldSize + inNewSize <= N)
|
|
|
|
+ {
|
|
|
|
+ mNumElementsUsed += inNewSize - inOldSize;
|
|
|
|
+ return base_ptr;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // We can't reallocate in place, fall back to the heap
|
|
|
|
+ return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Free memory
|
|
|
|
+ inline void deallocate(pointer inPointer, size_type inN)
|
|
|
|
+ {
|
|
|
|
+ // If the pointer is not in our local buffer, fall back to the heap
|
|
|
|
+ if (!is_local(inPointer))
|
|
|
|
+ return Base::deallocate(inPointer, inN);
|
|
|
|
+
|
|
|
|
+ // Else we can only reclaim memory if it was the last allocation
|
|
|
|
+ if (inPointer == reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inN)
|
|
|
|
+ mNumElementsUsed -= inN;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Allocators are not-stateless, assume if allocator address matches that the allocators are the same
|
|
|
|
+ inline bool operator == (const STLLocalAllocator<T, N> &inRHS) const
|
|
|
|
+ {
|
|
|
|
+ return this == &inRHS;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ inline bool operator != (const STLLocalAllocator<T, N> &inRHS) const
|
|
|
|
+ {
|
|
|
|
+ return this != &inRHS;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Converting to allocator for other type
|
|
|
|
+ template <typename T2>
|
|
|
|
+ struct rebind
|
|
|
|
+ {
|
|
|
|
+ using other = STLLocalAllocator<T2, N>;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+private:
|
|
|
|
+ /// Implements reallocate when the base class doesn't or when we go from local buffer to heap
|
|
|
|
+ inline pointer ReallocateImpl(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
|
|
|
|
+ {
|
|
|
|
+ pointer new_pointer = Base::allocate(inNewSize);
|
|
|
|
+ size_type n = min(inOldSize, inNewSize);
|
|
|
|
+ if constexpr (std::is_trivially_copyable<T>())
|
|
|
|
+ {
|
|
|
|
+ // Can use mem copy
|
|
|
|
+ memcpy(new_pointer, inOldPointer, n * sizeof(T));
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Need to actually move the elements
|
|
|
|
+ for (size_t i = 0; i < n; ++i)
|
|
|
|
+ {
|
|
|
|
+ new (new_pointer + i) T(std::move(inOldPointer[i]));
|
|
|
|
+ inOldPointer[i].~T();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ deallocate(inOldPointer, inOldSize);
|
|
|
|
+ return new_pointer;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ alignas(T) uint8 mElements[N * sizeof(T)];
|
|
|
|
+ size_type mNumElementsUsed = 0;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/// The STLLocalAllocator always implements a reallocate function as it can often reallocate in place
|
|
|
|
+template <class T, size_t N> struct AllocatorHasReallocate<STLLocalAllocator<T, N>> { static constexpr bool sValue = STLLocalAllocator<T, N>::has_reallocate; };
|
|
|
|
+
|
|
|
|
+#else
|
|
|
|
+
|
|
|
|
+template <typename T, size_t N> using STLLocalAllocator = std::allocator<T>;
|
|
|
|
+
|
|
|
|
+#endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
|
|
|
|
+
|
|
|
|
+JPH_NAMESPACE_END
|