stdex
Additional custom or not Standard C++ covered algorithms
Loading...
Searching...
No Matches
ring.hpp
1/*
2 SPDX-License-Identifier: MIT
3 Copyright © 2023-2024 Amebis
4*/
5
6#pragma once
7
8#include "compat.hpp"
9#include <condition_variable>
10#include <mutex>
11#include <tuple>
12
13#if defined(__GNUC__)
14#pragma GCC diagnostic push
15#pragma GCC diagnostic ignored "-Wunknown-pragmas"
16#endif
17
18namespace stdex
19{
26 template <class T, size_t N_cap>
27 class ring
28 {
29 public:
30#pragma warning(suppress:26495) // Don't bother to initialize m_data
31 ring() :
32 m_head(0),
33 m_size(0),
34 m_quit(false)
35 {}
36
42 std::tuple<T*, size_t> back()
43 {
44 std::unique_lock<std::mutex> lk(m_mutex);
45 if (!space()) {
46 m_head_moved.wait(lk, [&]{return m_quit || space();});
47 if (m_quit) _Unlikely_
48 return { nullptr, 0 };
49 }
50 size_t tail = wrap(m_head + m_size);
51 return { &m_data[tail], m_head <= tail ? N_cap - tail : m_head - tail };
52 }
53
59 void push(_In_ size_t size)
60 {
61 {
62 const std::lock_guard<std::mutex> lg(m_mutex);
63#ifndef NDEBUG
64 size_t tail = wrap(m_head + m_size);
65 _Assume_(size <= (m_head <= tail ? N_cap - tail : m_head - tail));
66#endif
67 m_size += size;
68 }
69 m_tail_moved.notify_one();
70 }
71
77 std::tuple<T*, size_t> front()
78 {
79 std::unique_lock<std::mutex> lk(m_mutex);
80 if (empty()) {
81 m_tail_moved.wait(lk, [&]{return m_quit || !empty();});
82 if (m_quit && empty()) _Unlikely_
83 return { nullptr, 0 };
84 }
85 size_t tail = wrap(m_head + m_size);
86 return { &m_data[m_head], m_head < tail ? m_size : N_cap - m_head };
87 }
88
94 void pop(_In_ size_t size)
95 {
96 {
97 const std::lock_guard<std::mutex> lg(m_mutex);
98#ifndef NDEBUG
99 size_t tail = wrap(m_head + m_size);
100 _Assume_(size <= (m_head < tail ? m_size : N_cap - m_head));
101#endif
102 m_head = wrap(m_head + size);
103 m_size -= size;
104 }
105 m_head_moved.notify_one();
106 }
107
111 void quit()
112 {
113 {
114 const std::lock_guard<std::mutex> lg(m_mutex);
115 m_quit = true;
116 }
117 m_head_moved.notify_one();
118 m_tail_moved.notify_one();
119 }
120
124 void sync()
125 {
126 std::unique_lock<std::mutex> lk(m_mutex);
127 m_head_moved.wait(lk, [&]{return m_quit || empty();});
128 }
129
130 protected:
131 size_t wrap(_In_ size_t idx) const
132 {
133 // TODO: When N_cap is power of 2, use & ~(N_cap - 1) instead.
134 return idx % N_cap;
135 }
136
137 size_t space() const
138 {
139 return N_cap - m_size;
140 }
141
142 bool empty() const
143 {
144 return !m_size;
145 }
146
147 protected:
148 std::mutex m_mutex;
149 std::condition_variable m_head_moved, m_tail_moved;
150 size_t m_head, m_size;
151 bool m_quit;
152 T m_data[N_cap];
153 };
154}
155
156#if defined(__GNUC__)
157#pragma GCC diagnostic pop
158#endif
Ring buffer.
Definition ring.hpp:28
void pop(size_t size)
Notifies the sender the data was consumed.
Definition ring.hpp:94
std::tuple< T *, size_t > back()
Allocates the data after the ring tail. Use push() after the allocated data is populated.
Definition ring.hpp:42
void quit()
Cancells waiting sender and receiver.
Definition ring.hpp:111
std::tuple< T *, size_t > front()
Peeks the data at the ring head. Use pop() after the data was consumed.
Definition ring.hpp:77
void push(size_t size)
Notifies the receiver the data was populated.
Definition ring.hpp:59
void sync()
Waits until the ring is flush.
Definition ring.hpp:124