// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors. // All rights reserved. // Code licensed under the BSD License. // http://www.anki3d.org/LICENSE #pragma once #include #include #include #include #include // For ptrdiff_t #include // For forward #include // For placement new #include // For some checks namespace anki { /// @addtogroup util_memory /// @{ /// Pool based allocator /// /// This is a template that accepts memory pools with a specific interface /// /// @tparam T The type /// /// @note Don't ever EVER remove the double copy constructor and the double operator=. The compiler will create defaults template class GenericPoolAllocator { template friend class GenericPoolAllocator; public: // Typedefs using size_type = size_t; using difference_type = ptrdiff_t; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using value_type = T; /// Move assignments between containers will copy the allocator as well. If propagate_on_container_move_assignment /// is not defined then not moves are going to happen. using propagate_on_container_move_assignment = std::true_type; /// A struct to rebind the allocator to another allocator of type Y template struct rebind { using other = GenericPoolAllocator; }; /// Default constructor GenericPoolAllocator() { } /// Copy constructor GenericPoolAllocator(const GenericPoolAllocator& b) { *this = b; } /// Copy constructor template GenericPoolAllocator(const GenericPoolAllocator& b) { *this = b; } /// Copy constructor, uses another type of allocator template GenericPoolAllocator(const GenericPoolAllocator& b) { auto balloc = b; m_pool = balloc.m_pool; if(m_pool) { m_pool->retain(); } } /// Constuctor that creates a pool template GenericPoolAllocator(AllocAlignedCallback allocCb, void* allocCbUserData, TArgs&&... args) { m_pool = static_cast(allocCb(allocCbUserData, nullptr, sizeof(TPool), alignof(TPool))); if(!m_pool) [[unlikely]] { ANKI_UTIL_LOGF("Out of memory"); } ::new(m_pool) TPool(); m_pool->init(allocCb, allocCbUserData, std::forward(args)...); m_pool->retain(); } /// Destructor ~GenericPoolAllocator() { clear(); } /// Copy GenericPoolAllocator& operator=(const GenericPoolAllocator& b) { copy(b); return *this; } /// Copy template GenericPoolAllocator& operator=(const GenericPoolAllocator& b) { copy(b); return *this; } /// Get the address of a reference pointer address(reference x) const { return &x; } /// Get the const address of a const reference const_pointer address(const_reference x) const { return &x; } /// Allocate memory /// @param n The elements of type T to allocate /// @param hint It's been used to override the alignment. The type should be PtrSize. pointer allocate(size_type n, [[maybe_unused]] const void* hint = nullptr) { ANKI_ASSERT(m_pool); size_type size = n * sizeof(value_type); // Operator new doesn't respect alignment (in GCC at least) so use the type's alignment. If hint override the // alignment PtrSize alignment = (hint != nullptr) ? *static_cast(hint) : alignof(value_type); void* out = m_pool->allocate(size, alignment); if(out == nullptr) [[unlikely]] { ANKI_UTIL_LOGF("Out of memory"); } return static_cast(out); } /// Allocate memory /// @param n The elements of type T to allocate /// @param alignment The alignment of the allocation. /// /// @note It's not part of the STL interface pointer allocate(size_type n, U32 alignment) { PtrSize hint = alignment; return allocate(n, &hint); } /// Deallocate memory void deallocate(void* p, [[maybe_unused]] size_type n) { ANKI_ASSERT(m_pool); m_pool->free(p); } /// Call constructor void construct(pointer p, const T& val) { ::new(p) T(val); } /// Call constructor with many arguments template void construct(Y* p, Args&&... args) { // Placement new ::new(static_cast(p)) Y(std::forward(args)...); } /// Call default constructor only for non-trivially constructible types. template void construct(Y* p) { if(!std::is_trivially_constructible::value) { ::new(static_cast(p)) Y(); } } /// Call destructor void destroy(pointer p) { static_assert(sizeof(T) > 0, "Incomplete type"); ANKI_ASSERT(p != nullptr); p->~T(); } /// Call destructor template void destroy(Y* p) { static_assert(sizeof(T) > 0, "Incomplete type"); ANKI_ASSERT(p != nullptr); p->~Y(); } /// Get the max allocation size size_type max_size() const { return kMaxPtrSize; } /// Get the memory pool /// @note This is AnKi specific const TPool& getMemoryPool() const { ANKI_ASSERT(m_pool); return *m_pool; } /// Get the memory pool /// @note This is AnKi specific TPool& getMemoryPool() { ANKI_ASSERT(m_pool); return *m_pool; } /// Allocate a new object and call it's constructor /// @note This is AnKi specific template Y* newInstance(Args&&... args) { typename rebind::other alloc(*this); Y* ptr = alloc.allocate(1); if(ptr) { alloc.construct(ptr, std::forward(args)...); } return ptr; } /// Allocate a new array of objects and call their constructor /// @note This is AnKi specific template Y* newArray(size_type n) { typename rebind::other alloc(*this); Y* ptr = alloc.allocate(n); if(ptr) { // Call the constuctors for(size_type i = 0; i < n; i++) { alloc.construct(&ptr[i]); } } return ptr; } /// Allocate a new array of objects and call their constructor /// @note This is AnKi specific template Y* newArray(size_type n, const Y& v) { typename rebind::other alloc(*this); Y* ptr = alloc.allocate(n); if(ptr) { // Call the constuctors for(size_type i = 0; i < n; i++) { alloc.construct(&ptr[i], v); } } return ptr; } /// Allocate a new array of objects and call their constructor. /// @note This is AnKi specific. /// @note The output is a parameter to work with template deduction. template void newArray(size_type n, WeakArray& out) { TValue* arr = newArray(n); ANKI_ASSERT(n < std::numeric_limits::max()); out.setArray(arr, TSize(n)); } /// Allocate a new array of objects and call their constructor. /// @note This is AnKi specific. /// @note The output is a parameter to work with template deduction. template void newArray(size_type n, const TValue& v, WeakArray& out) { TValue* arr = newArray(n, v); ANKI_ASSERT(n < std::numeric_limits::max()); out.setArray(arr, TSize(n)); } /// Call the destructor and deallocate an object /// @note This is AnKi specific template void deleteInstance(Y* ptr) { if(ptr != nullptr) { typename rebind::other alloc(*this); alloc.destroy(ptr); alloc.deallocate(ptr, 1); } } /// Call the destructor and deallocate an array of objects /// @note This is AnKi specific template void deleteArray(Y* ptr, size_type n) { typename rebind::other alloc(*this); if(ptr != nullptr) { // Call the destructors for(size_type i = 0; i < n; i++) { alloc.destroy(&ptr[i]); } alloc.deallocate(ptr, n); } else { ANKI_ASSERT(n == 0); } } /// Call the destructor and deallocate an array of objects /// @note This is AnKi specific template void deleteArray(WeakArray& arr) { deleteArray(arr.getBegin(), arr.getSize()); arr.setArray(nullptr, 0); } private: TPool* m_pool = nullptr; template void copy(const GenericPoolAllocator& b) { clear(); if(b.m_pool) { m_pool = b.m_pool; m_pool->retain(); } } void clear() { if(m_pool) { auto count = m_pool->release(); if(count == 1) { auto allocCb = m_pool->getAllocationCallback(); auto ud = m_pool->getAllocationCallbackUserData(); ANKI_ASSERT(allocCb); m_pool->~TPool(); allocCb(ud, m_pool, 0, 0); } m_pool = nullptr; } } }; /// @name GenericPoolAllocator global functions /// @{ /// Another allocator of the same type can deallocate from this one template inline Bool operator==(const GenericPoolAllocator&, const GenericPoolAllocator&) { return true; } /// Another allocator of the another type cannot deallocate from this one template inline Bool operator==(const GenericPoolAllocator&, const AnotherAllocator&) { return false; } /// Another allocator of the same type can deallocate from this one template inline Bool operator!=(const GenericPoolAllocator&, const GenericPoolAllocator&) { return false; } /// Another allocator of the another type cannot deallocate from this one template inline Bool operator!=(const GenericPoolAllocator&, const AnotherAllocator&) { return true; } /// @} /// Allocator using the base memory pool. template using GenericMemoryPoolAllocator = GenericPoolAllocator>; /// Heap based allocator. The default allocator. It uses malloc and free for allocations/deallocations template using HeapAllocator = GenericPoolAllocator>; /// Allocator that uses a StackMemoryPool template using StackAllocator = GenericPoolAllocator>; #define ANKI_FRIEND_ALLOCATOR \ template \ friend class GenericPoolAllocator; /// @} } // end namespace anki