// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #if (defined(__AUTORTFM) && __AUTORTFM) #include "ContainerValidation.h" #include "ExternAPI.h" #include "Utils.h" #include #include namespace AutoRTFM { // A stack container with an inline fixed capacity that once exceeded spills to // the heap. // // Notes: // * Heap allocated memory is not automatically freed when popping elements. // Only calling Reset() or destructing the stack will free heap allocated // memory. // * This class is not relocatable and so is not safe to use in UE's containers // which require elements to be relocatable. // // Template parameters: // T - the element type. Must support copy and move constructors. // InlineCapacity - the number of elements that can be held before spilling to // the heap. // Validation - if Disabled, do not perform validity assertions. template class TStack { public: using ElementType = T; // Constructor. TStack() = default; // Copy constructor. TStack(const TStack& Other) { CopyFrom(Other); } // Move constructor. TStack(TStack&& Other) { MoveFrom(std::move(Other)); } // Destructor. // Frees all the memory allocated by the allocator. ~TStack() { Reset(); } // Copy assignment operator TStack& operator=(const TStack& Other) { if (&Other != this) { Clear(); CopyFrom(Other); } return *this; } // Move assignment operator TStack& operator=(TStack&& Other) { if (&Other != this) { Clear(); MoveFrom(std::move(Other)); } return *this; } // Clears all the items from the stack, preserving the capacity. void Clear() { for (T& Item : *this) { Item.~T(); } Count = 0; } // Clears all the items from the stack, freeing all heap allocations and // resetting the capacity to InlineCapacity void Reset() { Clear(); if (Data != InlineData()) { Free(Data); Data = reinterpret_cast(InlineDataBuffer); } Capacity = InlineCapacity; } // Pushes a new item on to the stack. inline void Push(const T& Item) { if (Count >= Capacity) { Reserve(Capacity * 2); } new (Data + Count) T(Item); ++Count; } // Moves all the items from Other to this stack. // Other will hold no elements after calling. inline void PushAll(TStack&& Other) { size_t NewCount = Count + Other.Count; if (NewCount >= Capacity) { Reserve(std::max(NewCount, Capacity * 2)); } if constexpr (std::is_trivial_v) { memcpy(Data + Count, Other.Data, Other.Count * sizeof(T)); } else { for (size_t I = 0, N = Other.Count; I < N; I++) { new (Data + Count + I) T(std::move(Other.Data[I])); Other.Data[I].~T(); } } Count = NewCount; Other.SetToInitialState(); } // Removes the last item on the stack. inline void Pop() { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Count > 0); --Count; Data[Count].~T(); } // Reserves memory for at NewCapacity items. inline void Reserve(size_t NewCapacity) { if (NewCapacity <= Capacity) { return; // Already has space for the new capacity } if constexpr (std::is_trivial_v) { if (Data == InlineData()) { T* NewData = reinterpret_cast(AutoRTFM::Allocate(NewCapacity * sizeof(T), alignof(T))); memcpy(NewData, InlineDataBuffer, Count * sizeof(T)); Data = NewData; } else { Data = reinterpret_cast(AutoRTFM::Reallocate(Data, NewCapacity * sizeof(T), alignof(T))); } } else { T* NewData = reinterpret_cast(AutoRTFM::Allocate(NewCapacity * sizeof(T), alignof(T))); for (size_t I = 0, N = Count; I < N; I++) { new (NewData + I) T(std::move(Data[I])); Data[I].~T(); } if (Data != InlineData()) { AutoRTFM::Free(Data); } Data = NewData; } Capacity = NewCapacity; } inline size_t Num() const { return Count; } inline bool IsEmpty() const { return Count == 0; } T& operator[](size_t Index) { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Index < Count); return Data[Index]; } const T& operator[](size_t Index) const { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Index < Count); return Data[Index]; } T& Front() { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Count > 0); return Data[0]; } const T& Front() const { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Count > 0); return Data[0]; } T& Back() { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Count > 0); return Data[Count - 1]; } const T& Back() const { AUTORTFM_ASSERT(Validation == EContainerValidation::Disabled || Count > 0); return Data[Count - 1]; } T* begin() { return Data; } const T* begin() const { return Data; } T* end() { return Data + Count; } const T* end() const { return Data + Count; } private: // Copies the data from Other to this stack. // This stack must be empty before calling. void CopyFrom(const TStack& Other) { AUTORTFM_ASSERT(IsEmpty()); Reserve(Other.Count); if constexpr (std::is_trivial_v) { memcpy(Data, Other.Data, Other.Count * sizeof(T)); } else { for (size_t I = 0, N = Other.Count; I < N; I++) { new (Data + I) T(Other.Data[I]); } } Count = Other.Count; } // Moves the data from Other to this stack. // This stack must be empty before calling. void MoveFrom(TStack&& Other) { AUTORTFM_ASSERT(IsEmpty()); if (Other.Data == Other.InlineData()) { if constexpr (std::is_trivial_v) { memcpy(Data, Other.Data, Other.Count * sizeof(T)); } else { for (size_t I = 0, N = Other.Count; I < N; I++) { new (Data + I) T(std::move(Other.Data[I])); Other.Data[I].~T(); } } } else { // Steal the heap allocation from Other. Data = Other.Data; Capacity = Other.Capacity; } Count = Other.Count; Other.SetToInitialState(); } // Sets Data, Capacity and Count to the initially default-constructed values. // Warning: This does not free or destruct any elements held by this stack. void SetToInitialState() { Data = InlineData(); Capacity = InlineCapacity; Count = 0; } T* InlineData() { return reinterpret_cast(InlineDataBuffer); } const T* InlineData() const { return reinterpret_cast(InlineDataBuffer); } alignas(T) std::byte InlineDataBuffer[sizeof(T) * InlineCapacity]; T* Data = reinterpret_cast(InlineDataBuffer); size_t Capacity = InlineCapacity; size_t Count = 0; }; } #endif // (defined(__AUTORTFM) && __AUTORTFM)