From f07977ff0638285f8510f8944785ad5606168526 Mon Sep 17 00:00:00 2001 From: Riley King-Saunders Date: Mon, 23 Jun 2025 00:35:18 +1000 Subject: [PATCH] Implemented a dynamic array object that uses 24 bytes on the stack versus std::vector's 32. It uses basalt_memory module to allocate and free itself, thus requiring a tag. It is not atomic or thread-safe. --- include/containers/basalt_darray.h | 367 +++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 include/containers/basalt_darray.h diff --git a/include/containers/basalt_darray.h b/include/containers/basalt_darray.h new file mode 100644 index 0000000..5cb62a7 --- /dev/null +++ b/include/containers/basalt_darray.h @@ -0,0 +1,367 @@ +#pragma once +#include +#include +#include +#include "core/basalt_memory.h" +#include "core/basalt_logger.h" + +namespace basalt +{ + template + class darray + { + public: + darray(const darray& src); + darray& operator =(const darray& src); + darray(darray&& src); + darray& operator =(darray&& src); + + darray(MEMORY_TAG tag); + darray(size_t n_capacity, MEMORY_TAG tag); + darray(size_t n_elements, const T& fill_value, MEMORY_TAG tag); + ~darray(); + + void swap(darray& other); + + T* operator +(const size_t offset) noexcept; + T* operator -(const size_t offset) noexcept; + const T* operator +(const size_t offset) const noexcept; + const T* operator -(const size_t offset) const noexcept; + + operator T* () noexcept; + + T& operator [](const size_t idx) noexcept; + const T& operator [](const size_t idx) const noexcept; + + const size_t size(void) const noexcept; + + void resize(size_t new_size) noexcept; + void expand(size_t extra_capacity) noexcept; + void reserve(size_t new_capacity) noexcept; + void shrink_fit(void); + + void push_back(const T& val) noexcept; + void push_back(T&& val) noexcept; + + T&& pop_back(void) noexcept; + T& peek_back(void) noexcept; + const T& peek_back(void) const noexcept; + T& peek_front(void) noexcept; + const T& peek_front(void) const noexcept; + + T* m_pdata = nullptr; + size_t m_nelements = 0; + size_t m_ncapacity = 0; + MEMORY_TAG m_tag = 0; + }; + template + inline darray::darray(const darray& src) + { + this->m_pdata = mem::allocT(src.m_nelements, src.m_tag); + BASSERT_ERROR(this->m_pdata != nullptr, return, "Assertion %s failed at %s:%d\n\tMemory allocation failed for allocation size %u\n\t%s | %s | %u"); + this->m_tag = src.m_tag; + this->m_ncapacity = src.m_nelements; + this->m_nelements = src.m_nelements; + memcpy(this->m_pdata, src.m_pdata, sizeof(T) * this->m_nelements); + } + template + inline basalt::darray& darray::operator=(const darray& src) + { + if (&src == this) + return *this; + this->~darray(); + this->m_pdata = mem::allocT(src.m_nelements, src.m_tag); + BASSERT_ERROR(this->m_pdata != nullptr, return, "Assertion %s failed at %s:%d\n\tMemory allocation failed for allocation size %u\n\t%s | %s | %u"); + this->m_tag = src.m_tag; + this->m_ncapacity = src.m_nelements; + this->m_nelements = src.m_nelements; + memcpy(this->m_pdata, src.m_pdata, sizeof(T)*this->m_nelements); + } + + template + inline darray::darray(darray&& src) + { + this->m_pdata = src.m_pdata; + this->m_ncapacity = src.m_ncapacity; + this->m_nelements = src.m_nelements; + this->m_tag = src.m_tag; + src.m_pdata = nullptr; + src.m_ncapacity = 0; + src.m_nelements = 0; + src.m_tag = 0; + } + + template + inline darray& darray::operator=(darray&& src) + { + if (&src == this) + return *this; + this->~darray(); + this->m_pdata = src.m_pdata; + this->m_ncapacity = src.m_ncapacity; + this->m_nelements = src.m_nelements; + this->m_tag = src.m_tag; + src.m_pdata = nullptr; + src.m_ncapacity = 0; + src.m_nelements = 0; + src.m_tag = 0; + return *this; + } + + template + inline darray::darray(MEMORY_TAG tag) + { + this->m_pdata = nullptr; + this->m_ncapacity = 0; + this->m_nelements = 0; + this->m_tag = tag; + } + + template + inline darray::darray(size_t n_capacity, MEMORY_TAG tag) + { + this->m_pdata = mem::allocT(n_capacity, tag); + this->m_ncapacity = n_capacity; + this->m_nelements = 0; + this->m_tag = tag; + } + + template + inline darray::darray(size_t n_elements, const T& fill_value, MEMORY_TAG tag) + { + this->m_pdata = mem::allocT(n_capacity, tag); + this->m_ncapacity = n_capacity; + this->m_nelements = n_elements; + for (size_t i = 0; i < n_elements; ++i) + this->m_pdata[i] = fill_value; + this->m_tag = tag; + } + + template + inline darray::~darray() + { + if (this->m_pdata != nullptr) + mem::dealloc(this->m_pdata, this->m_nelements * sizeof(T), this->m_tag); + } + template + inline void darray::swap(darray& other) + { + size_t tmp = other.m_ncapacity; + other.m_ncapacity = this->m_ncapacity; + this->m_ncapacity = tmp; + + tmp = other.m_nelements; + other.m_nelements = this->m_nelements; + this->m_nelements = tmp; + + MEMORY_TAG tmp2 = other.m_tag; + other.m_tag = this->m_tag; + this->m_tag = tmp2; + + T* tmp = other.m_pdata; + other.m_pdata = this->m_pdata; + this->m_pdata = tmp; + } + template + const size_t darray::size(void) const noexcept + { return m_nelements; } + + template + inline T* darray::operator+(const size_t offset) noexcept + { return this->m_pdata+offset; } + template + inline T* darray::operator-(const size_t offset) noexcept + { return this->m_pdata - offset; } + template + inline const T* darray::operator+(const size_t offset) const noexcept + { return this->m_pdata + offset; } + template + inline const T* darray::operator-(const size_t offset) const noexcept + { return this->m_pdata - offset; } + template + inline darray::operator T* () noexcept + { return this->m_pdata; } + + template + inline T& darray::operator [](const size_t idx) noexcept + { return this->m_pdata[idx]; } + template + inline const T& darray::operator [](const size_t idx) const noexcept + { return this->m_pdata[idx]; } + template + inline void darray::resize(size_t new_size) noexcept + { + if (new_size & (1 << 63)) + { + BWARN("Attempted to resize object dynarray<%s> to a negative value of %ll\n", typeid(T).name(), new_size); + return; + } + if ((this->m_nelements + new_size) > this->m_ncapacity) + this->reserve(new_size * 2); + // Call destructor for objects outside of the new size + if (!std::is_trivially_destructible_v) + if (new_size < m_nelements) + for (size_t i = new_size; i < this->m_nelements) + this->m_pdata[i].~T(); + + this->m_nelements = new_size; + } + template + inline void darray::expand(size_t extra_capacity) noexcept + { this->reserve(this->m_ncapacity + extra_capacity); } + template + inline void darray::reserve(size_t new_capacity) noexcept + { + if (new_capacity < this->m_ncapacity) + return; + T* tmp = mem::allocT(new_capacity, this->m_tag); + if (std::is_trivially_copy_assignable_v) + memcpy(tmp, this->m_pdata, this->m_nelements * sizeof(T)); + else if (std::is_move_assignable_v) + for (size_t i = 0; i < this->m_nelements; ++i) + tmp[i] = std::move(this->m_pdata[i]); + else if (std::is_move_constructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + new (tmp + i) T(std::move(this->m_pdata[i])); + else if (std::is_copy_assignable_v) + for (size_t i = 0; i < this->m_nelements; ++i) + tmp[i] = this->m_pdata[i]; + else if (std::is_copy_constructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + new (tmp+i) T(this->m_pdata[i]); + else + { + BERROR("Can not resize object dynarray<%s>\n\tIt is not trivially copyable, copy assignable, copy constructible, move assignable or move constructable\n", typeid(T).name()); + mem::dealloc(tmp, this->m_ncapacity*sizeof(T), this->m_tag); + return; + } + + if (!std::is_trivially_destructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + this->m_pdata[i].~T(); + mem::dealloc(this->m_pdata, this->m_nelements*sizeof(T), this->m_tag); + this->m_ncapacity = new_capacity; + this->m_pdata = tmp; + } + template + inline void darray::shrink_fit(void) + { + if (this->m_nelements == this->m_ncapacity) + return; + T* tmp = mem::allocT(this->m_nelements, this->m_tag); + if (std::is_trivially_copy_assignable_v) + memcpy(tmp, this->m_pdata, this->m_nelements * sizeof(T)); + else if (std::is_move_assignable_v) + for (size_t i = 0; i < this->m_nelements; ++i) + tmp[i] = std::move(this->m_pdata[i]); + else if (std::is_move_constructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + new (tmp + i) T(std::move(this->m_pdata[i])); + else if (std::is_copy_assignable_v) + for (size_t i = 0; i < this->m_nelements; ++i) + tmp[i] = this->m_pdata[i]; + else if (std::is_copy_constructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + new (tmp + i) T(this->m_pdata[i]); + else + { + BERROR("Can not resize object dynarray<%s>\n\tIt is not trivially copyable, copy assignable, copy constructible, move assignable or move constructable\n", typeid(T).name()); + mem::dealloc(tmp); + return; + } + + if (!std::is_trivially_destructible_v) + for (size_t i = 0; i < this->m_nelements; ++i) + this->m_pdata[i].~T(); + mem::dealloc(this->m_pdata, this->m_nelements * sizeof(T), this->m_tag); + this->m_ncapacity = this->m_nelements; + this->m_pdata = tmp; + } + + template + inline void darray::push_back(const T& val) noexcept + { + if ((this->m_nelements + 1) > this->m_ncapacity) + this->expand(this->m_ncapacity < 1 ? 1 : this->m_ncapacity); + if (std::is_trivially_copyable_v) + memcpy(this->m_pdata + this->m_nelements, &val, sizeof(T)); + else if (std::is_copy_constructible_v) + new (this->m_pdata + this->m_nelements) T(val); + else if (std::is_copy_assignable_v) + this->m_pdata[this->m_nelements] = val; + else + { + BERROR("Can not use push_back(const %s&) on object that is not copy constructable or assignable\n", typeid(T).name()); + return; + } + this->m_nelements++; + } + + template + inline void darray::push_back(T&& val) noexcept + { + if ((this->m_nelements + 1) > this->m_ncapacity) + this->expand(this->m_ncapacity < 1 ? 1 : this->m_ncapacity); + if (std::is_trivially_move_assignable_v) + memcpy(this->m_pdata + this->m_nelements, &val, sizeof(T)); + else if (std::is_move_constructible_v) + new (this->m_pdata + this->m_nelements) T(std::move(val)); + else if (std::is_move_assignable_v) + this->m_pdata[this->m_nelements] = std::move(val); + else + { + BERROR("Can not use push_back(%s&&) on object that is not move constructable or assignable\n", typeid(T).name()); + return; + } + this->m_nelements++; + } + + template + inline const T& darray::peek_back(void) const noexcept + { + size_t idx = this->m_nelements - 1; + BASSERT_FATAL(this->m_nelements > 0, "Assertion %s failed at %s:%d\n\tAttempted to pop an empty darray<%s>\n", typeid(T).name()); + if (idx > this->m_nelements) + idx = 0; + return *this->m_pdata[idx]; + } + + template + inline T& darray::peek_front(void) noexcept + { + BASSERT_FATAL(this->m_nelements > 0, "Assertion %s failed at %s:%d\n\tAttempted to pop an empty darray<%s>\n", typeid(T).name()); + return *this->m_pdata; + } + + template + const T& darray::peek_front(void) const noexcept + { + BASSERT_FATAL(this->m_nelements > 0, "Assertion %s failed at %s:%d\n\tAttempted to pop an empty darray<%s>\n", typeid(T).name()); + return *this->m_pdata; + } + + template + inline T& basalt::darray::peek_back(void) noexcept + { + size_t idx = this->m_nelements - 1; + BASSERT_FATAL(this->m_nelements > 0, "Assertion %s failed at %s:%d\n\tAttempted to pop an empty darray<%s>\n", typeid(T).name()); + if (idx > this->m_nelements) + idx = 0; + return this->m_pdata[idx]; + } + + template + T&& darray::pop_back(void) noexcept + { + size_t idx = this->m_nelements - 1; + BASSERT_FATAL(this->m_nelements > 0, "Assertion %s failed at %s:%d\n\tAttempted to pop an empty darray<%s>\n", typeid(T).name()); + if (idx > this->m_nelements) + idx = 0; + darray tmp = std::move(this->m_pdata[idx]); + --this->m_nelements; + if (!std::is_trivially_destructible_v) + this->m_pdata[this->m_nelements].~T(); + return std::move(tmp); + } +} +