diff --git a/src/EterLib/SPSCQueue.h b/src/EterLib/SPSCQueue.h new file mode 100644 index 0000000..10ae55b --- /dev/null +++ b/src/EterLib/SPSCQueue.h @@ -0,0 +1,79 @@ +#ifndef __INC_ETERLIB_SPSCQUEUE_H__ +#define __INC_ETERLIB_SPSCQUEUE_H__ + +#include +#include +#include + +// Lock-free queue for single producer/consumer pairs +template +class SPSCQueue +{ +public: + explicit SPSCQueue(size_t capacity) + : m_capacity(capacity + 1) // +1 to distinguish full from empty + , m_buffer(m_capacity) + , m_head(0) + , m_tail(0) + { + assert(capacity > 0); + } + + ~SPSCQueue() + { + } + + // Push item (returns false if full) + bool Push(const T& item) + { + const size_t head = m_head.load(std::memory_order_relaxed); + const size_t next_head = (head + 1) % m_capacity; + + if (next_head == m_tail.load(std::memory_order_acquire)) + return false; // Queue is full + + m_buffer[head] = item; + m_head.store(next_head, std::memory_order_release); + return true; + } + + // Pop item (returns false if empty) + bool Pop(T& item) + { + const size_t tail = m_tail.load(std::memory_order_relaxed); + + if (tail == m_head.load(std::memory_order_acquire)) + return false; // Queue is empty + + item = m_buffer[tail]; + m_tail.store((tail + 1) % m_capacity, std::memory_order_release); + return true; + } + + // Check if empty + bool IsEmpty() const + { + return m_tail.load(std::memory_order_acquire) == m_head.load(std::memory_order_acquire); + } + + // Get queue size + size_t Size() const + { + const size_t head = m_head.load(std::memory_order_acquire); + const size_t tail = m_tail.load(std::memory_order_acquire); + + if (head >= tail) + return head - tail; + else + return m_capacity - tail + head; + } + +private: + const size_t m_capacity; + std::vector m_buffer; + + alignas(64) std::atomic m_head; + alignas(64) std::atomic m_tail; +}; + +#endif // __INC_ETERLIB_SPSCQUEUE_H__