STLLocalAllocator.h 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2025 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #pragma once
  5. #include <Jolt/Core/STLAllocator.h>
  6. JPH_NAMESPACE_BEGIN
  7. #ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
  8. /// STL allocator that keeps N elements in a local buffer before falling back to regular allocations
  9. template <typename T, size_t N>
  10. class STLLocalAllocator : private STLAllocator<T>
  11. {
  12. using Base = STLAllocator<T>;
  13. public:
  14. /// General properties
  15. using value_type = T;
  16. using pointer = T *;
  17. using const_pointer = const T *;
  18. using reference = T &;
  19. using const_reference = const T &;
  20. using size_type = size_t;
  21. using difference_type = ptrdiff_t;
  22. /// The allocator is not stateless (has local buffer)
  23. using is_always_equal = std::false_type;
  24. /// We cannot copy, move or swap allocators
  25. using propagate_on_container_copy_assignment = std::false_type;
  26. using propagate_on_container_move_assignment = std::false_type;
  27. using propagate_on_container_swap = std::false_type;
  28. /// Constructor
  29. STLLocalAllocator() = default;
  30. STLLocalAllocator(const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
  31. STLLocalAllocator(STLLocalAllocator &&) = delete; // Can't move an allocator as the buffer is local to the original
  32. STLLocalAllocator & operator = (const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
  33. /// Constructor used when rebinding to another type. This expects the allocator to use the original memory pool from the first allocator,
  34. /// but in our case we cannot use the local buffer of the original allocator as it has different size and alignment rules.
  35. /// To solve this we make this allocator fall back to the heap immediately.
  36. template <class T2>
  37. explicit STLLocalAllocator(const STLLocalAllocator<T2, N> &) : mNumElementsUsed(N) { }
  38. /// Check if inPointer is in the local buffer
  39. inline bool is_local(const_pointer inPointer) const
  40. {
  41. ptrdiff_t diff = inPointer - reinterpret_cast<const_pointer>(mElements);
  42. return diff >= 0 && diff < ptrdiff_t(N);
  43. }
  44. /// Allocate memory
  45. inline pointer allocate(size_type inN)
  46. {
  47. // If we allocate more than we have, fall back to the heap
  48. if (mNumElementsUsed + inN > N)
  49. return Base::allocate(inN);
  50. // Allocate from our local buffer
  51. pointer result = reinterpret_cast<pointer>(mElements) + mNumElementsUsed;
  52. mNumElementsUsed += inN;
  53. return result;
  54. }
  55. /// Always implements a reallocate function as we can often reallocate in place
  56. static constexpr bool has_reallocate = true;
  57. /// Reallocate memory
  58. inline pointer reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
  59. {
  60. JPH_ASSERT(inNewSize > 0); // Reallocating to zero size is implementation dependent, so we don't allow it
  61. // If there was no previous allocation, we can go through the regular allocate function
  62. if (inOldPointer == nullptr)
  63. return allocate(inNewSize);
  64. // If the pointer is outside our local buffer, fall back to the heap
  65. if (!is_local(inOldPointer))
  66. {
  67. if constexpr (AllocatorHasReallocate<Base>::sValue)
  68. return Base::reallocate(inOldPointer, inOldSize, inNewSize);
  69. else
  70. return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
  71. }
  72. // If we happen to have space left, we only need to update our bookkeeping
  73. pointer base_ptr = reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inOldSize;
  74. if (inOldPointer == base_ptr
  75. && mNumElementsUsed - inOldSize + inNewSize <= N)
  76. {
  77. mNumElementsUsed += inNewSize - inOldSize;
  78. return base_ptr;
  79. }
  80. // We can't reallocate in place, fall back to the heap
  81. return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
  82. }
  83. /// Free memory
  84. inline void deallocate(pointer inPointer, size_type inN)
  85. {
  86. // If the pointer is not in our local buffer, fall back to the heap
  87. if (!is_local(inPointer))
  88. return Base::deallocate(inPointer, inN);
  89. // Else we can only reclaim memory if it was the last allocation
  90. if (inPointer == reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inN)
  91. mNumElementsUsed -= inN;
  92. }
  93. /// Allocators are not-stateless, assume if allocator address matches that the allocators are the same
  94. inline bool operator == (const STLLocalAllocator<T, N> &inRHS) const
  95. {
  96. return this == &inRHS;
  97. }
  98. inline bool operator != (const STLLocalAllocator<T, N> &inRHS) const
  99. {
  100. return this != &inRHS;
  101. }
  102. /// Converting to allocator for other type
  103. template <typename T2>
  104. struct rebind
  105. {
  106. using other = STLLocalAllocator<T2, N>;
  107. };
  108. private:
  109. /// Implements reallocate when the base class doesn't or when we go from local buffer to heap
  110. inline pointer ReallocateImpl(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
  111. {
  112. pointer new_pointer = Base::allocate(inNewSize);
  113. size_type n = min(inOldSize, inNewSize);
  114. if constexpr (std::is_trivially_copyable<T>())
  115. {
  116. // Can use mem copy
  117. memcpy(new_pointer, inOldPointer, n * sizeof(T));
  118. }
  119. else
  120. {
  121. // Need to actually move the elements
  122. for (size_t i = 0; i < n; ++i)
  123. {
  124. new (new_pointer + i) T(std::move(inOldPointer[i]));
  125. inOldPointer[i].~T();
  126. }
  127. }
  128. deallocate(inOldPointer, inOldSize);
  129. return new_pointer;
  130. }
  131. alignas(T) uint8 mElements[N * sizeof(T)];
  132. size_type mNumElementsUsed = 0;
  133. };
  134. /// The STLLocalAllocator always implements a reallocate function as it can often reallocate in place
  135. template <class T, size_t N> struct AllocatorHasReallocate<STLLocalAllocator<T, N>> { static constexpr bool sValue = STLLocalAllocator<T, N>::has_reallocate; };
  136. #else
  137. template <typename T, size_t N> using STLLocalAllocator = std::allocator<T>;
  138. #endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
  139. JPH_NAMESPACE_END