/* *********************************************************************************************************************** * * Copyright (c) 2015-2025 Advanced Micro Devices, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * **********************************************************************************************************************/ /** *********************************************************************************************************************** * @file palVector.h * @brief PAL utility collection Vector and VectorIterator class declarations. *********************************************************************************************************************** */ #pragma once #include "palUtil.h" #include "palAssert.h" #include "palSpan.h" #include "palSysMemory.h" #include namespace Util { // Forward declarations. template class Vector; /** *********************************************************************************************************************** * @brief Iterator for traversal of elements in Vector. * * Supports forward traversal. *********************************************************************************************************************** */ template class VectorIterator { public: /// Checks if the current index is within bounds of the number of elements in the vector. /// /// @returns True if the current element this iterator is pointing to is within the permitted range. bool IsValid() const { return (m_curIndex < m_srcVector.m_numElements); } /// Returns the element the iterator is currently pointing to as a reference. /// /// @warning This may cause an access violation if the iterator is not valid. /// /// @returns The element the iterator is currently pointing to. T& Get() const { PAL_ASSERT(IsValid()); return (*(m_srcVector.m_pData + m_curIndex)); } /// Advances the iterator to point to the next element. /// /// @warning Does not do bounds checking. void Next() { ++m_curIndex; } /// Retrieves the current vector position of this iterator. /// /// @returns The location in the vector of the element the iterator is currently pointing to. uint32 Position() const { return m_curIndex; } private: VectorIterator(uint32 index, const Vector& srcVec); uint32 m_curIndex; // The current index of the vector iterator. const Vector& m_srcVector; // The vector container this iterator is used for. PAL_DISALLOW_DEFAULT_CTOR(VectorIterator); // Although this is a transgression of coding standards, it means that Vector does not need to have a public // interface specifically to implement this class. The added encapsulation this provides is worthwhile. friend class Vector; }; /** *********************************************************************************************************************** * @brief Vector container. * * Vector is a templated array based storage that starts with a default-size allocation in the stack. If more space is * needed it then resorts to dynamic allocation by doubling the size every time the capacity is exceeded. * Operations which this class supports are: * * - Insertion at the end of the array. * - Forward iteration. * - Random access. * * @warning This class is not thread-safe. *********************************************************************************************************************** */ template class Vector { public: /// A convenient shorthand for VectorIterator. typedef VectorIterator Iter; /// When this allocates, it doubles the old size of memory static constexpr uint32 GrowthFactor = 2; /// Constructor. /// /// @param [in] pAllocator The allocator that will allocate memory if required. Vector(Allocator*const pAllocator); /// Destructor. ~Vector(); /// Move constructor. /// /// @param [in] vector Reference to a dying vector, from which resources will be stolen. Vector(Vector&& vector); /// Increases maximal capacity to value greater or equal to the newCapacity. /// If newCapacity is greater than the maximal capacity, new storage is allocated, /// otherwise the method does nothing. /// /// @note All existing iterators will not get invalidated, even in case new storage is allocated, /// because iterators are referencing vector, rather than elements of that vector. /// /// @warning All pointers and references to elements of a vector will be invalidated, /// in case new storage is allocated. /// /// @param [in] newCapacity The new capacity of a vector, which is lower limit of the maximal capacity. /// /// @returns Result ErrorOutOfMemory if the operation failed. Result Reserve(uint32 newCapacity); /// Increases maximum capacity to the number of elements in the vector, plus the specified increment amount. /// Equivalent to this->Reserve(this->NumElements() + amount); /// /// @param [in] amount Number of items beyond the current element count to increas the capacity to. /// /// @returns Result ErrorOutOfMemory if the operation failed. Result Grow(uint32 amount) { return Reserve(NumElements() + amount); } /// Set size to newSize. /// If size is decreased, elements at the end of the vector will be removed. /// If size is increased, new elements will be set to newVal. /// If newSize requires a higher capacity, a new allocation is made. See notes on Reserve. /// /// @note If size is decreased, any iterators referencing removed elements will become invalid. All other /// iterators will remain valid. Otherwise, all iterators will still be valid. /// /// @warning All pointers and references to elements of a vector will be invalidated, /// in case new storage is allocated. /// /// @param [in] newSize The new size of a vector. /// /// @returns Result ErrorOutOfMemory if the operation failed. Result Resize(uint32 newSize, const T& newVal = T()); /// Copy/Move an element to end of the vector. If not enough space is available, new space will be allocated and /// the old data will be copied to the new space. /// /// @param [in] data The element to be pushed to the vector. The element will become the last element. /// /// @returns Result ErrorOutOfMemory if the operation failed. Result PushBack(const T& data); Result PushBack(T&& data); /// Constructs an object in-place at the end of the vector. If not enough space is available, new space will be /// allocated and the old data will be copied to the new space. /// /// @param [in] args... The arguments passed to the constructor /// /// @returns Result ErrorOutOfMemory if the operation failed. template Result EmplaceBack(Args&&... args); /// Returns the element at the end of the vector and destroys it. /// /// @param [out] pData The element at the end of the vector. /// It is expected that pData is uninitialized as it will be overwritten and not destructed. void PopBack(T* pData); /// Destroys all elements stored in the vector. All dynamically allocated memory will be saved for reuse. void Clear(); ///@{ /// Returns the element at the location specified. /// /// @warning Calling this function with an out-of-bounds index will cause an access violation! /// /// @param [in] index Integer location of the element needed. /// /// @returns The element at location specified by index by reference T& At(uint32 index) { PAL_ASSERT(index < m_numElements); return *(m_pData + index); } const T& At(uint32 index) const { PAL_ASSERT(index < m_numElements); return *(m_pData + index); } T& operator[](uint32 index) noexcept { return At(index); } const T& operator[](uint32 index) const noexcept { return At(index); } ///@} /// Returns the data at the front of the vector. /// /// @warning Calling this function on an empty vector will cause an access violation! /// /// @returns The data at the front of the vector. T& Front() const { PAL_ASSERT(IsEmpty() == false); return *m_pData; } /// Returns the data at the back of the vector. /// /// @warning Calling this function on an empty vector will cause an access violation! /// /// @returns The data at the back of the vector. T& Back() const { PAL_ASSERT(IsEmpty() == false); return *(m_pData + (m_numElements - 1)); } /// Returns an iterator to the first element of the vector. /// /// @warning Accessing an element using an iterator of an empty vector will cause an access violation! /// /// @returns An iterator to first element of the vector. Iter Begin() const { return Iter(0, *this); } /// Returns an iterator to the last element of the vector. /// /// @warning Accessing an element using an iterator of an empty vector will cause an access violation! /// /// @returns VectorIterator An iterator to last element of the vector. Iter End() const { return Iter((m_numElements - 1), *this); } ///@{ /// Implicitly gets the current contents of the vector as a Span. /// /// @returns The contents of the vector as a Span; same as Span(Data(), NumElements()). operator Span() { return Span(Data(), NumElements()); } operator Span() const { return Span(Data(), NumElements()); } ///@} /// Returns pointer to the underlying buffer serving as data storage. /// The returned pointer defines always valid range [Data(), Data() + NumElements()), /// even if the container is empty (Data() is not dereferenceable in that case). /// /// @warning Dereferencing pointer returned by Data() from an empty vector will cause an access violation! /// /// @returns Pointer to the underlying data storage for read & write access. /// For a non-empty vector, the returned pointer contains address of the first element. /// For an empty vector, the returned pointer may or may not be a null pointer. T* Data() { return m_pData; } /// Returns pointer to the underlying buffer serving as data storage. /// The returned pointer defines always valid range [Data(), Data() + NumElements()), /// even if the container is empty (Data() is not dereferenceable in that case). /// /// @warning Dereferencing pointer returned by Data() from an empty vector will cause an access violation! /// /// @returns Pointer to the underlying data storage for read only access. /// For a non-empty vector, the returned pointer contains address of the first element. /// For an empty vector, the returned pointer may or may not be a null pointer. const T* Data() const { return m_pData; } /// Returns the size of the vector. /// /// @returns An unsigned integer equal to the number of elements currently present in the vector. uint32 NumElements() const { return m_numElements; } /// Returns true if the number of elements present in the vector is equal to zero. /// /// @returns True if the vector is empty. bool IsEmpty() const { return (m_numElements == 0); } /// Returns a pointer to the allocator used for this container's memory management. /// /// @returns Allocator pointer. Allocator* GetAllocator() const { return m_pAllocator; } ///@{ /// @internal Satisfies concept `range_expression`, using T* as `iterator` and 32-bit size and difference types /// /// @note - These are a convenience intended to be used by c++ language features such as `range for`. /// These should not be called directly as they do not adhere to PAL coding standards. using value_type = T; using reference = T&; using const_reference = const T&; using iterator = T*; using const_iterator = const T*; using difference_type = int32; using size_type = uint32; iterator begin() noexcept { return m_pData; } iterator end() noexcept { return (m_pData + m_numElements); } const_iterator begin() const noexcept { return m_pData; } const_iterator end() const noexcept { return (m_pData + m_numElements); } const_iterator cbegin() const noexcept { return m_pData; } const_iterator cend() const noexcept { return (m_pData + m_numElements); } [[nodiscard]] bool empty() const noexcept { return IsEmpty(); } size_type size() const noexcept { return m_numElements; } ///@} /// Erases the element at the specified iterator. void Erase(Iter it); /// Erases the element at the specified iterator. void Erase(iterator it); /// Erases the element at the specified index. void Erase(uint32 index); /// Erase the element at the specified iterator, and swap last element to that position. /// If the element to erase is the last element, erase directly and no swap operation. void EraseAndSwapLast(Iter it); /// Erase the element at the specified iterator, and swap last element to that position. /// If the element to erase is the last element, erase directly and no swap operation. void EraseAndSwapLast(iterator it); /// Erases the element at the specified index, and swap last element to that position. /// If the element to erase is the last element, erase directly and no swap operation. void EraseAndSwapLast(uint32 index); private: // This is a POD-type that exactly fits one T value. typedef typename std::aligned_storage::type ValueStorage; ValueStorage m_data[defaultCapacity]; // The initial data buffer stored within the vector object. T* m_pData; // Pointer to the current data buffer. uint32 m_numElements; // Number of elements present. uint32 m_maxCapacity; // Maximum size it can hold. Allocator*const m_pAllocator; // Allocator for this Vector. PAL_DISALLOW_COPY_AND_ASSIGN(Vector); // Although this is a transgression of coding standards, it prevents VectorIterator requiring a public constructor; // constructing a 'bare' VectorIterator (i.e. without calling Vector::GetIterator) can never be a legal operation, // so this means that these two classes are much safer to use. friend class VectorIterator; }; // ===================================================================================================================== template VectorIterator::VectorIterator( uint32 index, const Vector& srcVec) : m_curIndex(index), m_srcVector(srcVec) { } // ===================================================================================================================== template Vector::Vector( Allocator*const pAllocator) : m_pData(reinterpret_cast(m_data)), m_numElements(0), m_maxCapacity(defaultCapacity), m_pAllocator(pAllocator) { } // ===================================================================================================================== template Vector::~Vector() { // Explicitly destroy all non-trivial types. if (!std::is_trivial::value) { for (uint32 idx = 0; idx < m_numElements; ++idx) { m_pData[idx].~T(); } } // Check if we have dynamically allocated memory. if (m_pData != reinterpret_cast(m_data)) { // Free the memory that was allocated dynamically. PAL_FREE(m_pData, m_pAllocator); } } // ===================================================================================================================== // Steals allocation from a dying vector, if data buffer uses storage from heap allocation. // Moves objects between local buffers of new and dying vectors (for non-trivial types) or // copies local buffer from a dying vector to a new vector (for trivial types), // if data buffer uses storage from local buffer. template Vector::Vector( Vector&& vector) : m_numElements(vector.m_numElements), m_maxCapacity(vector.m_maxCapacity), m_pAllocator(vector.m_pAllocator) { if (vector.m_pData == reinterpret_cast(vector.m_data)) // Local buffer { // Data buffer will be using storage from local buffer. m_pData = reinterpret_cast(m_data); if (std::is_trivial::value) { // Optimize trivial types by copying local buffer. std::memcpy(m_pData, vector.m_pData, sizeof(T) * m_numElements); } else { // Move objects from local buffer of a dying vector to local buffer of a new vector. for (uint32 idx = 0; idx < m_numElements; ++idx) { PAL_PLACEMENT_NEW(m_pData + idx) T(Move(vector.m_pData[idx])); } } } else // Heap allocation { // Steal heap allocation from dying vector. m_pData = vector.m_pData; // After the allocation has been stolen, dying vector is just an empty shell. vector.m_pData = nullptr; vector.m_numElements = 0; vector.m_maxCapacity = 0; } } } // Util