add new ring buffer
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#include "hack/patterns/ring_buffer.hpp"
|
|
||||||
#include "hack/patterns/identificator.hpp"
|
#include "hack/patterns/identificator.hpp"
|
||||||
#include "hack/logger/logger.hpp"
|
#include "hack/logger/logger.hpp"
|
||||||
|
#include "hack/patterns/ring_buffer.hpp"
|
||||||
|
|
||||||
auto main(int argc, char *argv[]) -> int
|
auto main(int argc, char *argv[]) -> int
|
||||||
{
|
{
|
||||||
@@ -8,21 +8,34 @@ auto main(int argc, char *argv[]) -> int
|
|||||||
hack::patterns::ring_buffer<int> rb;
|
hack::patterns::ring_buffer<int> rb;
|
||||||
rb.create(10);
|
rb.create(10);
|
||||||
for (int i = 0; i < 10; ++i) rb.put(i);
|
for (int i = 0; i < 10; ++i) rb.put(i);
|
||||||
hack::log()(rb);
|
hack::log()("rb =", rb);
|
||||||
hack::log()(rb.size());
|
hack::log()("size =", rb.size());
|
||||||
rb.skip(3);
|
rb.skip(3);
|
||||||
|
hack::log()(rb.pop().value());
|
||||||
|
hack::log()("size =", rb.size());
|
||||||
hack::log()(rb.get().value());
|
hack::log()(rb.get().value());
|
||||||
hack::log()(rb.size());
|
hack::log()("size =", rb.size());
|
||||||
std::vector<int> v(3);
|
std::vector<int> v(3);
|
||||||
rb.get(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
|
// identificator
|
||||||
struct id_struct : public hack::patterns::identificator<> {} aa;
|
struct id_struct : public hack::patterns::identificator<> {} aa;
|
||||||
id_struct bb;
|
id_struct bb;
|
||||||
id_struct cc;
|
id_struct cc;
|
||||||
id_struct dd;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ executable(
|
|||||||
# 'examples/concepts/main.cpp',
|
# 'examples/concepts/main.cpp',
|
||||||
# 'examples/math/main.cpp',
|
# 'examples/math/main.cpp',
|
||||||
# 'examples/range/main.cpp',
|
# 'examples/range/main.cpp',
|
||||||
# 'examples/patterns/main.cpp',
|
'examples/patterns/main.cpp',
|
||||||
'examples/logger/main.cpp',
|
# 'examples/logger/main.cpp',
|
||||||
dependencies : deps,
|
dependencies : deps,
|
||||||
cpp_args: args,
|
cpp_args: args,
|
||||||
include_directories : inc
|
include_directories : inc
|
||||||
|
|||||||
@@ -1,22 +1,70 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <atomic>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
namespace hack::patterns
|
namespace hack::patterns
|
||||||
{
|
{
|
||||||
// Иногда нужно, чтобы был id но в виде какого-то числа.
|
/**
|
||||||
// Например при выводе графики в массиве, типа как в VueJS
|
* @brief Потокобезопасный генератор уникальных идентификаторов
|
||||||
// вот этьо класс и пытается этим заниматься.
|
* @tparam T Тип идентификатора (должен быть целочисленным)
|
||||||
|
*
|
||||||
|
* Используется для генерации уникальных ID в пределах типа T.
|
||||||
|
* Автоматически обрабатывает переполнение и обеспечивает потокобезопасность.
|
||||||
|
* Идеально для идентификации элементов в UI, графиках, базах данных и т.д.
|
||||||
|
*/
|
||||||
template<typename T = std::size_t>
|
template<typename T = std::size_t>
|
||||||
class identificator
|
class identificator
|
||||||
{
|
{
|
||||||
public:
|
static_assert(std::is_integral_v<T>, "T must be an integral type");
|
||||||
identificator() { m_id = m_counter; ++m_counter; }
|
static_assert(!std::is_same_v<T, bool>, "T cannot be bool");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
T m_id;
|
/// @brief Конструктор по умолчанию - генерирует новый уникальный ID
|
||||||
|
identificator() noexcept : m_id(next_id()) {}
|
||||||
|
|
||||||
private:
|
/// @brief Конструктор копирования - создает новый ID (не копирует старый!)
|
||||||
static inline T m_counter = 0;
|
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<T>::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<T> m_counter = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
155
src/hack/patterns/ring_buffer.OLD.hpp
Normal file
155
src/hack/patterns/ring_buffer.OLD.hpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "hack/exception/exception.hpp"
|
||||||
|
|
||||||
|
namespace hack::patterns
|
||||||
|
{
|
||||||
|
// Колцевой буфер.
|
||||||
|
template<typename T>
|
||||||
|
class ring_buffer
|
||||||
|
{
|
||||||
|
using MUTEX = std::lock_guard<std::recursive_mutex>;
|
||||||
|
|
||||||
|
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<T>& 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<T>& source)
|
||||||
|
{
|
||||||
|
put(source, source.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<T> 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<T>& 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<T>& 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<T> m_data;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,151 +6,311 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "hack/exception/exception.hpp"
|
#include "hack/exception/exception.hpp"
|
||||||
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
namespace hack::patterns
|
namespace hack::patterns
|
||||||
{
|
{
|
||||||
// Колцевой буфер.
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class ring_buffer
|
class ring_buffer
|
||||||
{
|
{
|
||||||
using MUTEX = std::lock_guard<std::recursive_mutex>;
|
// Используем рекурсивный мьютекс для возможности вызова методов из других методов
|
||||||
|
using mutex_type = std::recursive_mutex;
|
||||||
|
using lock_guard = std::lock_guard<mutex_type>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ring_buffer() = default;
|
// Конструктор по умолчанию - создает неинициализированный буфер
|
||||||
explicit ring_buffer(int s)
|
// Не выделяет память, все поля остаются нулевыми
|
||||||
{
|
ring_buffer() = default;
|
||||||
m_size = s;
|
|
||||||
m_data.resize(m_size);
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
// Конструктор с параметром - создает буфер заданного размера
|
||||||
void create(int s)
|
// explicit предотвращает неявное преобразование
|
||||||
|
explicit ring_buffer(std::size_t size) : m_size(size), m_data(size)
|
||||||
|
{
|
||||||
|
// Инициализация через список инициализации:
|
||||||
|
// m_size(size) - устанавливает емкость буфера
|
||||||
|
// m_data(size) - создает вектор с заданным количеством элементов
|
||||||
|
// Элементы вектора инициализируются значением по умолчанию для типа T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализирует буфер заданным размером, если он еще не инициализирован
|
||||||
|
// Если буфер уже был инициализирован, игнорирует вызов (идиома "initialize once")
|
||||||
|
void create(std::size_t size)
|
||||||
|
{
|
||||||
|
lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности
|
||||||
|
if (m_size == 0) // Проверяем, не инициализирован ли уже буфер
|
||||||
{
|
{
|
||||||
if (m_size > 0) return;
|
m_size = size; // Устанавливаем размер буфера
|
||||||
m_size = s;
|
m_data.resize(size); // Выделяем память под данные через resize()
|
||||||
m_data.resize(m_size);
|
// 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<typename InputIt>
|
||||||
|
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<T>& source) noexcept
|
||||||
|
{
|
||||||
|
// Вызываем push_range с итераторами начала и конца вектора
|
||||||
|
return push_range(source.begin(), source.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляет указанное количество элементов из вектора в буфер
|
||||||
|
// Полезно когда нужно добавить только часть вектора
|
||||||
|
std::size_t put(const std::vector<T>& 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(T item) noexcept
|
// Создаем временный диапазон из первых 'size' элементов вектора
|
||||||
|
auto first = source.begin();
|
||||||
|
auto last = source.begin() + size;
|
||||||
|
return push_range(first, last); // Добавляем указанный диапазон
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекает один элемент из буфера (потокобезопасно)
|
||||||
|
// Возвращает std::optional<T> - содержит элемент если буфер не пуст, или std::nullopt
|
||||||
|
// noexcept гарантирует, что метод не выбрасывает исключений
|
||||||
|
std::optional<T> 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<T>& 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)
|
||||||
{
|
{
|
||||||
MUTEX lock(m_mutex);
|
// Перемещаем элемент в выходной итератор
|
||||||
m_data[m_head] = item;
|
// std::move передает владение ресурсами элемента
|
||||||
head_refresh();
|
out.push_back(std::move(m_data[m_tail]));
|
||||||
|
advance_tail(); // Обновляем позицию хвоста после каждого извлечения
|
||||||
|
}
|
||||||
|
return actual_count; // Возвращаем количество извлеченных элементов
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращает один элемент из буфера (потокобезопасно)
|
||||||
|
// Возвращает std::optional<T> - содержит элемент если буфер не пуст, или std::nullopt
|
||||||
|
// noexcept гарантирует, что метод не выбрасывает исключений
|
||||||
|
// Возвращенный элемент не затирается, хвост не сдвигается. Можно использовать просто для просмотра
|
||||||
|
std::optional<T> get() noexcept
|
||||||
|
{
|
||||||
|
lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности
|
||||||
|
if (empty()) return std::nullopt; // Проверяем, не пуст ли буфер
|
||||||
|
return m_data[m_tail];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Безопасная версия get() - копирует элементы в вектор с проверкой границ
|
||||||
|
// Возвращает количество фактически скопированных элементов
|
||||||
|
std::size_t get(std::vector<T>& 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;
|
||||||
// т.к. может нужно положить только часть из переданного массива
|
}
|
||||||
void put(const std::vector<T>& 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)
|
// Создает копию, чтобы избежать проблем с одновременным доступом
|
||||||
{
|
std::vector<T> get_logger_data() const noexcept
|
||||||
m_data[m_head] = source[i];
|
{
|
||||||
head_refresh();
|
lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности
|
||||||
}
|
// Создаем копию данных для безопасного доступа извне
|
||||||
}
|
// Это предотвращает data race при одновременной модификации буфера
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
|
||||||
// если знаем, что нужно класть весь массив
|
// Пропускает n элементов в хвосте буфера (увеличивает позицию tail)
|
||||||
void put(const std::vector<T>& source)
|
// Возвращает количество фактически пропущенных элементов
|
||||||
{
|
std::size_t skip(std::size_t n) noexcept
|
||||||
put(source, source.size());
|
{
|
||||||
}
|
lock_guard lock(m_mutex); // Захватываем мьютекс для потокобезопасности
|
||||||
|
|
||||||
std::optional<T> get() noexcept
|
// Нельзя пропустить больше элементов чем есть в буфере
|
||||||
{
|
std::size_t actual_skip = std::min(n, size());
|
||||||
MUTEX lock(m_mutex);
|
|
||||||
|
|
||||||
if(empty()) return std::nullopt;
|
// Обновляем позицию хвоста с учетом круговой природы буфера
|
||||||
|
m_tail = (m_tail + actual_skip) % m_size;
|
||||||
|
|
||||||
auto val = m_data[m_tail];
|
// Обновляем флаг полноты:
|
||||||
m_tail = (m_tail + 1) % m_size;
|
// Если буфер был полон и мы пропустили хоть один элемент, он больше не полон
|
||||||
m_full = false;
|
m_full = m_full && (actual_skip == 0);
|
||||||
|
|
||||||
return val;
|
return actual_skip; // Возвращаем количество пропущенных элементов
|
||||||
}
|
}
|
||||||
|
|
||||||
void get(std::vector<T>& d, int n)
|
// Проверяет, пуст ли буфер (потокобезопасно)
|
||||||
{
|
bool empty() const noexcept
|
||||||
if (empty()) return;
|
{
|
||||||
|
lock_guard lock(m_mutex); // Захватываем мьютекс
|
||||||
|
// Буфер пуст если:
|
||||||
|
// 1. Он не полон (m_full == false)
|
||||||
|
// 2. Голова совпадает с хвостом (m_head == m_tail)
|
||||||
|
return !m_full && m_head == m_tail;
|
||||||
|
}
|
||||||
|
|
||||||
int c = m_tail;
|
// Проверяет, полон ли буфер (потокобезопасно)
|
||||||
for (int i = 0; i < n; ++i)
|
bool full() const noexcept
|
||||||
{
|
{
|
||||||
d[i] = m_data[c];
|
lock_guard lock(m_mutex); // Захватываем мьютекс
|
||||||
c = (c + 1) % m_size;
|
return m_full; // Просто возвращаем значение флага
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<T>& get_logger_data() const noexcept
|
// Возвращает текущее количество элементов в буфере (потокобезопасно)
|
||||||
{
|
std::size_t size() const noexcept
|
||||||
return m_data;
|
{
|
||||||
}
|
lock_guard lock(m_mutex); // Захватываем мьютекс
|
||||||
|
if (m_full) return m_size; // Если буфер полон, размер равен емкости
|
||||||
|
|
||||||
void skip(std::size_t n)
|
// Вычисляем размер в зависимости от относительного положения head и tail
|
||||||
{
|
if (m_head >= m_tail)
|
||||||
m_tail += n;
|
// Обычный случай: голова после хвоста в линейной памяти
|
||||||
while (m_tail >= m_size) m_tail -= m_size;
|
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; // Просто возвращаем размер буфера
|
||||||
|
}
|
||||||
|
|
||||||
bool empty() const noexcept
|
// Сбрасывает буфер в начальное состояние (очищает)
|
||||||
{
|
// Не освобождает память, только сбрасывает указатели и флаги
|
||||||
MUTEX lock(m_mutex);
|
void reset() noexcept
|
||||||
return (!m_full && (m_head == m_tail));
|
{
|
||||||
}
|
lock_guard lock(m_mutex); // Захватываем мьютекс
|
||||||
|
m_head = 0; // Полностью сбрасываем позиции в начало
|
||||||
|
m_tail = 0; // Полностью сбрасываем позиции в начало
|
||||||
|
m_full = false; // Сбрасываем флаг полноты
|
||||||
|
// Данные в векторе остаются, но будут перезаписаны при следующих операциях
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t size() const noexcept
|
// Очищает буфер, сбрасывая все состояния (потокобезопасно)
|
||||||
{
|
// Аналогично reset(), но с более понятным именем
|
||||||
MUTEX lock(m_mutex);
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
reset(); // Просто вызываем reset() для consistency
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t s;
|
private:
|
||||||
if (!m_full) s = (m_head >= m_tail) ? m_head - m_tail : m_size - (m_tail - m_head);
|
// Внутренний метод для обновления позиции головы после добавления элемента
|
||||||
else s = m_size;
|
void advance_head() noexcept
|
||||||
|
{
|
||||||
|
// Если буфер полон, сдвигаем хвост (перезаписываем старые данные)
|
||||||
|
// Это реализация поведения "перезаписи старейших данных"
|
||||||
|
if (m_full) m_tail = (m_tail + 1) % m_size;
|
||||||
|
|
||||||
return s;
|
// Сдвигаем голову с учетом круговой природы буфера
|
||||||
}
|
// % m_size обеспечивает круговое поведение
|
||||||
|
m_head = (m_head + 1) % m_size;
|
||||||
|
// Проверяем, не стал ли буфер полным после добавления
|
||||||
|
// Буфер полон если голова "догнала" хвост
|
||||||
|
m_full = (m_head == m_tail);
|
||||||
|
}
|
||||||
|
|
||||||
void reset() noexcept
|
// Внутренний метод для обновления позиции хвоста после извлечения элемента
|
||||||
{
|
void advance_tail() noexcept
|
||||||
MUTEX lock(m_mutex);
|
{
|
||||||
m_head = m_tail;
|
// Сдвигаем хвост с учетом круговой природы буфера
|
||||||
m_full = false;
|
// % m_size обеспечивает круговое поведение
|
||||||
}
|
m_tail = (m_tail + 1) % m_size;
|
||||||
|
// После извлечения элемента буфер точно не полон
|
||||||
|
// (если он был полон, теперь в нем есть свободное место)
|
||||||
|
m_full = false;
|
||||||
|
}
|
||||||
|
|
||||||
bool full() const noexcept
|
private:
|
||||||
{
|
// Приватные поля класса:
|
||||||
return m_full;
|
std::size_t m_head = 0; // Индекс для следующей операции записи (голова)
|
||||||
}
|
std::size_t m_tail = 0; // Индекс для следующей операции чтения (хвост)
|
||||||
|
std::size_t m_size = 0; // Общая емкость буфера (количество элементов)
|
||||||
|
bool m_full = false; // Флаг, указывающий что буфер полностью заполнен
|
||||||
|
|
||||||
std::size_t capacity() const noexcept
|
// Мьютекс для обеспечения потокобезопасности
|
||||||
{
|
// mutable позволяет мьютексу быть изменяемым в const-методах
|
||||||
return m_size;
|
mutable mutex_type m_mutex;
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
// Вектор для хранения данных буфера
|
||||||
void head_refresh()
|
// Хранит фактические элементы буфера
|
||||||
{
|
std::vector<T> m_data;
|
||||||
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<T> m_data;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user