Files
EMS-ESP32/lib/esp32-psram/src/esp32-psram/VectorHIMEM.h
2026-02-15 14:15:13 +01:00

534 lines
14 KiB
C++

#pragma once
// HIMEM is only available on original ESP32
#if CONFIG_IDF_TARGET_ESP32
#include "HimemBlock.h"
namespace esp32_psram {
/**
* @class VectorHIMEM
* @brief Vector implementation that uses ESP32's high memory (himem) for
* storage
* @tparam T Type of elements stored in the vector
*/
template <typename T>
class VectorHIMEM {
public:
// Type definitions
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
/**
* @brief Default constructor - creates an empty vector
*/
VectorHIMEM() = default;
/**
* @brief Constructs a vector with the given number of default-initialized
* elements
* @param count The size of the vector
*/
explicit VectorHIMEM(size_type count) { resize(count); }
/**
* @brief Constructs a vector with the given number of copies of a value
* @param count The size of the vector
* @param value The value to initialize elements with
*/
VectorHIMEM(size_type count, const T& value) { resize(count, value); }
/**
* @brief Copy constructor
* @param other The vector to copy from
*/
VectorHIMEM(const VectorHIMEM& other) {
if (other.element_count > 0) {
if (reallocate(other.element_count)) {
T temp;
VectorHIMEM& other_ = const_cast<VectorHIMEM&>(other);
for (size_t i = 0; i < other.element_count; ++i) {
other_.memory.read((void*)&temp, i * sizeof(T), sizeof(T));
memory.write((void*)&temp, i * sizeof(T), sizeof(T));
}
element_count = other.element_count;
}
}
}
/**
* @brief Move constructor
* @param other The vector to move from
*/
VectorHIMEM(VectorHIMEM&& other) noexcept
: memory(std::move(other.memory)),
element_count(other.element_count),
element_capacity(other.element_capacity) {
other.element_count = 0;
other.element_capacity = 0;
}
/**
* @brief Initializer list constructor
* @param init The initializer list to copy from
*/
VectorHIMEM(std::initializer_list<T> init) {
if (init.size() > 0) {
if (reallocate(init.size())) {
size_t i = 0;
for (const auto& item : init) {
memory.write(&item, i * sizeof(T), sizeof(T));
++i;
}
element_count = init.size();
}
}
}
/**
* @brief Destructor - ensures all elements are properly destroyed
*/
~VectorHIMEM() { clear(); }
/**
* @brief Copy assignment operator
* @param other The vector to copy from
* @return Reference to this vector
*/
VectorHIMEM& operator=(const VectorHIMEM& other) {
if (this != &other) {
clear();
if (other.element_count > 0) {
if (reallocate(other.element_count)) {
T temp;
for (size_t i = 0; i < other.element_count; ++i) {
const_cast<esp32_psram::HimemBlock*>(&other.memory)->read(&temp, i * sizeof(T), sizeof(T));
memory.write(&temp, i * sizeof(T), sizeof(T));
}
element_count = other.element_count;
}
}
}
return *this;
}
/**
* @brief Move assignment operator
* @param other The vector to move from
* @return Reference to this vector
*/
VectorHIMEM& operator=(VectorHIMEM&& other) noexcept {
if (this != &other) {
clear();
memory = std::move(other.memory);
element_count = other.element_count;
element_capacity = other.element_capacity;
other.element_count = 0;
other.element_capacity = 0;
}
return *this;
}
/**
* @brief Initializer list assignment operator
* @param ilist The initializer list to copy from
* @return Reference to this vector
*/
VectorHIMEM& operator=(std::initializer_list<T> ilist) {
clear();
if (ilist.size() > 0) {
if (reallocate(ilist.size())) {
size_t i = 0;
for (const auto& item : ilist) {
memory.write(&item, i * sizeof(T), sizeof(T));
++i;
}
element_count = ilist.size();
}
}
return *this;
}
/**
* @brief Access element with bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
reference at(size_type pos) {
if (pos >= element_count) {
throw std::out_of_range("VectorHIMEM: index out of range");
}
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access element with bounds checking (const version)
* @param pos The position of the element
* @return Const reference to the element at position pos
* @throws std::out_of_range if pos is not within the range of the vector
*/
const_reference at(size_type pos) const {
if (pos >= element_count) {
throw std::out_of_range("VectorHIMEM: index out of range");
}
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access element without bounds checking
* @param pos The position of the element
* @return Reference to the element at position pos
*/
reference operator[](size_type pos) {
static T temp;
memory.read(&temp, pos * sizeof(T), sizeof(T));
return temp;
}
// Add const version of operator[]
const_reference operator[](size_type pos) const {
static T result;
// Need to cast away const for the read operation since it doesn't modify content
HimemBlock& non_const_memory = const_cast<HimemBlock&>(memory);
non_const_memory.read(&result, pos * sizeof(T), sizeof(T));
return result;
}
/**
* @brief Access the first element
* @return Reference to the first element
*/
reference front() {
static T temp;
memory.read(&temp, 0, sizeof(T));
return temp;
}
/**
* @brief Access the first element (const version)
* @return Const reference to the first element
*/
const_reference front() const {
static T temp;
memory.read(&temp, 0, sizeof(T));
return temp;
}
/**
* @brief Access the last element
* @return Reference to the last element
*/
reference back() {
static T temp;
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Access the last element (const version)
* @return Const reference to the last element
*/
const_reference back() const {
static T temp;
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
return temp;
}
/**
* @brief Check if the vector is empty
* @return true if the vector is empty, false otherwise
*/
bool empty() const { return element_count == 0; }
/**
* @brief Get the number of elements
* @return The number of elements in the vector
*/
size_type size() const { return element_count; }
/**
* @brief Get the maximum possible number of elements
* @return The maximum possible number of elements the vector can hold
*/
size_type max_size() const {
return std::numeric_limits<size_type>::max() / sizeof(T);
}
/**
* @brief Reserve storage
* @param new_cap The new capacity of the vector
*/
void reserve(size_type new_cap) {
if (new_cap > element_capacity) {
reallocate(new_cap);
}
}
/**
* @brief Get the number of elements that can be held in current storage
* @return The capacity of the vector
*/
size_type capacity() const { return element_capacity; }
/**
* @brief Clear the contents
*/
void clear() {
// We're using plain memory so no destructors to call
element_count = 0;
}
/**
* @brief Add an element to the end
* @param value The value to append
*/
void push_back(const T& value) {
if (element_count >= element_capacity) {
size_t new_capacity =
element_capacity == 0 ? min_elements : element_capacity * 2;
if (!reallocate(new_capacity)) {
ESP_LOGE(TAG, "Failed to reallocate for push_back");
return;
}
}
// Write the new element at the end
memory.write(&value, element_count * sizeof(T), sizeof(T));
++element_count;
}
/**
* @brief Add an element to the end by moving it
* @param value The value to append
*/
void push_back(T&& value) {
// For POD types, this is the same as the const& version
push_back(static_cast<const T&>(value));
}
/**
* @brief Remove the last element
*/
void pop_back() {
if (element_count > 0) {
--element_count;
}
}
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
*/
void resize(size_type count) {
if (count > element_capacity) {
if (!reallocate(count)) {
return;
}
}
element_count = count;
}
/**
* @brief Change the number of elements stored
* @param count The new size of the vector
* @param value The value to initialize new elements with
*/
void resize(size_type count, const value_type& value) {
size_t old_size = element_count;
if (count > element_count) {
// Need to add elements
if (count > element_capacity) {
if (!reallocate(count)) {
return;
}
}
// Initialize new elements with value
for (size_t i = old_size; i < count; ++i) {
memory.write(&value, i * sizeof(T), sizeof(T));
}
}
element_count = count;
}
/**
* @brief Erase an element
* @param pos Index of the element to erase
*/
void erase(size_type pos) {
if (pos >= element_count) {
return;
}
// Move each element down by one, overwriting the erased element
T temp;
for (size_t i = pos + 1; i < element_count; ++i) {
memory.read(&temp, i * sizeof(T), sizeof(T));
memory.write(&temp, (i - 1) * sizeof(T), sizeof(T));
}
--element_count;
}
/**
* @brief Insert an element at a specific position
* @param pos Position where the element should be inserted
* @param value The value to insert
*/
void insert(size_type pos, const T& value) {
if (pos > element_count) {
return;
}
if (element_count >= element_capacity) {
size_t new_capacity =
element_capacity == 0 ? min_elements : element_capacity * 2;
if (!reallocate(new_capacity)) {
return;
}
}
// Move elements up to make space
T temp;
for (size_t i = element_count; i > pos; --i) {
memory.read(&temp, (i - 1) * sizeof(T), sizeof(T));
memory.write(&temp, i * sizeof(T), sizeof(T));
}
// Insert new element
memory.write(&value, pos * sizeof(T), sizeof(T));
++element_count;
}
/**
* @brief Swap the contents of this vector with another
* @param other Vector to swap with
*/
void swap(VectorHIMEM& other) noexcept {
std::swap(memory, other.memory);
std::swap(element_count, other.element_count);
std::swap(element_capacity, other.element_capacity);
}
protected:
HimemBlock memory;
size_t element_count = 0;
size_t element_capacity = 0;
static constexpr size_t min_elements = 16; // Minimum allocation size
/**
* @brief Calculate required memory size in bytes for a given number of
* elements
* @param count Number of elements
* @return Size in bytes
*/
static size_t calculate_size_bytes(size_t count) { return count * sizeof(T); }
/**
* @brief Reallocate memory with a new capacity
* @param new_capacity The new capacity to allocate
* @return true if reallocation was successful, false otherwise
*/
bool reallocate(size_t new_capacity) {
if (new_capacity <= element_capacity) {
return true; // No need to reallocate
}
// Calculate new size (at least min_elements)
new_capacity = std::max(new_capacity, min_elements);
// Create a new memory block
HimemBlock new_memory;
element_capacity = new_memory.allocate(calculate_size_bytes(new_capacity)) / sizeof(T);
if (element_capacity == 0) {
ESP_LOGE(TAG, "Failed to allocate new memory block");
return false;
}
// Copy existing elements if any
if (element_count > 0) {
T temp;
for (size_t i = 0; i < element_count; ++i) {
// Read from old memory
memory.read(&temp, i * sizeof(T), sizeof(T));
// Write to new memory
new_memory.write(&temp, i * sizeof(T), sizeof(T));
}
}
// Swap the memory blocks
memory = std::move(new_memory);
return true;
}
};
/**
* @brief Equality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are equal, false otherwise
*/
template <typename T>
bool operator==(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
T lhs_val, rhs_val;
for (size_t i = 0; i < lhs.size(); ++i) {
lhs_val = lhs[i]; // Uses operator[] which calls read()
rhs_val = rhs[i];
if (!(lhs_val == rhs_val)) {
return false;
}
}
return true;
}
/**
* @brief Inequality comparison operator
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
* @return true if the vectors are not equal, false otherwise
*/
template <typename T>
bool operator!=(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
return !(lhs == rhs);
}
/**
* @brief Swap the contents of two vectors
* @tparam T Type of elements in the vectors
* @param lhs First vector
* @param rhs Second vector
*/
template <typename T>
void swap(VectorHIMEM<T>& lhs, VectorHIMEM<T>& rhs) noexcept {
lhs.swap(rhs);
}
} // namespace esp32_psram
#endif // CONFIG_IDF_TARGET_ESP32