Program Listing for File Vector.h

Return to documentation for file (Source\Containers\Inc\Containers\Vector.h)

#pragma once

// for memcpy
#include <cassert>
#include <cstring>
#include <functional>
#include <iterator>

#include "Array.h"
#include "Memory/Allocator.h"
#include "Types.h"

#include "Utils/Macros.h"

namespace Azura {
namespace Containers {

struct ContainerExtent {
  U32 m_size;
  U32 m_reserveSize;

  explicit ContainerExtent(const U32 size)
    : m_size(size),
      m_reserveSize(size) {
  }

  ContainerExtent(const U32 size, const U32 reserveSize)
    : m_size(size),
      m_reserveSize(reserveSize) {
  }
};

template <typename Type>
class Vector {
public:

  explicit Vector(Memory::Allocator& alloc);

  Vector(UINT maxSize, Memory::Allocator& alloc);

  Vector(UINT currentSize, UINT maxSize, Memory::Allocator& alloc);

  Vector(const std::initializer_list<Type>& list, Memory::Allocator& alloc);

  template <typename... Args>
  Vector(ContainerExtent extent, Memory::Allocator& alloc, Args&&... args);

  ~Vector();

  Vector(const Vector& other);
  Vector(const Vector & other, Memory::Allocator & alloc);
  Vector(Vector&& other) noexcept;
  Vector& operator=(const Vector& other);
  Vector& operator=(Vector&& other) noexcept;

  void PushBack(const Type& data);

  void PushBack(Type&& data);

  template <typename... Args>
  void EmplaceBack(Args ... args);

  void PopBack();

  int FindFirst(const Type& data);

  void Remove(const Type& data);

  void Reserve(U32 requiredSize);

  void Resize(U32 requiredSize);

  bool IsEmpty() const;

  void InsertAt(U32 idx, const Type& data);

  Type* Data();

  const Type* Data() const;

  void Reset();

  void Clear();

  Type& operator[](U32 idx);
  Type& operator[](U32 idx) const;

  Type& Last();
  Type& Last() const;

  U32 GetSize() const {
    return m_size;
  }

  U32 GetMaxSize() const {
    return m_maxSize;
  }

  template <class InputIt>
  void Assign(InputIt first, InputIt last);

  class Iterator {
  public:
    Iterator() = default;
    ~Iterator() = default;
    Iterator(const Iterator& other) = default;
    Iterator& operator=(const Iterator& other) = default;

    using iterator_category = std::random_access_iterator_tag;
    using value_type = Type;
    using difference_type = int;
    using pointer = Type*;
    using reference = Type&;

    Iterator(const Vector* ptr, int index)
      : mPtr(ptr),
        mIndex(index) {
    }

    Iterator(Iterator&& other) noexcept = default;
    Iterator& operator=(Iterator&& other) noexcept = default;

    // Pre Increment
    Iterator& operator++() {
      ++mIndex;
      return *this;
    }

    // Post Increment
    Iterator operator++(int) {
      Iterator copy(*this);
      operator++();
      return copy;
    }

    // Pre Decrement
    Iterator& operator--() {
      --mIndex;
      return *this;
    }

    // Post Decrement
    Iterator operator--(int) {
      Iterator copy(*this);
      operator--();
      return copy;
    }

    bool operator==(const Iterator& rhs) {
      return mPtr == rhs.mPtr && mIndex == rhs.mIndex;
    }

    bool operator!=(const Iterator& rhs) {
      return !(*this == rhs);
    }

    bool operator<(const Iterator& rhs) {
      assert(mPtr == rhs.mPtr);
      return mIndex < rhs.mIndex;
    }

    bool operator<=(const Iterator& rhs) {
      assert(mPtr == rhs.mPtr);
      return mIndex <= rhs.mIndex;
    }

    bool operator>(const Iterator& rhs) {
      assert(mPtr == rhs.mPtr);
      return mIndex > rhs.mIndex;
    }

    bool operator>=(const Iterator& rhs) {
      assert(mPtr == rhs.mPtr);
      return mIndex >= rhs.mIndex;
    }

    Iterator operator+(const int& idx) {
      return Iterator(mPtr, mIndex + idx);
    }

    Iterator& operator+=(const int& idx) {
      mIndex += idx;
      return *this;
    }

    Iterator operator-(const int& idx) {
      assert(mIndex - idx > 0);
      return Iterator(mPtr, mIndex - idx);
    }

    Iterator& operator-=(const int& idx) {
      assert(mIndex - idx > 0);
      mIndex -= idx;
      return *this;
    }

    int operator-(const Iterator& rhs) {
      assert(mPtr == rhs.mPtr);
      return mIndex - rhs.mIndex;
    }

    friend int operator-(const Iterator& lhs, const Iterator& rhs) {
      Iterator copy(lhs);
      return copy - rhs;
    }

    // Element Accessors

    Type& operator*() {
      return mPtr->operator[](mIndex);
    }

    Type* operator->() {
      return &mPtr->operator[](mIndex);
    }

    Type& operator[](const int& idx) {
      return mPtr->operator[](mIndex + idx);
    }

  private:
    const Vector<Type>* mPtr{nullptr};
    int mIndex{-1};
  };

  Iterator Begin() const;

  Iterator End() const;

  Iterator begin() const;

  Iterator end() const;

private:
  void GrowIfNeeded();

  U32 m_size{0};
  U32 m_maxSize{0};
  std::reference_wrapper<Memory::Allocator> m_allocator;
  Memory::UniqueArrayPtr<Type> m_base{nullptr};
};

template <typename Type>
Vector<Type>::Vector(Memory::Allocator& alloc)
  : m_allocator(alloc) {
}

template <typename Type>
Vector<Type>::Vector(const UINT maxSize, Memory::Allocator& alloc)
  : m_maxSize(maxSize),
    m_allocator(alloc),
    m_base(m_allocator.get().RawNewArray<Type>(m_maxSize)) {
}

template <typename Type>
Vector<Type>::Vector(UINT currentSize, UINT maxSize, Memory::Allocator& alloc)
    :
    m_size(currentSize),
    m_maxSize(maxSize),
    m_allocator(alloc),
    m_base(m_allocator.get().RawNewArray<Type>(m_maxSize)) {
  }

template <typename Type>
Vector<Type>::Vector(const std::initializer_list<Type>& list, Memory::Allocator& alloc)
  : m_size(U32(list.size())),
    m_maxSize(U32(list.size())),
    m_allocator(alloc),
    m_base(m_allocator.get().RawNewArray<Type>(m_maxSize)) {

  if constexpr (std::is_trivially_copyable_v<Type>) {
    // Copy over Contents
    std::memcpy(m_base.get(), list.begin(), m_size * sizeof(Type));
  } else {
    const Type* start = list.begin();
    // Manually Copy Construct each item
    for (U32 idx = 0; idx < m_size; ++idx) {
      new(&m_base[idx]) Type(*(start + idx));
    }
  }
}

template <typename Type>
template <typename... Args>
Vector<Type>::Vector(ContainerExtent extent, Memory::Allocator& alloc, Args&&... args)
  : m_maxSize(extent.m_reserveSize),
    m_size(extent.m_size),
    m_allocator(alloc),
    m_base(m_allocator.get().RawNewArray<Type>(m_maxSize)) {
  assert(m_size <= m_maxSize);

  for (U32 idx = 0; idx < m_size; ++idx) {
    new(&m_base[idx]) Type(std::forward<Args>(args)...);
  }
}

template <typename Type>
Vector<Type>::~Vector() {
  for (U32 idx = 0; idx < m_size; ++idx) {
    // TODO(vasumahesh1): MSVC gives a NoDIscard warning here. Not sure why, but due to C++17.
    UNUSED(m_base[idx].~Type());
  }
};

template <typename Type>
Vector<Type>::Vector(const Vector& other)
  : m_size(other.m_size),
    m_maxSize(other.m_maxSize),
    m_allocator(other.m_allocator) {
  // Allocate Memory
  m_base = m_allocator.get().RawNewArray<Type>(m_maxSize);

  if constexpr (std::is_trivially_copyable_v<Type>) {
    // Copy over Contents
    std::memcpy(m_base.get(), other.m_base.get(), other.m_size * sizeof(Type));
  } else {
    // Manually Copy Construct each item
    for (U32 idx = 0; idx < other.m_size; ++idx) {
      new(&m_base[idx]) Type(other.m_base[idx]);
    }
  }
}

template <typename Type>
Vector<Type>::Vector(const Vector& other, Memory::Allocator& alloc)
  : m_size(other.m_size),
  m_maxSize(other.m_maxSize),
  m_allocator(alloc) {
  // Allocate Memory
  m_base = m_allocator.get().RawNewArray<Type>(m_maxSize);

  if constexpr (std::is_trivially_copyable_v<Type>) {
    // Copy over Contents
    std::memcpy(m_base.get(), other.m_base.get(), other.m_size * sizeof(Type));
  } else {
    // Manually Copy Construct each item
    for (U32 idx = 0; idx < other.m_size; ++idx) {
      new(&m_base[idx]) Type(other.m_base[idx]);
    }
  }
}

template <typename Type>
Vector<Type>::Vector(Vector&& other) noexcept
  : m_size(std::move(other.m_size)),
    m_maxSize(std::move(other.m_maxSize)),
    m_allocator(other.m_allocator),
    m_base(std::move(other.m_base)) {
  other.m_base    = nullptr;
  other.m_size    = 0;
  other.m_maxSize = 0;
}

template <typename Type>
Vector<Type>& Vector<Type>::operator=(const Vector& other) {
  if (this == &other) {
    return *this;
  }

  m_size      = other.m_size;
  m_maxSize   = other.m_maxSize;
  m_allocator = other.m_allocator;

  // Allocate Memory
  m_base = m_allocator.get().RawNewArray<Type>(m_maxSize);

  if constexpr (std::is_trivially_copyable_v<Type>) {
    // Copy over Contents
    std::memcpy(m_base.get(), other.m_base.get(), other.m_size * sizeof(Type));
  } else {
    // Manually Copy Construct each item
    for (U32 idx = 0; idx < other.m_size; ++idx) {
      new(&m_base[idx]) Type(other.m_base[idx]);
    }
  }

  return *this;
}

template <typename Type>
Vector<Type>& Vector<Type>::operator=(Vector&& other) noexcept {
  if (this == &other) {
    return *this;
  }

  m_size      = std::move(other.m_size);
  m_maxSize   = std::move(other.m_maxSize);
  m_allocator = other.m_allocator;
  m_base      = std::move(other.m_base);

  other.m_base    = nullptr;
  other.m_size    = 0;
  other.m_maxSize = 0;

  return *this;
}

template <typename Type>
void Vector<Type>::GrowIfNeeded() {
  if (m_size < m_maxSize)
  {
    return;
  }

  if (m_maxSize == 0)
  {
#ifdef BUILD_DEBUG
    printf("=== Debug Warning: Vector was not initialized with any size. Set a size using the ctor or Reserve() or Resize(). ===\n");
#endif
    Reserve(1);
    return;
  }

#ifdef BUILD_DEBUG
  printf("=== Debug Warning: Vector Growing in Size. This is underperformant -- consider setting the initial size. ===\n");
#endif

  m_maxSize = 2 * m_maxSize;

  // Grow Vector
  auto newDataHandle = m_allocator.get().RawNewArray<Type>(m_maxSize);

  if constexpr (std::is_trivially_copyable_v<Type>) {
    // Copy over Contents
    std::memmove(newDataHandle.get(), m_base.get(), m_size * sizeof(Type));
  }
  else {
    // Manually Move Construct each item into new space
    for (U32 idx = 0; idx < m_size; ++idx) {
      new(&newDataHandle[idx]) Type(std::move(m_base[idx]));

      // TODO(vasumahesh1): MSVC gives a NoDiscard warning here. Not sure why, but due to C++17.
      UNUSED(m_base[idx].~Type());
    }
  }

  m_base = std::move(newDataHandle);
}

template <typename Type>
void Vector<Type>::PushBack(const Type& data) {
  GrowIfNeeded();

  new(&m_base[m_size]) Type(data);
  ++m_size;
}

template <typename Type>
void Vector<Type>::PushBack(Type&& data) {
  GrowIfNeeded();

  new(&m_base[m_size]) Type(std::move(data));
  ++m_size;
}

template <typename Type>
template <typename... Args>
void Vector<Type>::EmplaceBack(Args ... args) {
  GrowIfNeeded();

  new(&m_base[m_size]) Type(args...);
  ++m_size;
}

template <typename Type>
void Vector<Type>::PopBack() {
  assert(m_size > 0);

  const U32 targetId = m_size - 1;
  m_base[targetId].~Type();
  --m_size;
}

template <typename Type>
int Vector<Type>::FindFirst(const Type& data) {
  int idx = -1;

  for (U32 itr = 0; itr < m_size; ++itr) {
    if (data == m_base[itr]) {
      idx = itr;
      break;
    }
  }

  return idx;
}

template <typename Type>
void Vector<Type>::Remove(const Type& data) {
  const int idx = FindFirst(data);

  if (idx >= 0) {
    for (U32 itr      = idx + 1; itr < m_size; ++itr) {
      m_base[itr - 1] = m_base[itr];
    }

    --m_size;
  }
}

template <typename Type>
void Vector<Type>::Reserve(U32 requiredSize) {
  m_maxSize = requiredSize;
  m_base    = m_allocator.get().RawNewArray<Type>(m_maxSize);
}

template <typename Type>
void Vector<Type>::Resize(U32 requiredSize) {
  m_maxSize = requiredSize;
  m_size    = requiredSize;
  m_base    = m_allocator.get().RawNewArray<Type>(m_maxSize);
}

template <typename Type>
bool Vector<Type>::IsEmpty() const {
  return m_size == 0;
}

template <typename Type>
void Vector<Type>::InsertAt(U32 idx, const Type& data) {
  assert(idx >= 0 && idx <= m_size);

  for (U32 itr  = m_size; itr > idx; --itr) {
    m_base[itr] = m_base[itr - 1];
  }

  m_base[idx] = data;
}

template <typename Type>
Type* Vector<Type>::Data() {
  return m_base.get();
}

template <typename Type>
const Type* Vector<Type>::Data() const {
  return m_base.get();
}

template <typename Type>
void Vector<Type>::Reset() {
  m_size = 0;
}

template <typename Type>
void Vector<Type>::Clear() {
  m_size = 0;
}

template <typename Type>
Type& Vector<Type>::operator[](const U32 idx) {
  assert(idx < m_size);
  return m_base[idx];
}

template <typename Type>
Type& Vector<Type>::operator[](const U32 idx) const {
  assert(idx < m_size);
  return m_base[idx];
}

template <typename Type>
Type& Vector<Type>::Last() {
  return m_base[m_size - 1];
}

template <typename Type>
Type& Vector<Type>::Last() const {
  return m_base[m_size - 1];
}

template <typename Type>
template <class InputIt>
void Vector<Type>::Assign(InputIt first, InputIt last) {
  U32 count           = 0;
  for (auto itr       = first; itr != last; ++itr) {
    operator[](count) = *itr;
    ++count;
  }
}

template <typename Type>
typename Vector<Type>::Iterator Vector<Type>::Begin() const {
  return Iterator(this, 0);
}

template <typename Type>
typename Vector<Type>::Iterator Vector<Type>::End() const {
  return Iterator(this, m_size);
}

template <typename Type>
typename Vector<Type>::Iterator Vector<Type>::begin() const {
  return Begin();
}

template <typename Type>
typename Vector<Type>::Iterator Vector<Type>::end() const {
  return End();
}
} // namespace Containers
} // namespace Azura