Etterna 0.74.4
Loading...
Searching...
No Matches
RageUtil_CircularBuffer.h
1/* CircBuf - A fast, thread-safe, lockless circular buffer. */
2
3#ifndef RAGE_UTIL_CIRCULAR_BUFFER
4#define RAGE_UTIL_CIRCULAR_BUFFER
5
6/* Lock-free circular buffer. This should be threadsafe if one thread is
7 * reading and another is writing. */
8template<class T>
9class CircBuf
10{
11 T* buf;
12 /* read_pos is the position data is read from; write_pos is the position
13 * data is written to. If read_pos == write_pos, the buffer is empty.
14 *
15 * There will always be at least one position empty, as a completely full
16 * buffer (read_pos == write_pos) is indistinguishable from an empty buffer.
17 *
18 * Invariants: read_pos < size, write_pos < size. */
19 unsigned size = 0;
20 unsigned m_iBlockSize = 0;
21
22 /* These are volatile to prevent reads and writes to them from being
23 * optimized. */
24 volatile unsigned read_pos, write_pos;
25
26 public:
27 CircBuf()
28 {
29 buf = NULL;
30 clear();
31 }
32
33 ~CircBuf() { delete[] buf; }
34
35 void swap(CircBuf& rhs)
36 {
37 std::swap(size, rhs.size);
38 std::swap(m_iBlockSize, rhs.m_iBlockSize);
39 std::swap(read_pos, rhs.read_pos);
40 std::swap(write_pos, rhs.write_pos);
41 std::swap(buf, rhs.buf);
42 }
43
44 CircBuf& operator=(const CircBuf& rhs)
45 {
46 CircBuf c(rhs);
47 this->swap(c);
48 return *this;
49 }
50
51 CircBuf(const CircBuf& cpy)
52 {
53 size = cpy.size;
54 read_pos = cpy.read_pos;
55 write_pos = cpy.write_pos;
56 m_iBlockSize = cpy.m_iBlockSize;
57 if (size) {
58 buf = new T[size];
59 memcpy(buf, cpy.buf, size * sizeof(T));
60 } else {
61 buf = nullptr;
62 }
63 }
64
65 /* Return the number of elements available to read. */
66 unsigned num_readable() const
67 {
68 const int rpos = read_pos;
69 const int wpos = write_pos;
70 if (rpos < wpos)
71 /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */
72 return wpos - rpos;
73 else if (rpos > wpos)
74 /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */
75 return size - (rpos - wpos);
76 else // if( rpos == wpos )
77 /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */
78 return 0;
79 }
80
81 /* Return the number of writable elements. */
82 unsigned num_writable() const
83 {
84 const int rpos = read_pos;
85 const int wpos = write_pos;
86
87 int ret;
88 if (rpos < wpos)
89 /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */
90 ret = size - (wpos - rpos);
91 else if (rpos > wpos)
92 /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */
93 ret = rpos - wpos;
94 else // if( rpos == wpos )
95 /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */
96 ret = size;
97
98 /* Subtract the blocksize, to account for the element that we never fill
99 * while keeping the entries aligned to m_iBlockSize. */
100 return ret - m_iBlockSize;
101 }
102
103 unsigned capacity() const { return size; }
104
105 void reserve(unsigned n, int iBlockSize = 1)
106 {
107 m_iBlockSize = iBlockSize;
108
109 clear();
110 delete[] buf;
111 buf = NULL;
112
113 /* Reserve an extra byte. We'll never fill more than n bytes; the extra
114 * byte is to guarantee that read_pos != write_pos when the buffer is
115 * full, since that would be ambiguous with an empty buffer. */
116 if (n != 0) {
117 size = n + 1;
118 size =
119 ((size + iBlockSize - 1) / iBlockSize) * iBlockSize; // round up
120
121 buf = new T[size];
122 } else
123 size = 0;
124 }
125
126 void clear() { read_pos = write_pos = 0; }
127
128 /* Indicate that n elements have been written. */
129 void advance_write_pointer(int n) { write_pos = (write_pos + n) % size; }
130
131 /* Indicate that n elements have been read. */
132 void advance_read_pointer(int n) { read_pos = (read_pos + n) % size; }
133
134 void get_write_pointers(T* pPointers[2], unsigned pSizes[2])
135 {
136 const int rpos = read_pos;
137 const int wpos = write_pos;
138
139 if (rpos <= wpos) {
140 /* The buffer looks like "eeeeDDDDeeee" or "eeeeeeeeeeee" (e =
141 * empty, D = data). */
142 pPointers[0] = buf + wpos;
143 pPointers[1] = buf;
144
145 pSizes[0] = size - wpos;
146 pSizes[1] = rpos;
147 } else if (rpos > wpos) {
148 /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */
149 pPointers[0] = buf + wpos;
150 pPointers[1] = NULL;
151
152 pSizes[0] = rpos - wpos;
153 pSizes[1] = 0;
154 }
155
156 /* Subtract the blocksize, to account for the element that we never fill
157 * while keeping the entries aligned to m_iBlockSize. */
158 if (pSizes[1])
159 pSizes[1] -= m_iBlockSize;
160 else
161 pSizes[0] -= m_iBlockSize;
162 }
163
164 /* Like get_write_pointers, but only return the first range available. */
165 T* get_write_pointer(unsigned* pSizes)
166 {
167 T* pBothPointers[2];
168 unsigned iBothSizes[2];
169 get_write_pointers(pBothPointers, iBothSizes);
170 *pSizes = iBothSizes[0];
171 return pBothPointers[0];
172 }
173
174 void get_read_pointers(T* pPointers[2], unsigned pSizes[2])
175 {
176 const int rpos = read_pos;
177 const int wpos = write_pos;
178
179 if (rpos < wpos) {
180 /* The buffer looks like "eeeeDDDDeeee" (e = empty, D = data). */
181 pPointers[0] = buf + rpos;
182 pPointers[1] = NULL;
183
184 pSizes[0] = wpos - rpos;
185 pSizes[1] = 0;
186 } else if (rpos > wpos) {
187 /* The buffer looks like "DDeeeeeeeeDD" (e = empty, D = data). */
188 pPointers[0] = buf + rpos;
189 pPointers[1] = buf;
190
191 pSizes[0] = size - rpos;
192 pSizes[1] = wpos;
193 } else {
194 /* The buffer looks like "eeeeeeeeeeee" (e = empty, D = data). */
195 pPointers[0] = NULL;
196 pPointers[1] = NULL;
197
198 pSizes[0] = 0;
199 pSizes[1] = 0;
200 }
201 }
202
203 /* Write buffer_size elements from buffer, and advance the write pointer. If
204 * the data will not fit entirely, the write pointer will be unchanged
205 * and false will be returned. */
206 bool write(const T* buffer, unsigned buffer_size)
207 {
208 T* p[2];
209 unsigned sizes[2];
210 get_write_pointers(p, sizes);
211
212 if (buffer_size > sizes[0] + sizes[1])
213 return false;
214
215 const int from_first = buffer_size <= sizes[0] ? buffer_size : sizes[0];
216 memcpy(p[0], buffer, from_first * sizeof(T));
217 if (buffer_size > sizes[0])
218 memcpy(
219 p[1], buffer + from_first, (buffer_size - sizes[0]) * sizeof(T));
220
221 advance_write_pointer(buffer_size);
222
223 return true;
224 }
225
226 /* Read buffer_size elements from buffer, and advance the read pointer. If
227 * the buffer can not be filled completely, the read pointer will be
228 * unchanged and false will be returned. */
229 bool read(T* buffer, unsigned buffer_size)
230 {
231 T* p[2];
232 unsigned sizes[2];
233 get_read_pointers(p, sizes);
234
235 if (buffer_size > sizes[0] + sizes[1])
236 return false;
237
238 const int from_first = buffer_size <= sizes[0] ? buffer_size : sizes[0];
239 memcpy(buffer, p[0], from_first * sizeof(T));
240 if (buffer_size > sizes[0])
241 memcpy(
242 buffer + from_first, p[1], (buffer_size - sizes[0]) * sizeof(T));
243
244 /* Set the data that we just read to 0xFF. This way, if we're passing
245 * pointesr through, we can tell if we accidentally get a stale pointer.
246 */
247 memset(p[0], 0xFF, from_first * sizeof(T));
248 if (buffer_size > sizes[0])
249 memset(p[1], 0xFF, (buffer_size - sizes[0]) * sizeof(T));
250
251 advance_read_pointer(buffer_size);
252 return true;
253 }
254};
255
256#endif
Definition RageUtil_CircularBuffer.h:10