#pragma once #include "CoreTypes.h" #include "Memory/Memory.h" #include "Templates/Noncopyable.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/AssertionMacros.h" NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) struct FAllocatorInterface; template concept CAllocatableObject = CObject && !CConst && !CVolatile && CDestructible; template concept CAllocator = !CSameAs && CAllocatableObject && requires (typename A::template ForElementType& Allocator, T* InPtr, size_t Num, size_t NumAllocated) { { Allocator.Allocate(Num) } -> CSameAs; { Allocator.Deallocate(InPtr) } -> CSameAs; { AsConst(Allocator).IsTransferable(InPtr) } -> CBooleanTestable; { AsConst(Allocator).CalculateSlackGrow(Num, NumAllocated) } -> CSameAs; { AsConst(Allocator).CalculateSlackShrink(Num, NumAllocated) } -> CSameAs; { AsConst(Allocator).CalculateSlackReserve(Num) } -> CSameAs; }; template concept CMultipleAllocator = CAllocator && A::bSupportsMultipleAllocation; /** * This is the allocator interface, the allocator does not use virtual, this contains the default of * the allocator interface functions. Unlike std::allocator, IAllocator should be bound to only a object, * such as a container, because there may be side effects between multiple allocations, for example, * inline storage cannot be allocated multiple times in TInlineAllocator. */ struct FAllocatorInterface { /** * If this flag is false, it is possible to allocate an address that has already been allocated. * Should be allocated according to the results given by the CalculateSlackReserve() family, * without needing to allocate memory of the same size as the allocated memory, * this is to support special allocators such as TInlineAllocator. */ static constexpr bool bSupportsMultipleAllocation = true; template class ForElementType /*: private FSingleton*/ { public: ForElementType() = default; ForElementType(const ForElementType&) = delete; ForElementType(ForElementType&&) = delete; ForElementType& operator=(const ForElementType&) = delete; ForElementType& operator=(ForElementType&&) = delete; /** Allocates uninitialized storage. If 'InNum' is zero, return nullptr. */ NODISCARD FORCEINLINE T* Allocate(size_t InNum) = delete; /** Deallocates storage. */ FORCEINLINE void Deallocate(T* InPtr) = delete; /** @return true if allocation can be deallocated by another allocator, otherwise false. always return true when bSupportsMultipleAllocation is true. */ NODISCARD FORCEINLINE bool IsTransferable(T* InPtr) const { return true; } /** Calculates the amount of slack to allocate for an array that has just grown to a given number of elements. */ NODISCARD FORCEINLINE size_t CalculateSlackGrow(size_t Num, size_t NumAllocated) const = delete; /** Calculates the amount of slack to allocate for an array that has just shrunk to a given number of elements. */ NODISCARD FORCEINLINE size_t CalculateSlackShrink(size_t Num, size_t NumAllocated) const = delete; /** Calculates the amount of slack to allocate for an array that has just grown or shrunk to a given number of elements. */ NODISCARD FORCEINLINE size_t CalculateSlackReserve(size_t Num) const = delete; }; }; #define ALLOCATOR_WRAPPER_BEGIN(Allocator, Type, Name) \ \ struct PREPROCESSOR_JOIN(F, Name) /*: private FSingleton*/ #define ALLOCATOR_WRAPPER_END(Allocator, Type, Name) ; \ \ template && !CFinal> \ struct PREPROCESSOR_JOIN(T, Name); \ \ template \ struct PREPROCESSOR_JOIN(T, Name) : public PREPROCESSOR_JOIN(F, Name), private A \ { \ NODISCARD FORCEINLINE A& operator*() { return *this; } \ NODISCARD FORCEINLINE const A& operator*() const { return *this; } \ NODISCARD FORCEINLINE A* operator->() { return this; } \ NODISCARD FORCEINLINE const A* operator->() const { return this; } \ }; \ \ template \ struct PREPROCESSOR_JOIN(T, Name) : public PREPROCESSOR_JOIN(F, Name) \ { \ NODISCARD FORCEINLINE A& operator*() { return AllocatorInstance; } \ NODISCARD FORCEINLINE const A& operator*() const { return AllocatorInstance; } \ NODISCARD FORCEINLINE A* operator->() { return &AllocatorInstance; } \ NODISCARD FORCEINLINE const A* operator->() const { return &AllocatorInstance; } \ \ private: \ \ A AllocatorInstance; \ \ }; \ \ PREPROCESSOR_JOIN(T, Name)> Name; /** This is heap allocator that calls Memory::Malloc() directly for memory allocation. */ struct FHeapAllocator { static constexpr bool bSupportsMultipleAllocation = true; template class ForElementType /*: private FSingleton*/ { public: ForElementType() = default; ForElementType(const ForElementType&) = delete; ForElementType(ForElementType&&) = delete; ForElementType& operator=(const ForElementType&) = delete; ForElementType& operator=(ForElementType&&) = delete; NODISCARD FORCEINLINE T* Allocate(size_t InNum) { return InNum != 0 ? static_cast(Memory::Malloc(Memory::QuantizeSize(InNum * sizeof(T)), alignof(T))) : nullptr; } FORCEINLINE void Deallocate(T* InPtr) { Memory::Free(InPtr); } NODISCARD FORCEINLINE bool IsTransferable(T* InPtr) const { return true; } NODISCARD FORCEINLINE size_t CalculateSlackGrow(size_t Num, size_t NumAllocated) const { const size_t FirstGrow = 4; const size_t ConstantGrow = 16; size_t Result; check(Num > NumAllocated); Result = (NumAllocated != 0) ? (Num + 3 * Num / 8 + ConstantGrow) : (Num > FirstGrow ? Num : FirstGrow); Result = Memory::QuantizeSize(Result * sizeof(T), alignof(T)) / sizeof(T); return Result; } NODISCARD FORCEINLINE size_t CalculateSlackShrink(size_t Num, size_t NumAllocated) const { size_t Result; check(Num < NumAllocated); const bool bTooManySlackBytes = (NumAllocated - Num) * sizeof(T) >= 16 * 1024; const bool bTooManySlackElements = 3 * Num < 2 * NumAllocated; const bool bNeedToShrink = (bTooManySlackBytes || bTooManySlackElements) && (NumAllocated - Num > 64 || Num == 0); if (bNeedToShrink) { Result = Num != 0 ? Memory::QuantizeSize(Num * sizeof(T), alignof(T)) / sizeof(T) : 0; } else { Result = NumAllocated; } return Result; } NODISCARD FORCEINLINE size_t CalculateSlackReserve(size_t Num) const { return Num != 0 ? Memory::QuantizeSize(Num * sizeof(T), alignof(T)) / sizeof(T) : 0; } }; }; /** * The inline allocator allocates up to a specified number of elements in the same allocation as the container. * Any allocation needed beyond that causes all data to be moved into an indirect allocation. */ template struct TInlineAllocator { static constexpr bool bSupportsMultipleAllocation = false; template class ForElementType /*: private FSingleton*/ { public: ForElementType() = default; ForElementType(const ForElementType&) = delete; ForElementType(ForElementType&&) = delete; ForElementType& operator=(const ForElementType&) = delete; ForElementType& operator=(ForElementType&&) = delete; NODISCARD FORCEINLINE T* Allocate(size_t InNum) { if (InNum == 0) return nullptr; check(InNum >= NumInline); if (InNum == NumInline) return Impl.GetInline(); return Impl->Allocate(InNum); } FORCEINLINE void Deallocate(T* InPtr) { if (InPtr == Impl.GetInline()) return; Impl->Deallocate(InPtr); } NODISCARD FORCEINLINE bool IsTransferable(T* InPtr) const { if (InPtr == Impl.GetInline()) return false; return Impl->IsTransferable(InPtr); } NODISCARD FORCEINLINE size_t CalculateSlackGrow(size_t Num, size_t NumAllocated) const { check(Num > NumAllocated); check(NumAllocated >= NumInline); if (Num <= NumInline) return NumInline; return Impl->CalculateSlackGrow(Num, NumAllocated <= NumInline ? 0 : NumAllocated); } NODISCARD FORCEINLINE size_t CalculateSlackShrink(size_t Num, size_t NumAllocated) const { check(Num < NumAllocated); check(NumAllocated >= NumInline); if (Num <= NumInline) return NumInline; return Impl->CalculateSlackShrink(Num, NumAllocated); } NODISCARD FORCEINLINE size_t CalculateSlackReserve(size_t Num) const { if (Num <= NumInline) return NumInline; return Impl->CalculateSlackReserve(Num); } private: ALLOCATOR_WRAPPER_BEGIN(SecondaryAllocator, T, Impl) { TAlignedStorage InlineStorage[NumInline]; NODISCARD FORCEINLINE T* GetInline() { return reinterpret_cast< T*>(&InlineStorage); } NODISCARD FORCEINLINE const T* GetInline() const { return reinterpret_cast(&InlineStorage); } } ALLOCATOR_WRAPPER_END(SecondaryAllocator, T, Impl) }; }; /** This is a null allocator for which all operations are illegal. */ struct FNullAllocator { static constexpr bool bSupportsMultipleAllocation = true; template class ForElementType /*: private FSingleton*/ { public: ForElementType() = default; ForElementType(const ForElementType&) = delete; ForElementType(ForElementType&&) = delete; ForElementType& operator=(const ForElementType&) = delete; ForElementType& operator=(ForElementType&&) = delete; NODISCARD FORCEINLINE T* Allocate(size_t InNum) { check_no_entry(); return nullptr; } FORCEINLINE void Deallocate(T* InPtr) { check_no_entry(); } NODISCARD FORCEINLINE bool IsTransferable(T* InPtr) const { check_no_entry(); return false; } NODISCARD FORCEINLINE size_t CalculateSlackGrow(size_t Num, size_t NumAllocated) const { check_no_entry(); return 0; } NODISCARD FORCEINLINE size_t CalculateSlackShrink(size_t Num, size_t NumAllocated) const { check_no_entry(); return 0; } NODISCARD FORCEINLINE size_t CalculateSlackReserve(size_t Num) const { check_no_entry(); return 0; } }; }; template using TFixedAllocator = TInlineAllocator; NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END