From 85078ee5a5bc257d1e564f0029782f934bd33f77 Mon Sep 17 00:00:00 2001 From: chatlanin Date: Thu, 11 Sep 2025 11:40:04 +0300 Subject: [PATCH] add new ring buffer --- bin/examples/patterns/main.cpp | 25 +- bin/meson.build | 4 +- src/hack/patterns/identificator.hpp | 66 +++- src/hack/patterns/ring_buffer.OLD.hpp | 155 +++++++++ src/hack/patterns/ring_buffer.hpp | 434 ++++++++++++++++++-------- 5 files changed, 530 insertions(+), 154 deletions(-) create mode 100644 src/hack/patterns/ring_buffer.OLD.hpp diff --git a/bin/examples/patterns/main.cpp b/bin/examples/patterns/main.cpp index 270008a..478954e 100644 --- a/bin/examples/patterns/main.cpp +++ b/bin/examples/patterns/main.cpp @@ -1,6 +1,6 @@ -#include "hack/patterns/ring_buffer.hpp" #include "hack/patterns/identificator.hpp" #include "hack/logger/logger.hpp" +#include "hack/patterns/ring_buffer.hpp" auto main(int argc, char *argv[]) -> int { @@ -8,21 +8,34 @@ auto main(int argc, char *argv[]) -> int hack::patterns::ring_buffer rb; rb.create(10); for (int i = 0; i < 10; ++i) rb.put(i); - hack::log()(rb); - hack::log()(rb.size()); + hack::log()("rb =", rb); + hack::log()("size =", rb.size()); rb.skip(3); + hack::log()(rb.pop().value()); + hack::log()("size =", rb.size()); hack::log()(rb.get().value()); - hack::log()(rb.size()); + hack::log()("size =", rb.size()); std::vector v(3); rb.get(v, 3); - hack::log()(v); + hack::log()("rb =", rb); + hack::log()("v =", v); + rb.pop(v, 5); + hack::log()("v =", v); + hack::log()(rb.pop().value()); + hack::log()("rb =", rb); + hack::log()("rb:", rb.pop().has_value(), " (пусто...)"); + rb.put(1); + hack::log()("rb =", rb); + hack::log()(rb.pop().value()); + hack::log()("rb =", rb); + hack::log()("rb:", rb.pop().has_value(), " (пусто...)"); // identificator struct id_struct : public hack::patterns::identificator<> {} aa; id_struct bb; id_struct cc; id_struct dd; - hack::log()(aa.m_id, bb.m_id, cc.m_id, dd.m_id); + hack::log()(aa.get_id(), bb.get_id(), cc.get_id(), dd.get_id()); return 0; } diff --git a/bin/meson.build b/bin/meson.build index 73ddb99..af450fc 100755 --- a/bin/meson.build +++ b/bin/meson.build @@ -4,8 +4,8 @@ executable( # 'examples/concepts/main.cpp', # 'examples/math/main.cpp', # 'examples/range/main.cpp', - # 'examples/patterns/main.cpp', - 'examples/logger/main.cpp', + 'examples/patterns/main.cpp', + # 'examples/logger/main.cpp', dependencies : deps, cpp_args: args, include_directories : inc diff --git a/src/hack/patterns/identificator.hpp b/src/hack/patterns/identificator.hpp index 3a7bc0c..64c3994 100644 --- a/src/hack/patterns/identificator.hpp +++ b/src/hack/patterns/identificator.hpp @@ -1,22 +1,70 @@ #pragma once #include +#include +#include +#include +#include "hack/logger/logger.hpp" namespace hack::patterns { - // Иногда нужно, чтобы был id но в виде какого-то числа. - // Например при выводе графики в массиве, типа как в VueJS - // вот этьо класс и пытается этим заниматься. + /** + * @brief Потокобезопасный генератор уникальных идентификаторов + * @tparam T Тип идентификатора (должен быть целочисленным) + * + * Используется для генерации уникальных ID в пределах типа T. + * Автоматически обрабатывает переполнение и обеспечивает потокобезопасность. + * Идеально для идентификации элементов в UI, графиках, базах данных и т.д. + */ template class identificator { - public: - identificator() { m_id = m_counter; ++m_counter; } + static_assert(std::is_integral_v, "T must be an integral type"); + static_assert(!std::is_same_v, "T cannot be bool"); - public: - T m_id; + public: + /// @brief Конструктор по умолчанию - генерирует новый уникальный ID + identificator() noexcept : m_id(next_id()) {} - private: - static inline T m_counter = 0; + /// @brief Конструктор копирования - создает новый ID (не копирует старый!) + identificator(const identificator&) noexcept : m_id(next_id()) {} + + /// @brief Конструктор перемещения - создает новый ID + identificator(identificator&&) noexcept : m_id(next_id()) {} + + /// @brief Оператор присваивания - не меняет ID объекта! + identificator& operator=(const identificator&) noexcept { return *this; } + + /// @brief Оператор перемещения - не меняет ID объекта! + identificator& operator=(identificator&&) noexcept { return *this; } + + /// @brief Сравнение идентификаторов + bool operator==(const identificator& other) const noexcept { return m_id == other.m_id; } + bool operator!=(const identificator& other) const noexcept { return m_id != other.m_id; } + bool operator<(const identificator& other) const noexcept { return m_id < other.m_id; } + + T get_id() const noexcept { return m_id; } + + private: + /// @brief Генерация следующего уникального ID + static T next_id() noexcept { + T new_id = m_counter.fetch_add(1, std::memory_order_relaxed); + // Обработка переполнения - циклическое повторение + if (new_id == std::numeric_limits::max()) + { + hack::warn()("Identificator is overflow limminent..."); + // Можно бросить исключение или сбросить счетчик + // В данном случае циклически повторяем + m_counter.store(1, std::memory_order_relaxed); + return new_id; // Возвращаем максимальное значение как особый случай + } + + return new_id; + } + + private: + T m_id; + // Атомарный счетчик для потокобезопасности + static inline std::atomic m_counter = 0; }; } diff --git a/src/hack/patterns/ring_buffer.OLD.hpp b/src/hack/patterns/ring_buffer.OLD.hpp new file mode 100644 index 0000000..832a7b9 --- /dev/null +++ b/src/hack/patterns/ring_buffer.OLD.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include + +#include "hack/exception/exception.hpp" + +namespace hack::patterns +{ + // Колцевой буфер. + template + class ring_buffer + { + using MUTEX = std::lock_guard; + + public: + ring_buffer() = default; + explicit ring_buffer(int s) + { + m_size = s; + m_data.resize(m_size); + }; + + public: + void create(int s) + { + if (m_size > 0) return; + m_size = s; + m_data.resize(m_size); + } + + void put(T item) noexcept + { + MUTEX lock(m_mutex); + m_data[m_head] = item; + head_refresh(); + } + + // указываем размер, который хотим положить + // т.к. может нужно положить только часть из переданного массива + void put(const std::vector& source, std::size_t size) + { + if (source.size() < size) + { + hack::exception ex; + ex.title(exception_title::NO_VALID_SIZE); + ex.description("data size is not equal source"); + throw ex; + } + + MUTEX lock(m_mutex); + for (std::size_t i = 0; i < size; ++i) + { + m_data[m_head] = source[i]; + head_refresh(); + } + } + + // если знаем, что нужно класть весь массив + void put(const std::vector& source) + { + put(source, source.size()); + } + + std::optional get() noexcept + { + MUTEX lock(m_mutex); + if(empty()) return std::nullopt; + + auto val = m_data[m_tail]; + m_tail = (m_tail + 1) % m_size; + m_full = false; + + return val; + } + + void get(std::vector& d, int n) + { + if (empty()) return; + + int c = m_tail; + for (int i = 0; i < n; ++i) + { + d[i] = m_data[c]; + c = (c + 1) % m_size; + } + } + + const std::vector& get_logger_data() const noexcept + { + return m_data; + } + + void skip(std::size_t n) + { + m_tail += n; + while (m_tail >= m_size) m_tail -= m_size; + } + + + bool empty() const noexcept + { + MUTEX lock(m_mutex); + return (!m_full && (m_head == m_tail)); + } + + std::size_t size() const noexcept + { + MUTEX lock(m_mutex); + + std::size_t s; + if (!m_full) s = (m_head >= m_tail) ? m_head - m_tail : m_size - (m_tail - m_head); + else s = m_size; + + return s; + } + + void reset() noexcept + { + MUTEX lock(m_mutex); + m_head = m_tail; + m_full = false; + } + + bool full() const noexcept + { + return m_full; + } + + std::size_t capacity() const noexcept + { + return m_size; + } + + private: + void head_refresh() + { + if (m_full) m_tail = (m_tail + 1) % m_size; + m_head = (m_head + 1) % m_size; + m_full = m_head == m_tail; + } + + private: + std::size_t m_head = 0; + std::size_t m_tail = 0; + std::size_t m_size = 0; + bool m_full = false; + + private: + mutable std::recursive_mutex m_mutex; + mutable std::vector m_data; + }; +} diff --git a/src/hack/patterns/ring_buffer.hpp b/src/hack/patterns/ring_buffer.hpp index ba553c8..3c88a64 100644 --- a/src/hack/patterns/ring_buffer.hpp +++ b/src/hack/patterns/ring_buffer.hpp @@ -6,151 +6,311 @@ #include #include "hack/exception/exception.hpp" +#include "hack/logger/logger.hpp" namespace hack::patterns { - // Колцевой буфер. template class ring_buffer { - using MUTEX = std::lock_guard; + // Используем рекурсивный мьютекс для возможности вызова методов из других методов + using mutex_type = std::recursive_mutex; + using lock_guard = std::lock_guard; - public: - ring_buffer() = default; - explicit ring_buffer(int s) - { - m_size = s; - m_data.resize(m_size); - }; - - public: - void create(int s) - { - if (m_size > 0) return; - m_size = s; - m_data.resize(m_size); - } - - void put(T item) noexcept - { - MUTEX lock(m_mutex); - m_data[m_head] = item; - head_refresh(); - } - - // указываем размер, который хотим положить - // т.к. может нужно положить только часть из переданного массива - void put(const std::vector& source, std::size_t size) - { - if (source.size() < size) - { - hack::exception ex; - ex.title(exception_title::NO_VALID_SIZE); - ex.description("data size is not equal source"); - throw ex; - } - - MUTEX lock(m_mutex); - for (std::size_t i = 0; i < size; ++i) - { - m_data[m_head] = source[i]; - head_refresh(); - } - } - - // если знаем, что нужно класть весь массив - void put(const std::vector& source) - { - put(source, source.size()); - } - - std::optional get() noexcept - { - MUTEX lock(m_mutex); - - if(empty()) return std::nullopt; - - auto val = m_data[m_tail]; - m_tail = (m_tail + 1) % m_size; - m_full = false; - - return val; - } - - void get(std::vector& d, int n) - { - if (empty()) return; - - int c = m_tail; - for (int i = 0; i < n; ++i) - { - d[i] = m_data[c]; - c = (c + 1) % m_size; - } - } - - const std::vector& get_logger_data() const noexcept - { - return m_data; - } - - void skip(std::size_t n) - { - m_tail += n; - while (m_tail >= m_size) m_tail -= m_size; - } - - - bool empty() const noexcept - { - MUTEX lock(m_mutex); - return (!m_full && (m_head == m_tail)); - } - - std::size_t size() const noexcept - { - MUTEX lock(m_mutex); - - std::size_t s; - if (!m_full) s = (m_head >= m_tail) ? m_head - m_tail : m_size - (m_tail - m_head); - else s = m_size; - - return s; - } - - void reset() noexcept - { - MUTEX lock(m_mutex); - m_head = m_tail; - m_full = false; - } - - bool full() const noexcept - { - return m_full; - } - - std::size_t capacity() const noexcept - { - return m_size; - } - - private: - void head_refresh() - { - if (m_full) m_tail = (m_tail + 1) % m_size; - m_head = (m_head + 1) % m_size; - m_full = m_head == m_tail; - } + public: + // Конструктор по умолчанию - создает неинициализированный буфер + // Не выделяет память, все поля остаются нулевыми + ring_buffer() = default; - private: - std::size_t m_head = 0; - std::size_t m_tail = 0; - std::size_t m_size = 0; - bool m_full = false; + // Конструктор с параметром - создает буфер заданного размера + // explicit предотвращает неявное преобразование + explicit ring_buffer(std::size_t size) : m_size(size), m_data(size) + { + // Инициализация через список инициализации: + // m_size(size) - устанавливает емкость буфера + // m_data(size) - создает вектор с заданным количеством элементов + // Элементы вектора инициализируются значением по умолчанию для типа T + } - private: - mutable std::recursive_mutex m_mutex; - mutable std::vector m_data; + // Инициализирует буфер заданным размером, если он еще не инициализирован + // Если буфер уже был инициализирован, игнорирует вызов (идиома "initialize once") + void create(std::size_t size) + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + if (m_size == 0) // Проверяем, не инициализирован ли уже буфер + { + m_size = size; // Устанавливаем размер буфера + m_data.resize(size); // Выделяем память под данные через resize() + // resize() гарантирует, что вектор будет содержать ровно size элементов + // Все элементы будут инициализированы значением по умолчанию для типа T + } + else hack::warn()("Buffer is initialize..."); + // Если буфер уже инициализирован (m_size > 0), игнорируем вызов + // Это предотвращает случайное пересоздание буфера + } + + // Добавляет один элемент в буфер (потокобезопасно) + // Возвращает true если элемент успешно добавлен, false если буфер не инициализирован + bool put(T item) noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + if (m_size == 0) return false; // Проверяем инициализацию буфера + + // Помещаем элемент в текущую позицию головы + m_data[m_head] = item; + advance_head(); // Обновляем позицию головы и флаги состояния + return true; + } + + // Добавляет диапазон элементов [first, last) в буфер + template + bool put(InputIt first, InputIt last) noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + if (m_size == 0) return 0; // Проверяем инициализацию буфера + + std::size_t count = 0; + // Итерируем по переданному диапазону от first до last + while (first != last) + { + // Копируем элемент из исходного диапазона в буфер + // Используем копирование, а не перемещение, чтобы не нарушать исходные данные + m_data[m_head] = *first; + advance_head(); // Обновляем позицию головы после каждого добавления + ++first; // Переходим к следующему элементу в исходном диапазоне + ++count; // Увеличиваем счетчик добавленных элементов + } + return count; // Возвращаем количество фактически добавленных элементов + } + + // Добавляет все элементы из вектора в буфер + // Удобная обертка над put для работы с std::vector + std::size_t put(const std::vector& source) noexcept + { + // Вызываем push_range с итераторами начала и конца вектора + return push_range(source.begin(), source.end()); + } + + // Добавляет указанное количество элементов из вектора в буфер + // Полезно когда нужно добавить только часть вектора + std::size_t put(const std::vector& source, std::size_t size) + { + // Проверяем, что запрошенный размер не превышает размер исходного вектора + if (source.size() < size) + { + // Создаем и бросаем исключение с информацией об ошибке + hack::exception ex; + ex.title(exception_title::NO_VALID_SIZE); + ex.description("data size is not equal source"); + throw ex; + } + + // Создаем временный диапазон из первых 'size' элементов вектора + auto first = source.begin(); + auto last = source.begin() + size; + return push_range(first, last); // Добавляем указанный диапазон + } + + // Извлекает один элемент из буфера (потокобезопасно) + // Возвращает std::optional - содержит элемент если буфер не пуст, или std::nullopt + // noexcept гарантирует, что метод не выбрасывает исключений + std::optional pop() noexcept + { + // Захватываем мьютекс для потокобезопасности + lock_guard lock(m_mutex); + // Проверяем, не пуст ли буфер + if (empty()) return std::nullopt; + + // Извлекаем элемент из хвоста с перемещением + // std::move позволяет избежать копирования и передать владение ресурсами + T value = std::move(m_data[m_tail]); + advance_tail(); // Обновляем позицию хвоста и снимаем флаг полноты + return value; // Возвращаем извлеченный элемент (неявно преобразуется в std::optional) + } + + // Извлекает несколько элементов в выходной итератор + // count - максимальное количество элементов для извлечения + // Возвращает количество фактически извлеченных элементов + std::size_t pop(std::vector& out, std::size_t count) noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + + // Определяем сколько элементов можно реально извлечь + // std::min гарантирует, что мы не попытаемся извлечь больше чем есть + std::size_t actual_count = std::min(count, size()); + + // Извлекаем элементы один за другим + for (std::size_t i = 0; i < actual_count; ++i) + { + // Перемещаем элемент в выходной итератор + // std::move передает владение ресурсами элемента + out.push_back(std::move(m_data[m_tail])); + advance_tail(); // Обновляем позицию хвоста после каждого извлечения + } + return actual_count; // Возвращаем количество извлеченных элементов + } + + // Возвращает один элемент из буфера (потокобезопасно) + // Возвращает std::optional - содержит элемент если буфер не пуст, или std::nullopt + // noexcept гарантирует, что метод не выбрасывает исключений + // Возвращенный элемент не затирается, хвост не сдвигается. Можно использовать просто для просмотра + std::optional get() noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + if (empty()) return std::nullopt; // Проверяем, не пуст ли буфер + return m_data[m_tail]; + } + + // Безопасная версия get() - копирует элементы в вектор с проверкой границ + // Возвращает количество фактически скопированных элементов + std::size_t get(std::vector& destination, std::size_t count) noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + if (empty()) return 0; // Проверяем, не пуст ли буфер + + // Определяем сколько элементов можно реально извлечь + std::size_t actual_count = std::min(count, size()); + actual_count = std::min(actual_count, destination.size()); // Учитываем размер приемника + + std::size_t current = m_tail; + for (std::size_t i = 0; i < actual_count; ++i) + { + // Копируем элемент из буфера в вектор-приемник + destination[i] = m_data[current]; + // Перемещаемся к следующему элементу с учетом круговой природы буфера + current = (current + 1) % m_size; + } + + return actual_count; + } + + // Возвращает копию внутренних данных для логирования (потокобезопасно) + // Создает копию, чтобы избежать проблем с одновременным доступом + std::vector get_logger_data() const noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + // Создаем копию данных для безопасного доступа извне + // Это предотвращает data race при одновременной модификации буфера + return m_data; + } + + // Пропускает n элементов в хвосте буфера (увеличивает позицию tail) + // Возвращает количество фактически пропущенных элементов + std::size_t skip(std::size_t n) noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности + + // Нельзя пропустить больше элементов чем есть в буфере + std::size_t actual_skip = std::min(n, size()); + + // Обновляем позицию хвоста с учетом круговой природы буфера + m_tail = (m_tail + actual_skip) % m_size; + + // Обновляем флаг полноты: + // Если буфер был полон и мы пропустили хоть один элемент, он больше не полон + m_full = m_full && (actual_skip == 0); + + return actual_skip; // Возвращаем количество пропущенных элементов + } + + // Проверяет, пуст ли буфер (потокобезопасно) + bool empty() const noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс + // Буфер пуст если: + // 1. Он не полон (m_full == false) + // 2. Голова совпадает с хвостом (m_head == m_tail) + return !m_full && m_head == m_tail; + } + + // Проверяет, полон ли буфер (потокобезопасно) + bool full() const noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс + return m_full; // Просто возвращаем значение флага + } + + // Возвращает текущее количество элементов в буфере (потокобезопасно) + std::size_t size() const noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс + if (m_full) return m_size; // Если буфер полон, размер равен емкости + + // Вычисляем размер в зависимости от относительного положения head и tail + if (m_head >= m_tail) + // Обычный случай: голова после хвоста в линейной памяти + return m_head - m_tail; + else + // Круговой случай: голова "перескочила" через конец и находится в начале + // Вычисляем: общий размер минус "пропуск" от tail до head + return m_size - (m_tail - m_head); + } + + // Возвращает максимальную вместимость буфера (потокобезопасно) + std::size_t capacity() const noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс + return m_size; // Просто возвращаем размер буфера + } + + // Сбрасывает буфер в начальное состояние (очищает) + // Не освобождает память, только сбрасывает указатели и флаги + void reset() noexcept + { + lock_guard lock(m_mutex); // Захватываем мьютекс + m_head = 0; // Полностью сбрасываем позиции в начало + m_tail = 0; // Полностью сбрасываем позиции в начало + m_full = false; // Сбрасываем флаг полноты + // Данные в векторе остаются, но будут перезаписаны при следующих операциях + } + + // Очищает буфер, сбрасывая все состояния (потокобезопасно) + // Аналогично reset(), но с более понятным именем + void clear() noexcept + { + reset(); // Просто вызываем reset() для consistency + } + + private: + // Внутренний метод для обновления позиции головы после добавления элемента + void advance_head() noexcept + { + // Если буфер полон, сдвигаем хвост (перезаписываем старые данные) + // Это реализация поведения "перезаписи старейших данных" + if (m_full) m_tail = (m_tail + 1) % m_size; + + // Сдвигаем голову с учетом круговой природы буфера + // % m_size обеспечивает круговое поведение + m_head = (m_head + 1) % m_size; + // Проверяем, не стал ли буфер полным после добавления + // Буфер полон если голова "догнала" хвост + m_full = (m_head == m_tail); + } + + // Внутренний метод для обновления позиции хвоста после извлечения элемента + void advance_tail() noexcept + { + // Сдвигаем хвост с учетом круговой природы буфера + // % m_size обеспечивает круговое поведение + m_tail = (m_tail + 1) % m_size; + // После извлечения элемента буфер точно не полон + // (если он был полон, теперь в нем есть свободное место) + m_full = false; + } + + private: + // Приватные поля класса: + std::size_t m_head = 0; // Индекс для следующей операции записи (голова) + std::size_t m_tail = 0; // Индекс для следующей операции чтения (хвост) + std::size_t m_size = 0; // Общая емкость буфера (количество элементов) + bool m_full = false; // Флаг, указывающий что буфер полностью заполнен + + // Мьютекс для обеспечения потокобезопасности + // mutable позволяет мьютексу быть изменяемым в const-методах + mutable mutex_type m_mutex; + + // Вектор для хранения данных буфера + // Хранит фактические элементы буфера + std::vector m_data; }; }