-
Notifications
You must be signed in to change notification settings - Fork 4
/
circular_buffer.h
302 lines (224 loc) · 6.17 KB
/
circular_buffer.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#ifndef CIRCULAR_BUFFER_H_
#define CIRCULAR_BUFFER_H_
#include <stdlib.h>
#include <stdbool.h>
struct circular_buffer_t {
char *buf;
size_t head;
size_t tail;
bool hbehind;
size_t sz;
};
int circular_buffer_init(struct circular_buffer_t *b, size_t sz);
void circular_buffer_destroy(struct circular_buffer_t *b);
size_t circular_buffer_get_free(struct circular_buffer_t *b);
size_t circular_buffer_get_ready(struct circular_buffer_t *b);
void circular_buffer_advance_head(struct circular_buffer_t *b, size_t s);
void circular_buffer_advance_tail(struct circular_buffer_t *b, size_t s);
/*
struct circular_buffer is a simple but quite efficient implementation
of a circular buffer over a continuous and finite memory slice.
Here is a its documentation. Enjoy it!
First, let's load this module to play with it
```cpp
.L circular_buffer.c
#include "circular_buffer.h"
#include <string.h>
```
Now, let's create a buffer of 16 bytes
```cpp
struct circular_buffer_t buf;
circular_buffer_init(&buf, 16);
```
The circular buffer has 2 pointers, the head
that mark the end of the data and the begin of the
free space and the tail, which it is the opposite of
the head.
At the begin, both pointers point to the same place,
the free space has the size of the whole buffer and the
ready space is empty
* /- head/tail
* V
* +--------------------------+
* | |
* +--------------------------+
```cpp
(buf.head == buf.tail)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
out:
(bool) true
(unsigned long) 16
(unsigned long) 0
```
When more data is written in the buffer, the
head pointer moves forward, the free space
is reduced and the ready space is increased,
all of them in the same proportion.
* /- tail /- head
* V V
* +--------------------------+
* |::::::::::::: |
* +--------------------------+
The implementation does not track how many bytes are written.
It is up to the caller to write in the buffer from
the buffer's head and notify the circular buffer how many
bytes wrote.
For example, if we write 10 bytes:
```cpp
memcpy(&buf.buf[buf.head], "AABBCCDDEE", 10);
```
Then we must notify how many bytes were written updating
the head pointer:
```cpp
circular_buffer_advance_head(&buf, 10);
(buf.head > buf.tail)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
out:
(bool) true
(unsigned long) 6
(unsigned long) 10
```
We can keep writing and reading from the buffer.
As we did with the write, once we read the data from
the buffer's tail we need to notify to the circular buffer
that the data can be discarded.
* /- tail /- head
* V V
* +--------------------------+
* | ::::::::::: |
* +--------------------------+
```cpp
memcpy(&buf.buf[buf.head], "FFGG", 4);
circular_buffer_advance_head(&buf, 4);
char read[8];
memcpy(read, &buf.buf[buf.tail], 8);
circular_buffer_advance_tail(&buf, 8);
read
out:
(char [8]) "AABBCCDD"
```
How many bytes we can write is determined by how many
free space the buffer has; how many bytes we can read
is determined by how many ready space the buffer has.
It is up to the caller honor this.
```cpp
(buf.head > buf.tail)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
out:
(bool) true
(unsigned long) 2
(unsigned long) 6
```
When the head pointer reaches the end of the buffer, the
header is restarted at the begin.
The free space is expanded from the head to the tail and
the tail is in front of the head:
* /- head /- tail
* V V
* +--------------------------+
* | :::::::::::::|
* +--------------------------+
```cpp
circular_buffer_advance_head(&buf, 2);
(buf.tail > buf.head)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
out:
(bool) true
(unsigned long) 8
(unsigned long) 8
```
If we keep writing (moving the head), the read space will not
increase because the circular buffer will always inform how many
*contiguous* bytes are ready (for reading) or free (for writing)
The ready space is limited by the end of the buffer:
* /- head /- tail
* V V
* +--------------------------+
* |:::: :::::::::::::|
* +--------------------------+
*
```cpp
circular_buffer_advance_head(&buf, 2);
(buf.tail > buf.head)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
out:
(bool) true
(unsigned long) 6
(unsigned long) 8
```
The critical point happens when the head reaches the tail.
Now, this is exactly the same situation that happen at the begin,
when the buffer was empty, but now it means that it is full.
* /- head/tail
* V
* +--------------------------+
* |::::::::::::::::::::::::::|
* +--------------------------+
*
To differentiate these two cases, internally there is a flag
that tracks when the head is *behind* the tail
```cpp
circular_buffer_advance_head(&buf, 6);
(buf.tail >= buf.head)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
(buf.hbehind)
out:
(bool) true
(unsigned long) 0
(unsigned long) 8
(bool) true
```
If we move the tail to the end of the buffer, the
rest of the bytes written are ready to be consumed and
the head is in front of the tail again
* /- tail /- head
* V V
* +--------------------------+
* |:::::::::::::: |
* +--------------------------+
*
```cpp
circular_buffer_advance_tail(&buf, 8);
(buf.head >= buf.tail)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
(buf.hbehind)
out:
(bool) true
(unsigned long) 8
(unsigned long) 8
(bool) false
```
If we move the head to the end we will reach again to
the ambiguous cases but the ``hbehind`` variable tell us that
the head is behind the tail again
* /- head/tail
* V
* +--------------------------+
* |::::::::::::::::::::::::::|
* +--------------------------+
*
```cpp
circular_buffer_advance_head(&buf, 8);
(buf.tail >= buf.head)
circular_buffer_get_free(&buf)
circular_buffer_get_ready(&buf)
(buf.hbehind)
out:
(bool) true
(unsigned long) 0
(unsigned long) 16
(bool) true
```
Finally, do not forget to destroy the buffer
```cpp
circular_buffer_destroy(&buf);
```
*/
#endif