randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rring
1#pragma once
2
3#include <algorithm> // std::min and std::max
4#include <cstring>
5#include <mutex>
6#include <new>
7#include <tuple>
8#include <vector>
9
10#include <randolf/rline>
11#include <randolf/rring_bam>
12
13namespace randolf {
14
15 /*======================================================================*//**
16 @brief
17 Ring buffer implementation for 8-bit bytes that's threadsafe and resizeable,
18 and the methods are designed to be efficient in various ways, including by
19 supporting the transfer of multiple bytes in variable quantities.
20
21 Data is stored internally as a @c char* array, but a few methods are provided
22 to also support easy interoperability with @c std::vector objects since there
23 are many scenarios where this can be helpful (and from the perspective of
24 optimization, this eliminates the cumbersome need to utilize conversions
25 between @c std::string and @c std::vector objects since, internally, we use
26 direct access to/from @c std::vector objects where needed).
27
28 @par Use case
29 Occasionally, ring buffers (a.k.a., circular buffers) are useful in specific
30 situations because they provide better performance compared to other types of
31 buffers, which either results in frequent allocations and deallocations of
32 memory for transient-but-ordered portions of data or tracking sections of
33 transient-but-ordered portions of data. A ring buffer relies on a single
34 allocation of memory, along with a few variables for tracking top and bottom
35 positions plus a few other internals to ensure smooth and accurate usage.
36
37 Expanding and reducing ring buffer memory automatically (which is disabled by
38 default) is a feature that's particularly useful for server daemons that
39 process small amounts of data most of the time. For example, an SMTP, IMAP4,
40 POP3, MEOW, FTP, HTTP, FINGER, or WHOIS server that is rqeuired to minimize
41 per-connection memory requirements can have a line buffer that begins with 80
42 characters, and be configured to increase to millions on-demand that will
43 automatically shrink back down to the minimal 80 character buffer size, hence
44 most of the time the buffer will only utilize 80 characters, with occasional
45 longer lines that can be rejected efficiently or handled in some other way by
46 the daemon without the complexity of memory management and, most importantly,
47 without having to pre-allocate larger amounts of memory "just in case" a few
48 connections start sending long lines (such as corrupt data, buffer overflow
49 attempts that could be part of fuzzing tests or hacking efforts, etc.).
50
51 @par Conventions
52 Lower-case letters "rb" are regularly used in partial example code to
53 represent an instantiated rring object.
54
55 An ASCIIZ string is a C-string (char* array) that includes a terminating null
56 (0) character at the end.
57
58 @par History
59 - 2024-Nov-25 v1.00 Initial version
60 - 2024-Dec-03 v1.00 Added @ref rring_bam structure integration
61 - 2024-Dec-03 v1.00 Added @ref data_bam and related methods
62 - 2024-Dec-23 v1.00 Added @ref defragment and @ref reverse methods
63 - 2024-Dec-23 v1.00 Renamed @c peek_ methods to @c peek_to_ and @c remove_
64 methods to @c remove_to, so that the intention of these
65 methods is more easily understood
66 - 2024-Dec-23 v1.00 Added internal methods to support ring buffer resizing
67 - 2024-Dec-24 v1.00 Added constructor that includes automatic expansion
68 policy parameters
69 - 2024-Dec-25 v1.00 Implemented automatic buffer size expansion policies
70 - 2024-Dec-27 v1.00 Automatic buffer size expansion, reduction, and block
71 resizing implementations are complete, and tested
72 - 2024-Jan-07 v1.00 Added @ref copy_to_rline method
73 - 2024-Jan-09 v1.00 Added optional @c len parameter to various constructor,
74 assign, and append methods for greater flexibility
75 - 2024-Jan-12 v1.00 Changed std::length_error to std::invalid_argument in
76 response to length being specified as a negative value
77 so this can more easily be handled with seperate code
78 @version 1.00
79 @author Randolf Richardson
80 *///=========================================================================
81 class rring {
82 private:
83 char* __buffer; // Pointer to buffer
84 size_t __size; // Size of buffer // TODO: Add a base_size to ease later calculations with xp
85 size_t __head = 0; // Head (beginning)
86 size_t __tail = 0; // Tail (end)
87 std::mutex __mutex; // Used to add support for multiple threads
88
89 // --------------------------------------------------------------------------
90 // Dynamic expansion variables.
91 // --------------------------------------------------------------------------
92 uint __xp_block_size; // Expansion increment/decrement block size (default == __size)
93 uint __xp_max = 0; // Maximum possible number of expansion blocks
94 uint __xp_play = 2; // Number of available blocks must be present before attempting reduction
95 uint __xp_used = 0; // Number of expansion increments used
96
97 // --------------------------------------------------------------------------
98 // Policy flags.
99 // --------------------------------------------------------------------------
100 bool __wipe_policy = true; // Destructor memory wipe-out policy
101
102 public:
103 /*======================================================================*//**
104 @brief
105 Instantiate an empty ring buffer (without automatic resizing-expansions).
106 @exception std::bad_alloc If @c malloc() fails to allocate memory
107 *///=========================================================================
108 rring(
109 /// Size of buffer
110 const size_t buffer_size) {
111 __buffer = (char*)malloc(buffer_size);
112 if (__buffer == nullptr) throw std::bad_alloc(); // Throw exception if __buffer wasn't allocated by malloc
113 __size = buffer_size;
114 __xp_block_size = buffer_size;
115 }; // -x- constructor rring -x-
116
117 /*======================================================================*//**
118 @brief
119 Instantiate an empty ring buffer (with automatic resizing-expansion policies
120 set so the internal buffer will be re-sized to adapt to unexpected allocation
121 needs behind-the-scenes automatically).
122 @exception std::bad_alloc If @c malloc() fails to allocate memory
123 *///=========================================================================
124 rring(
125 /// Size of buffer
126 const size_t buffer_size,
127 /// Maximum possible number of expansion blocks@n
128 /// 0 = no expansions / effectively disabling expansion (default)
129 const uint xp_max,
130 /// Minimum number of available blocks before a reduction can occur
131 const uint xp_play = 2,
132 /// Size of expansion blocks @n
133 /// 0 = use @c buffer_size (default) @n
134 const uint xp_block_size = 0
135 ) {
136 __buffer = (char*)malloc(buffer_size);
137 if (__buffer == nullptr) throw std::bad_alloc(); // Throw exception if __buffer wasn't allocated by malloc
138 __size = buffer_size;
139
140 // --------------------------------------------------------------------------
141 // Dynamic expansion variables.
142 // --------------------------------------------------------------------------
143 __xp_max = xp_max;
144 __xp_play = xp_play;
145 __xp_block_size = xp_block_size == 0 ? __size : xp_block_size;
146
147 }; // -x- constructor rring -x-
148
149 /*======================================================================*//**
150 @brief
151 Free internal memory resources, after clearing buffer's memory (if the policy
152 indicates that it needs to be cleared).
153 *///=========================================================================
154 ~rring() noexcept {
155 if (__buffer != nullptr) {
156
157 // --------------------------------------------------------------------------
158 // Overwrite buffer's memory with NULL if policy indicates it.
159 // --------------------------------------------------------------------------
160 if (__wipe_policy) while (__size > 0) __buffer[--__size] = '\0';
161
162 // --------------------------------------------------------------------------
163 // Free buffer's memory resource.
164 // --------------------------------------------------------------------------
165 free(__buffer);
166
167 } // -x- if __buffer -x-
168 }; // -x- destructor ~rring -x-
169
170 private:
171 // @returns Quantity of entries added (if positive) or removed (if negative)
172 // if ring buffer memory was increased/decreased respectively@n
173 // 0 = there was no change in ring buffer memory
174 /*======================================================================*//**
175 @brief
176 Calculate the number of available entries in the ring buffer, and expand or
177 contract the size of the buffer memory if needed quantity calls for it.
178 @exception std::overflow_error If the quantity of needed entries exceeds the
179 available free space in the ring buffer
180 @returns If `need > 0`, number of entries available, same as __available() @n
181 If `need <= 0`, number of entries removed (0 == none removed)
182 *///=========================================================================
183 uint __adjust_xp(
184 /// Number of additional entries to attempt to make available@n
185 /// 0 = attempt to reduce ring buffer memory (with no intention to expand it)
186 uint need = 0
187 ) {
188
189 // --------------------------------------------------------------------------
190 // Easy checks first.
191 // --------------------------------------------------------------------------
192 if (__xp_max == 0) return 0; // Expandable memory is effectively disabled, so nothing can change
193
194 // --------------------------------------------------------------------------
195 // Internal variables.
196 // --------------------------------------------------------------------------
197 size_t available = (__head < __tail) ? __tail - __head
198 : __size - (__head - __tail);
199
200 // --------------------------------------------------------------------------
201 // Reduce memory (as per policies) if there's no need to increase memory.
202 // --------------------------------------------------------------------------
203 if (need == 0) {
204
205 // --------------------------------------------------------------------------
206 // Internal variables.
207 // --------------------------------------------------------------------------
208 uint play = __xp_play == 0 ? __xp_block_size : __xp_play * __xp_block_size;
209
210//std::cout << "play=" << play << " available=" << available << " __xp_used=" << __xp_used << " __xp_play=" << __xp_play << std::endl;
211 // --------------------------------------------------------------------------
212 // If there isn't enough "play," then we don't need to reduce buffer memory.
213 //
214 // The reason we don't throw an exception is that reducing ring buffer memory
215 // here is a passive background function, so it's not actually an error when
216 // the conditions for reduction are not satisfied.
217 // --------------------------------------------------------------------------
218 if (available < play /* && __xp_used < __xp_play */) return 0; // No need to reduce buffer memory (because __xp_play is less than what's available)
219 if (available < __xp_used * __xp_block_size) return 0; // No need to reduce buffer memory (because some/all of it is still in use)
220
221 // --------------------------------------------------------------------------
222 // Allocate less memory and move buffer into it using __defragment (which
223 // takes care of re-allocating the new memory).
224 //
225 // Note: Utilized memory is calculated using "__size - available" and then
226 // we add "play" to maintain the minimal amount of available memory
227 // --------------------------------------------------------------------------
228 uint old_size = __size;
229 uint r_blocks = std::min((uint)(available / __xp_block_size), __xp_used);
230 uint new_size = __size - r_blocks * __xp_block_size;
231 __defragment(true, new_size);
232 __xp_used -= r_blocks;
233
234//std::cout << "old_size=" << old_size << " new_size=" << new_size << std::endl;
235 // --------------------------------------------------------------------------
236 // Return quantity of entries removed since the original need specified 0.
237 // --------------------------------------------------------------------------
238 return old_size - new_size;
239
240 } // -x- if need -x-
241
242 // --------------------------------------------------------------------------
243 // If there's already enough memory, then simply return the quantity that's
244 // already available.
245 // --------------------------------------------------------------------------
246 if (need <= available) return available; // Return already-calculated quantity of available entries
247
248 // --------------------------------------------------------------------------
249 // Calculate number of blocks needed, then check that it's possible to
250 // accomodate what's needed or throw an exception if there isn't enough.
251 // --------------------------------------------------------------------------
252 uint xp_blocks_needed = need / __xp_block_size + (need % __xp_block_size != 0); // Total number of blocks needed, mathematically rounded up
253//std::cout << "__size=" << __size << " xp_blocks_needed=" << xp_blocks_needed << " need=" << need << " __xp_max=" << __xp_max << " __xp_used=" << __xp_used << std::endl;
254 if (xp_blocks_needed > __xp_max - __xp_used) throw std::overflow_error("Amount of needed data (quantity=" + std::to_string(need) + ") exceeds expansion limits");
255
256 // --------------------------------------------------------------------------
257 // Allocate new memory and move buffer into it using __defragment (which
258 // takes care of re-allocating the new memory).
259 // --------------------------------------------------------------------------
260 __defragment(true, __size + __xp_block_size * xp_blocks_needed);
261
262 // --------------------------------------------------------------------------
263 // If __defragment()'s memory allocation was successful, then it means that
264 // the resizing was successful, and so now it's okay to update any important
265 // variables.
266 // --------------------------------------------------------------------------
267 __xp_used = xp_blocks_needed; // Not += (as of 2024-Dec-27)
268//std::cout << "__size=" << __size << " xp_blocks_needed=" << xp_blocks_needed << " need=" << need << " __xp_max=" << __xp_max << " __xp_used=" << __xp_used << std::endl;
269
270 return __available(); // Return re-calculated quantity of available entries because __defragment probably changed them
271
272 } // -x- int __adjust_xp -x-
273
274 /*======================================================================*//**
275 @brief
276 Calculate the number of available entries in the ring buffer.
277 @returns Number of entries available
278 @see get_available
279 *///=========================================================================
280 size_t __available() noexcept {
281 return (__head < __tail) ? __tail - __head
282 : __size - (__head - __tail);
283 } // -x- size_t __available -x-
284
285 /*======================================================================*//**
286 @brief
287 Calculate the number of utilized entries in the ring buffer.
288 @returns Number of entries utilized
289 @see get_utilized
290 *///=========================================================================
291 size_t __utilized() noexcept {
292 return (__head >= __tail) ? __head - __tail
293 : __size - (__tail - __head);
294 } // -x- size_t __utilized -x-
295
296 public:
297 /*======================================================================*//**
298 @brief
299 Add one character to the unused portion of the internal ring buffer.
300 @exception std::overflow_error If the amount of data exceeds the available
301 free space in the ring buffer
302 @returns The same rring object so as to facilitate stacking
303 @see peek
304 @see remove
305 *///=========================================================================
306 rring* append(
307 /// Data to add
308 const char data) {
309
310 // --------------------------------------------------------------------------
311 // Add data to ring buffer. If no data is needed, then skip all the mutex
312 // locking overhead, etc., and return the empty string.
313 // --------------------------------------------------------------------------
314 { std::lock_guard<std::mutex> guard(__mutex);
315 if (__adjust_xp(1) == 0) throw std::overflow_error("Ring buffer is full");
316 if (__head >= __size) __head = 0; // Wrap-around
317 __buffer[__head] = data; // Copy entry
318 __head++; // Advance to next entry
319 } // -x- mutex guard -x-
320
321 return this;
322 } // -x- rring* append -x-
323
324 /*======================================================================*//**
325 @brief
326 Add any number of characters that fit within the unused portion of the
327 internal ring buffer.
328 @exception std::invalid_argument If data length is -2 or below
329 @exception std::overflow_error If the amount of data exceeds the available
330 free space in the ring buffer
331 @returns The same rring object so as to facilitate stacking
332 @see c_str
333 @see peek_to_array
334 @see remove_to_array
335 *///=========================================================================
336 rring* append(
337 /// Data to add
338 const char* data,
339 /// Length of data (-1 = treat @c data as an ASCIIZ string)
340 int len = -1) {
341
342 // --------------------------------------------------------------------------
343 // Syntax checks.
344 // --------------------------------------------------------------------------
345 if (len == -1) len = std::strlen(data);
346 else if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
347
348 // --------------------------------------------------------------------------
349 // Add data to ring buffer. If no data is needed, then skip all the mutex
350 // locking overhead, etc., and return the empty string.
351 // --------------------------------------------------------------------------
352 if (len > 0) {
353 std::lock_guard<std::mutex> guard(__mutex);
354 if (__adjust_xp(len) < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds available free space in ring buffer");
355 for (int i = 0; i < len; i++) {
356 if (__head >= __size) __head = 0; // Wrap-around
357 __buffer[__head] = data[i]; // Copy entry
358 __head++; // Advance to next entry
359 } // -x- for i -x-
360 } // -x- if len -x-
361
362 return this;
363 } // -x- rring* append -x-
364
365 /*======================================================================*//**
366 @brief
367 Add any number of characters that fit within the unused portion of the
368 internal ring buffer.
369 @exception std::invalid_argument If data length is -2 or below
370 @exception std::length_error If data length is too long
371 @exception std::overflow_error If the amount of data exceeds the available
372 free space in the ring buffer
373 @returns The same rring object so as to facilitate stacking
374 @see c_str
375 @see peek_to_string
376 @see remove_to_string
377 *///=========================================================================
378 rring* append(
379 /// Data to add
380 const std::string data,
381 /// Length of data (-1 = rely on data.length())
382 int len = -1) {
383
384 // --------------------------------------------------------------------------
385 // Syntax checks.
386 // --------------------------------------------------------------------------
387 if (len == -1 ) len = data.length();
388 else if (len < -1 ) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
389 else if (len > data.length()) throw std::length_error("Unrealistic data length of " + std::to_string(len));
390
391 // --------------------------------------------------------------------------
392 // Add data to ring buffer. If no data is needed, then skip all the mutex
393 // locking overhead, etc., and return the empty string.
394 // --------------------------------------------------------------------------
395 if (len > 0) {
396 std::lock_guard<std::mutex> guard(__mutex);
397 if (__adjust_xp(len) < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds available free space in ring buffer");
398 for (int i = 0; i < len; i++) {
399 if (__head >= __size) __head = 0; // Wrap-around
400 __buffer[__head] = data[i]; // Copy entry
401 __head++; // Advance to next entry
402 } // -x- for i -x-
403 } // -x- if len -x-
404
405 return this;
406 } // -x- rring* append -x-
407
408 /*======================================================================*//**
409 @brief
410 Add any number of characters that fit within the unused portion of the
411 internal ring buffer.
412 @exception std::invalid_argument If data length is -2 or below
413 @exception std::overflow_error If the amount of data exceeds the available
414 free space in the ring buffer
415 @returns The same rring object so as to facilitate stacking
416 @see c_str
417 @see peek_to_vector
418 @see remove_to_vector
419 *///=========================================================================
420 rring* append(
421 /// Data to add
422 const std::vector<char> data,
423 /// Length of data (-1 = treat @c data as an ASCIIZ string)
424 int len = -1) {
425
426 // --------------------------------------------------------------------------
427 // Syntax checks.
428 // --------------------------------------------------------------------------
429 if (len == -1) len = data.size();
430 else if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
431
432 // --------------------------------------------------------------------------
433 // Add data to ring buffer. If no data is needed, then skip all the mutex
434 // locking overhead, etc., and return the empty string.
435 // --------------------------------------------------------------------------
436 if (len > 0) {
437 std::lock_guard<std::mutex> guard(__mutex);
438 if (__adjust_xp(len) < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds available free space in ring buffer");
439 for (int i = 0; i < len; i++) {
440 if (__head >= __size) __head = 0; // Wrap-around
441 __buffer[__head] = data[i]; // Copy entry
442 __head++; // Advance to next entry
443 } // -x- for i -x-
444 } // -x- if len -x-
445
446 return this;
447 } // -x- rring* append -x-
448
449 /*======================================================================*//**
450 @brief
451 Add any number of characters that fit within the unused portion of the
452 internal ring buffer.
453 @exception std::invalid_argument If data length is -2 or below
454 @exception std::overflow_error If the amount of data exceeds the available
455 free space in the ring buffer
456 @returns The same rring object so as to facilitate stacking
457 @see c_str
458 @see peek_to_vector
459 @see remove_to_vector
460 *///=========================================================================
461 rring* append(
462 /// Data to add
463 const std::vector<unsigned char> data,
464 /// Length of data (-1 = treat @c data as an ASCIIZ string)
465 int len = -1) {
466
467 // --------------------------------------------------------------------------
468 // Syntax checks.
469 // --------------------------------------------------------------------------
470 if (len == -1) len = data.size();
471 else if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
472
473 // --------------------------------------------------------------------------
474 // Add data to ring buffer. If no data is needed, then skip all the mutex
475 // locking overhead, etc., and return the empty string.
476 // --------------------------------------------------------------------------
477 if (len > 0) {
478 std::lock_guard<std::mutex> guard(__mutex);
479 if (__adjust_xp(len) < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds available free space in ring buffer");
480 for (int i = 0; i < len; i++) {
481 if (__head >= __size) __head = 0; // Wrap-around
482 __buffer[__head] = data[i]; // Copy entry
483 __head++; // Advance to next entry
484 } // -x- for i -x-
485 } // -x- if len -x-
486
487 return this;
488 } // -x- rring* append -x-
489
490 /*======================================================================*//**
491 @brief
492 Array-style access to the utilized portion of the ring buffer's contents,
493 which yields the same result as that of the @ref at() method.
494
495 The first element is at index 0.
496 @throws std::out_of_range if the index is out-of-range
497 @returns char
498 @see operator[]
499 *///=========================================================================
500 char at(
501 /// Index of character to access (0 = first element; negative index values
502 /// are calculated in reverse, starting with -1 as the final position)
503 int index) {
504 std::lock_guard<std::mutex> guard(__mutex);
505
506 // --------------------------------------------------------------------------
507 // Internal variables.
508 // --------------------------------------------------------------------------
509 int MAX = __utilized();
510
511 // --------------------------------------------------------------------------
512 // Syntax checks.
513 // --------------------------------------------------------------------------
514 if (index >= MAX || 0 - index > MAX) throw std::out_of_range("index (position=" + std::to_string(index) + ") falls outside of the utilized portion (length=" + std::to_string(MAX) + ") of the buffer");
515
516 // --------------------------------------------------------------------------
517 // Calculate character's position.
518 // --------------------------------------------------------------------------
519 index = index >= 0 ? __tail + index : __head + index;
520 if (index >= __size) index -= __size;
521
522 return __buffer[index];
523 } // -x- char at -x-
524
525 /*======================================================================*//**
526 @brief
527 Return the contents of the ring buffer as an ASCIIZ string.
528 @post
529 The string returned will need to be `free()`'d after it's no longer needed.
530
531 @code{.cpp}
532 #include <iostream> // std::cout, std::cerr, std::endl, etc.
533 #include <randolf/rring>
534
535 int main(int argc, char *argv[]) {
536 randolf::rring rb(1024);
537 rb.append("This is an example.");
538 char* c = rb.c_str();
539 std::cout << "\"" << c << "\"" << std::endl;
540 free(c);
541 return EXIT_SUCCESS;
542 } // -x- int main -x-
543 @endcode
544
545 @warning
546 When using the @ref data parameter in a multi-threaded scenario, the amount
547 of the memory needed should be set to the maximum size of this ring buffer to
548 prevent unintended under-allocation resulting from a simultaneous increase
549 in utilized ring buffer memory (caused by @ref set_size).
550 @exception std::bad_alloc If @c malloc() fails to allocate memory
551 @returns ASCIIZ string
552 @see append(const char*, int)
553 @see append(const std::string, int)
554 @see peek_to_array
555 @see peek_to_string
556 *///=========================================================================
557 char* c_str(
558 /// Where to store the ASCIIZ string (including terminating zero, so it is
559 /// imperative that `utilized() + 1` bytes be pre-allocated)@n
560 /// @c nullptr = allocate the needed memory using @c malloc()
561 char* data = nullptr) noexcept {
562
563 // --------------------------------------------------------------------------
564 // Internal variables.
565 // --------------------------------------------------------------------------
566 size_t len;
567
568 // --------------------------------------------------------------------------
569 // Generate ASCIIZ string.
570 // --------------------------------------------------------------------------
571 { std::lock_guard<std::mutex> guard(__mutex);
572 len = __utilized();
573 if (data == nullptr) {
574 data = (char*)malloc(len + 1);
575 if (data == nullptr) throw std::bad_alloc(); // Throw exception if data wasn't allocated by malloc
576 } // -x- if !data -x-
577 size_t t = __tail;
578 for (int i = 0; i < len; i++) {
579 if (t >= __size) t = 0; // Wrap-around
580 data[i] = __buffer[t]; // Copy entry
581 t++; // Advance to next entry
582 } // -x- for i -x-
583 } // -x- mutex guard -x-
584 data[len] = '\0'; // Not "len - 1" because we already allocated "len + 1"
585
586 return data;
587 } // -x- char* c_str -x-
588
589 /*======================================================================*//**
590 @brief
591 Copy a line of text into a randolf::rline object. If an EoL sequence is not
592 detected, the data will still fill the randolf::rline object.
593 @note
594 If providing a pre-existing randolf::rline object, data will be appended to
595 any existing data that's already present.
596 @throws std::invalid_argument If @c len is less than -1
597 @returns Data from ring buffer
598 @see append(const std::string, int)
599 @see c_str
600 @see peek
601 @see peek_to_array
602 @see peek_to_string
603 @see peek_to_vector
604 *///=========================================================================
605 randolf::rline* copy_to_rline(
606 /// Maximum quantity of data to duplicate@n
607 /// -1 = all data
608 int len = -1,
609 /// Pointer to an already-instantiated @ref randolf::rline object to use@n
610 /// nullptr = instantiate a new @ref randolf::rline object (this will need to
611 /// be `delete`'d when it's no longer needed)
612 randolf::rline* data = nullptr) {
613
614 // --------------------------------------------------------------------------
615 // Syntax checks.
616 // --------------------------------------------------------------------------
617 if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
618 if (data == nullptr) data = new randolf::rline();
619
620 // --------------------------------------------------------------------------
621 // Duplicate data from ring buffer, quickly (in one or two moves).
622 // --------------------------------------------------------------------------
623 { std::lock_guard<std::mutex> guard(__mutex);
624 switch (len) {
625 case -1: // Copy everything
626 if (__head >= __tail) { // Not wrapped
627 data->append(__buffer + __tail, __utilized());
628 } else {
629 int n = __size - __tail;
630 data->append(__buffer + __tail, std::min(len, n));
631 if (n < len) data->append(__buffer, len - n);
632 } // -x- if not_wrapped -x-
633 case 0: // Copy nothing at all
634 break;
635 default: // Copy specified quantity
636 int utilized = __utilized();
637 if (utilized < len) len = utilized; // throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds utilized space in ring buffer");
638 if (__head >= __tail) { // Not wrapped
639 data->append(__buffer + __tail, std::min(len, utilized));
640 } else {
641 int n = __size - __tail;
642 data->append(__buffer + __tail, std::min(len, n));
643 if (n < len) data->append(__buffer, len - n);
644 } // -x- if not_wrapped -x-
645 } // -x- switch len -x-
646 } // -x- mutex guard -x-
647
648 return data;
649 } // -x- randolf::rline* copy_to_rline -x-
650
651 /*======================================================================*//**
652 @brief
653 Returns a pointer to the underlying ring buffer's array. If the "defragment"
654 flag is cleared then the beginning of the array may not be the same as where
655 the data actually begins (and ends) within the buffer (see the @c warning
656 hereunder).
657
658 @warning
659 This is the live data, which should not be accessed or modified directly; use
660 the @ref data_bam() and @ref data_bam_adjust() methods instead when there is
661 a need to access or modify the backing array directly.
662 @exception std::bad_alloc If @c malloc() fails to allocate memory (this can
663 only occur when the "defragment" flag is set)
664 @returns Pointer to buffer (not null terminated)
665 @see data_bam
666 @see data_bam_adjust
667 @see defragment
668 @see get_available
669 @see get_utilized
670 @see set_size
671 @qualifier ADVANCED
672 *///=========================================================================
673 char* data(
674 /// Whether to defragment the data first@n
675 /// @c TRUE = fuly defragment so that data begins at position 0 (default) @n
676 /// @c FALSE = don't defragment the data (this is an advanced feature that
677 /// will require knowing how to use the @ref data_bam method, and
678 /// also the @ref data_bam method if adding or removing data)
679 bool defragment_flag = true) noexcept {
680 std::lock_guard<std::mutex> guard(__mutex);
681 if (defragment_flag) defragment(true);
682 return __buffer;
683 } // -x- char* data -x-
684
685 /*======================================================================*//**
686 @brief
687 Returns the lengths and addresses of available memory. There may be only one
688 block of memory, or two, depending on whether the available memory straddles
689 the boundary of the ring buffer.
690
691 This is the nearest equivalent of the @ref data() methods provided in classes
692 such as @c std::string and @c std::vector, but with the nuance of supporting
693 the underpinnings of this ring buffer wherein the utilized portions may be
694 stored at both the end and beginning of the buffer memory.
695 @note
696 The intention of this method is to provide an opportunity to directly update
697 the contents of the ring buffer's data section in an efficient manner (e.g.,
698 such as by reading file or socket data directly into memory without having to
699 perform separate data copying operations, etc.).
700 @warning
701 Upon updating a portion of the available-data block, the @c head will also
702 need to be updated accordingly, for which the @ref data_bam_adjust method is
703 provided.
704 @exception std::bad_alloc If @c malloc() fails to allocate memory
705 @returns Pointer to @ref rring_bam structure
706 @see data
707 @see data_bam_adjust
708 @see empty
709 @see get_available
710 @see get_head
711 @see get_tail
712 @see get_utilized
713 @see rring_bam
714 @qualifier ADVANCED
715 *///=========================================================================
716 rring_bam* data_bam(
717 /// Pointer to @ref rring_bam structure (that's already been allocated)@n
718 /// @c nullptr (default) = allocate memory for this structure with @c malloc
719 /// (which will need to be `free`'d when it's no longer needed)
720 rring_bam* bam = nullptr) noexcept {
721 std::lock_guard<std::mutex> guard(__mutex);
722
723 // --------------------------------------------------------------------------
724 // Internal variables.
725 // --------------------------------------------------------------------------
726 if (bam == nullptr) {
727 bam = (rring_bam*)malloc(__size);
728 if (bam == nullptr) throw std::bad_alloc(); // Throw exception if bam wasn't allocated by malloc
729 } // -x- if !bam -x-
730
731 // --------------------------------------------------------------------------
732 // Configure utilized data block pointer(s) and length(s).
733 // --------------------------------------------------------------------------
734 bam->block1 = __buffer + __tail;
735 if (__head >= __tail) { // Not wrapped
736 bam->block1_size = __head - __tail;
737 bam->block2 = nullptr;
738 bam->block2_size = 0;
739 } else { // Wrapped
740 bam->block1_size = __size - __tail;
741 bam->block2 = __size - __tail == 0 ? nullptr : __buffer;
742 bam->block2_size = __head;
743 } // -x- if __head -x-
744
745 // --------------------------------------------------------------------------
746 // Configure available data block pointer(s) and length(s).
747 // --------------------------------------------------------------------------
748 bam->avail1 = __buffer + __head;
749 if (__head < __tail) { // Not wrapped
750 bam->avail1_size = __tail - __head;
751 bam->avail2 = nullptr;
752 bam->avail2_size = 0;
753 } else { // Wrapped
754 bam->avail1_size = __size - __head;
755 bam->avail2 = __tail == 0 ? nullptr : __buffer;
756 bam->avail2_size = __tail;
757 } // -x- if __head -x-
758
759 return bam;
760 } // -x- rring_bam* data_bam -x-
761
762 /*======================================================================*//**
763 @brief
764 Arbitrarily change the head and tail positions in the ring buffer. This is
765 intended for use in conjunction with appending data directly to the ring
766 buffer after the @ref data_bam method.
767 @exception std::overflow_error If either adjustment would result in causing
768 an overflow
769 @returns The same rring object so as to facilitate stacking
770 @see data
771 @see data_bam
772 @see set_head
773 @see set_tail
774 @qualifier ADVANCED
775 *///=========================================================================
776 rring* data_bam_adjust(
777 /// New internal head position
778 size_t head,
779 /// New internal tail position
780 size_t tail = 0) {
781
782 // --------------------------------------------------------------------------
783 // Set new head and tail positions.
784 // --------------------------------------------------------------------------
785 { std::lock_guard<std::mutex> guard(__mutex);
786 __head = head;
787 __tail = tail;
788 } // -x- mutex guard -x-
789
790 return this;
791 } // -x- rring* data_bam_adjust -x-
792
793 private:
794 /*======================================================================*//**
795 @brief
796 Internal method used primarily by the defragment() and resize() methods.
797
798 A full defragmentation always results in the tail changing to position 0.
799
800 TO DO: Return to code in _archive/2024-Dec-22/ which is nearly finished, and
801 attempt to get the algorithm working for the last few bytes so that
802 the memory allocation approach we use now won't be needed (and also
803 therefore eliminates the wipe_policy implementation step).
804 @exception std::length_error If the new @c buffer_size is less than the total
805 quantity of utilized entries (which would result
806 in the truncation of data)
807 @exception std::bad_alloc If @c malloc() fails to allocate memory
808 @see defragment
809 @see set_size
810 *///=========================================================================
811 void __defragment(
812 /// Whether to defrag when the utilized block isn't straddling the internal
813 /// memory buffer boundary@n
814 /// TRUE = always fully defragment (default) @n
815 /// FALSE = only defragment if utilized block straddles memory boundary
816 const bool full,
817 /// Total size of new memory buffer to be allocated (used in resizing so that
818 /// we only allocate the new buffer once {here} rather than twice {here, and
819 /// in any of the methods that @ref resize the internal buffer})
820 const size_t buffer_size) {
821
822 // --------------------------------------------------------------------------
823 // Internal variables.
824 // --------------------------------------------------------------------------
825 const size_t utilized = __utilized();
826
827 // --------------------------------------------------------------------------
828 // Perform quick checks if size isn't increasing.
829 // --------------------------------------------------------------------------
830 if (buffer_size == __size) {
831
832 // --------------------------------------------------------------------------
833 // Already fully optimized, so don't even start.
834 // --------------------------------------------------------------------------
835 if (__tail == 0) return;
836
837 // --------------------------------------------------------------------------
838 // If not wrapped around ring buffer (and not resizing), optimization is a
839 // fast-and-easy matter of shifting all the elements without re-allocating
840 // the ring buffer memory.
841 // --------------------------------------------------------------------------
842 if (__tail <= __head) {
843 if (!full) return; // No work to be done
844 for (size_t i = 0; i < utilized; i++) // Move data
845 __buffer[i] = __buffer[__tail++];
846 __head = utilized;
847 __tail = 0;
848 return;
849 } // -x- if __tail -x-
850
851 // --------------------------------------------------------------------------
852 // Size reduction sanity check.
853 // --------------------------------------------------------------------------
854 } else if (buffer_size < utilized) {
855
856 // --------------------------------------------------------------------------
857 // Prevent truncation by throwing a length_error exception.
858 // --------------------------------------------------------------------------
859 throw std::length_error("Unrealistic buffer size reduction to " + std::to_string(buffer_size));
860
861 } // -x- if buffer_size -x-
862
863 // --------------------------------------------------------------------------
864 // Allocate new internal buffer.
865 // --------------------------------------------------------------------------
866 void* data = (char*)malloc(buffer_size); // Allocate new buffer to the same as as the current one
867 if (data == nullptr) throw std::bad_alloc(); // Throw exception if data wasn't allocated by malloc
868
869 // --------------------------------------------------------------------------
870 // Copy data to new buffer that is the same size as the current one.
871 // --------------------------------------------------------------------------
872 for (int i = 0; i < utilized; i++) {
873 if (__tail >= __size) __tail = 0; // Wrap-around
874 ((char*)data)[i] = __buffer[__tail]; // Copy entry
875 __tail++; // Advance to next entry
876 } // -x- for i -x-
877
878 // --------------------------------------------------------------------------
879 // De-allocate old internal buffer, and wipe old data before de-allocation if
880 // the policy warrants it.
881 // --------------------------------------------------------------------------
882 if (__wipe_policy)
883 for (int i = 0; i < __size; i++)
884 __buffer[i] = 0;
885 free(__buffer);
886
887 // --------------------------------------------------------------------------
888 // Set new buffer pointer and internal variables.
889 // --------------------------------------------------------------------------
890 __buffer = (char*)data; // Update pointer to internal buffer
891 __head = utilized;
892 __tail = 0;
893 __size = buffer_size;
894
895 return;
896 } // -x- void __defragment -x-
897
898 public:
899 /*======================================================================*//**
900 @brief
901 Reorganizes the utilized memory in the ring buffer memory by moving the tail
902 to the beginning. Defragmentation is completed in the most efficient manner,
903 depending on what needs to be done. If data wraps around the buffer memory,
904 then new memory will be allocated and data will be moved to it (old memory
905 will also be cleared if the wipe policy is in effect).
906
907 A full defragmentation always results in the tail changing to position 0.
908 @exception std::length_error If the new @c buffer_size is less than the total
909 quantity of utilized entries (which would result
910 in the truncation of data)
911 @exception std::bad_alloc If @c malloc() fails to allocate memory
912 @returns The same rring object so as to facilitate stacking
913 @see data
914 @see data_bam
915 @see get_ascii_map
916 @see get_wipe_policy
917 @see set_size
918 *///=========================================================================
919 rring* defragment(
920 /// Whether to defrag when the utilized block isn't straddling the internal
921 /// memory buffer boundary@n
922 /// TRUE = always fully defragment (default) @n
923 /// FALSE = only defragment if utilized block straddles memory boundary (this
924 /// favours reducing processor overhead if all that's wanted is to
925 /// have one block of memory to contend with in the BAM, hence it's
926 /// also what the @ref set_size() method uses for better performance
927 /// overall because resizing most likely occurs when the need for CPU
928 /// cycles are more critical, so we're doing our part by reducing our
929 /// CPU load)
930 const bool full = true) {
931 { std::lock_guard<std::mutex> guard(__mutex);
932 __defragment(full, __size);
933 } // -x- mutex guard -x-
934 return this;
935 } // -x- rring* defragment -x-
936
937 /*======================================================================*//**
938 @brief
939 Discard data from the ring buffer.
940 @throws std::invalid_argument If @c len is less than -1
941 @returns Number of entries that were discarded
942 @see remove
943 *///=========================================================================
944 size_t discard(
945 /// Number of bytes to discard@n
946 /// -1 = all utilized data that remains will be discarded (default)
947 int len = -1) noexcept {
948 if (len == 0) return 0; // Do nothing
949 size_t discarded;
950 { std::lock_guard<std::mutex> guard(__mutex);
951 if (len == -1) len = __size;
952 else if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
953 discarded = __utilized();
954
955 // --------------------------------------------------------------------------
956 // Discard everything because len is greater than or equal to the amount of
957 // data in the ring buffer.
958 // --------------------------------------------------------------------------
959 if (len >= discarded) { // Discard everything if len is too large
960 __head = 0;
961 __tail = 0;
962 __adjust_xp(); // Ring buffer memory allocation maintenance
963 return len; // Return quantity that was actually discarded
964 } // -x- if len -x-
965
966 // --------------------------------------------------------------------------
967 // Adjust __tail according to amount of data to discard. Calculating this is
968 // enormously faster than looping through the buffer.
969 // --------------------------------------------------------------------------
970 if (__head >= __tail || __size - __tail < len) { // Not wrapping, so adjustment is easy
971 __tail += len;
972 } else { // Wrapping, so change tail to discard size minus what's discarded from the end
973 __tail = len - (__size - __tail);
974 } // -x- if __head -x-
975
976 // --------------------------------------------------------------------------
977 // Ring buffer memory allocation maintenance.
978 // --------------------------------------------------------------------------
979 __adjust_xp();
980
981 } // -x- mutex guard -x-
982
983 return discarded - len; // Return quantity that was actually discarded
984 } // -x- int discard -x-
985
986 /*======================================================================*//**
987 @brief
988 Indicates whether the ring buffer is empty.
989 @returns TRUE = empty@n
990 FALSE = not empty
991 @see data_bam
992 @see get_available
993 @see get_utilized
994 @see get_size
995 *///=========================================================================
996 bool empty() noexcept {
997 std::lock_guard<std::mutex> guard(__mutex);
998 return __head == __tail;
999 } // -x- bool empty -x-
1000
1001 /*======================================================================*//**
1002 @brief
1003 Obtains a one-line ASCII art depiction of a map of the ring buffer's memory
1004 usage, wherein the utilized and available portions are represented by the `|`
1005 (pipe) and `.` (period) characters, respectively. These characters are
1006 configurable and may each contain more than one character, which is
1007 particularly useful for text-mode console output in a typical Linux shell in
1008 which @c ANSI or @c AVATAR codes may be used to present these characters or
1009 sequences in different colours, although there may be other uses for this
1010 too.
1011 @exception std::invalid_argument If length is -2 or below
1012 @returns One-line ASCII depiction of ring buffer's memory map
1013 @see get_available
1014 @see get_head
1015 @see get_tail
1016 @see get_utilized
1017 *///=========================================================================
1018 std::string get_ascii_map(
1019 /// Desired size of ASCII map@n
1020 /// -1 = Use size of entire ring buffer
1021 int len = 65,
1022 /// Character(s) representing utilized entries
1023 const std::string u = "|",
1024 /// Character(s) representing available entries
1025 const std::string a = ".") noexcept {
1026
1027 // --------------------------------------------------------------------------
1028 // Internal variables, which will be copied .
1029 // --------------------------------------------------------------------------
1030 double head;
1031 double tail;
1032 double size;
1033 { std::lock_guard<std::mutex> guard(__mutex);
1034 head = __head;
1035 tail = __tail;
1036 size = __size;
1037 } // -x- mutex guard -x-
1038 std::string map;
1039
1040 // --------------------------------------------------------------------------
1041 // Syntax checks.
1042 // --------------------------------------------------------------------------
1043 if (len == -1) len = size;
1044 if (len == 0) return map;
1045 if (len <= -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
1046
1047 // --------------------------------------------------------------------------
1048 // Generate output.
1049 // --------------------------------------------------------------------------
1050 if (len >= size) {
1051 head = head * (len / size);
1052 tail = tail * (len / size);
1053 } else {
1054 head = head / (size / len);
1055 tail = tail / (size / len);
1056 } // -x- if len -x-
1057 map.reserve(len); // Pre-allocate the exact amount of underlying memory needed
1058 if (head >= tail) for (int i = 0; i < len; i++) map.append(i < tail || i >= head ? a : u); // "a" or "u" could be longer than one character
1059 else for (int i = 0; i < len; i++) map.append(i < head || i >= tail ? u : a); // "a" or "u" could be longer than one character
1060
1061 return map;
1062 } // -x- std::string get_ascii_map -x-
1063
1064 /*======================================================================*//**
1065 @brief
1066 Calculate the number of available entries in the ring buffer.
1067 @returns Number of entries available
1068 @see data_bam
1069 @see get_utilized
1070 @see get_xp_blocks_available
1071 *///=========================================================================
1072 size_t get_available() noexcept {
1073 std::lock_guard<std::mutex> guard(__mutex);
1074 return __available();
1075 } // -x- size_t get_available -x-
1076
1077 /*======================================================================*//**
1078 @brief
1079 Obtains the base size of the ring buffer, regardless of how much is utilized
1080 or available, and without any of the expansion policy limits.
1081 @returns Maximum potential size of ring buffer
1082 @see empty
1083 @see get_available
1084 @see get_max_size
1085 @see get_size
1086 @see get_utilized
1087 @see set_size
1088 @qualifier ADVANCED
1089 *///=========================================================================
1090 size_t get_base_size() noexcept {
1091 std::lock_guard<std::mutex> guard(__mutex);
1092 return __size - (__xp_used * __xp_block_size);
1093 } // -x- size_t get_base_size -x-
1094
1095 /*======================================================================*//**
1096 @brief
1097 Obtains the position of the head in the ring buffer.
1098 @returns Position of head in ring buffer
1099 @see data_bam
1100 @see get_available
1101 @see get_tail
1102 @see get_utilized
1103 @see set_head
1104 @see set_tail
1105 @qualifier ADVANCED
1106 *///=========================================================================
1107 size_t get_head() noexcept {
1108 std::lock_guard<std::mutex> guard(__mutex);
1109 return __head;
1110 } // -x- size_t get_head -x-
1111
1112 /*======================================================================*//**
1113 @brief
1114 Obtains the potential total size of the ring buffer, including both its
1115 utilized and available portions, including the maximum limit of what the
1116 expansion policies can extend the internal ring buffer's size to.
1117 @returns Maximum potential size of ring buffer
1118 @see empty
1119 @see get_available
1120 @see get_base_size
1121 @see get_max_size
1122 @see get_size
1123 @see get_utilized
1124 @see set_size
1125 @qualifier ADVANCED
1126 *///=========================================================================
1127 size_t get_max_size() noexcept {
1128 std::lock_guard<std::mutex> guard(__mutex);
1129 return __size + ((__xp_max - __xp_used) * __xp_block_size);
1130 } // -x- size_t get_max_size -x-
1131
1132 /*======================================================================*//**
1133 @brief
1134 Obtains the total size of the current ring buffer, including both its
1135 utilized and available portions.
1136 @returns Total current size of ring buffer
1137 @see empty
1138 @see get_available
1139 @see get_base_size
1140 @see get_max_size
1141 @see get_utilized
1142 @see set_size
1143 @qualifier ADVANCED
1144 *///=========================================================================
1145 size_t get_size() noexcept {
1146 std::lock_guard<std::mutex> guard(__mutex);
1147 return __size;
1148 } // -x- size_t get_size -x-
1149
1150 /*======================================================================*//**
1151 @brief
1152 Obtains the position of the tail in the ring buffer.
1153 @returns Position of tail in ring buffer
1154 @see data_bam
1155 @see get_available
1156 @see get_head
1157 @see get_utilized
1158 @see set_head
1159 @see set_tail
1160 @qualifier ADVANCED
1161 *///=========================================================================
1162 size_t get_tail() noexcept {
1163 std::lock_guard<std::mutex> guard(__mutex);
1164 return __tail;
1165 } // -x- size_t get_tail -x-
1166
1167 /*======================================================================*//**
1168 @brief
1169 Calculate the number of utilized entries in the ring buffer.
1170 @returns Number of entries utilized
1171 @see data_bam
1172 @see get_available
1173 @see get_xp_blocks_utilized
1174 *///=========================================================================
1175 size_t get_utilized() noexcept {
1176 std::lock_guard<std::mutex> guard(__mutex);
1177 return __utilized();
1178 } // -x- size_t get_utilized -x-
1179
1180 /*======================================================================*//**
1181 @brief
1182 Indicate whether internal buffer's memory will be cleared by the destructor
1183 before de-allocating it (default is @c true upon instantiation).
1184
1185 @note
1186 This is an important security feature for when the ring buffer may have been
1187 storing private data because it prevents the leaking of private data to other
1188 process that coincidentally gain access to the same memory area (e.g., by way
1189 of future calls to `malloc()`).
1190 @returns Post-wipe policy (TRUE = clear / FALSE = don't clear)
1191 @see set_wipe_policy
1192 @qualifier ADVANCED
1193 *///=========================================================================
1194 bool get_wipe_policy() noexcept {
1195 std::lock_guard<std::mutex> guard(__mutex);
1196 return __wipe_policy;
1197 } // -x- bool get_wipe_policy -x-
1198
1199 /*======================================================================*//**
1200 @brief
1201 Obtain block size in expansion policy.
1202 @returns Expansion policy's block size
1203 @see get_xp_max
1204 @see get_xp_play
1205 @see set_xp_block_size
1206 @qualifier ADVANCED
1207 *///=========================================================================
1208 size_t get_xp_block_size() noexcept {
1209 std::lock_guard<std::mutex> guard(__mutex);
1210 return __xp_block_size;
1211 } // -x- size_t get_xp_block_size -x-
1212
1213 /*======================================================================*//**
1214 @brief
1215 Obtain quantity of blocks available for use in buffer ring memory expansions.
1216 @returns Quantity of expansion blocks available
1217 @see get_available
1218 @see get_xp_block_size
1219 @see get_xp_blocks_utilized
1220 @see get_xp_max
1221 @see get_xp_play
1222 @qualifier ADVANCED
1223 *///=========================================================================
1224 size_t get_xp_blocks_available() noexcept {
1225 std::lock_guard<std::mutex> guard(__mutex);
1226 return __xp_max - __xp_used;
1227 } // -x- size_t get_xp_blocks_available -x-
1228
1229 /*======================================================================*//**
1230 @brief
1231 Obtain quantity of blocks utilized utilized for buffer ring memory expansion.
1232 @returns Quantity of expansion blocks utilized
1233 @see get_utilized
1234 @see get_xp_block_size
1235 @see get_xp_blocks_available
1236 @see get_xp_max
1237 @see get_xp_play
1238 @qualifier ADVANCED
1239 *///=========================================================================
1240 size_t get_xp_blocks_utilized() noexcept {
1241 std::lock_guard<std::mutex> guard(__mutex);
1242 return __xp_used;
1243 } // -x- size_t get_xp_blocks_utilized -x-
1244
1245 /*======================================================================*//**
1246 @brief
1247 Obtain maximum block quantity in expansion policy.
1248 @returns Expansion policy's maximum block quantity
1249 @see get_xp_block_size
1250 @see get_xp_blocks_available
1251 @see get_xp_blocks_utilized
1252 @see get_xp_play
1253 @see set_xp_max
1254 @qualifier ADVANCED
1255 *///=========================================================================
1256 size_t get_xp_max() noexcept {
1257 std::lock_guard<std::mutex> guard(__mutex);
1258 return __xp_max;
1259 } // -x- size_t get_xp_max -x-
1260
1261 /*======================================================================*//**
1262 @brief
1263 Obtain minimum quantity of blocks available before automatic expansion block
1264 reduction policy.
1265 @returns Expansion policy's play setting
1266 @see get_xp_block_size
1267 @see get_xp_blocks_available
1268 @see get_xp_blocks_utilized
1269 @see get_xp_max
1270 @see set_xp_play
1271 @qualifier ADVANCED
1272 *///=========================================================================
1273 size_t get_xp_play() noexcept {
1274 std::lock_guard<std::mutex> guard(__mutex);
1275 return __xp_play;
1276 } // -x- size_t get_xp_play -x-
1277
1278 /*======================================================================*//**
1279 @brief
1280 Duplicate a single character from the utilized portion of the internal ring
1281 buffer, and return it as an @c char primitive.
1282 @exception std::overflow_error If the amount of data exceeds the amount of
1283 data in the ring buffer
1284 @returns Data from ring buffer
1285 @see append(const char)
1286 @see c_str
1287 @see peek_to_array
1288 @see peek_to_string
1289 @see peek_to_vector
1290 @see remove
1291 *///=========================================================================
1292 char peek() {
1293 std::lock_guard<std::mutex> guard(__mutex);
1294
1295 // --------------------------------------------------------------------------
1296 // Syntax checks.
1297 // --------------------------------------------------------------------------
1298 if (__utilized() == 0) throw std::overflow_error("Ring buffer is empty");
1299
1300 // --------------------------------------------------------------------------
1301 // Duplicate the last byte of data from the ring buffer.
1302 // --------------------------------------------------------------------------
1303 return __buffer[__tail]; // Copy entry
1304
1305 } // -x- char peek -x-
1306
1307 /*======================================================================*//**
1308 @brief
1309 Duplicate the specified number of characters from the utilized portion of the
1310 internal ring buffer, and return them as a @c char* array.
1311 @post
1312 The string returned will need to be `free()`'d after it's no longer needed.
1313 @exception std::bad_alloc If @c malloc() fails to allocate memory
1314 @exception std::overflow_error If the amount of data exceeds the amount of
1315 data in the ring buffer
1316 @returns Data from ring buffer
1317 @see append(const char*, int)
1318 @see c_str
1319 @see peek
1320 @see peek_to_string
1321 @see peek_to_vector
1322 @see remove_to_array
1323 *///=========================================================================
1324 char* peek_to_array(
1325 /// Amount of data to duplicate
1326 size_t len,
1327 /// Where to store the data@n
1328 /// @c nullptr = allocate the needed memory using @c malloc()
1329 char* data = nullptr) {
1330
1331 // --------------------------------------------------------------------------
1332 // Internal variables.
1333 // --------------------------------------------------------------------------
1334 if (data == nullptr) {
1335 data = (char*)malloc(len);
1336 if (data == nullptr) throw std::bad_alloc(); // Throw exception if data wasn't allocated by malloc
1337 } // -x- if !data -x-
1338
1339 // --------------------------------------------------------------------------
1340 // Remove data from ring buffer.
1341 // --------------------------------------------------------------------------
1342 { std::lock_guard<std::mutex> guard(__mutex);
1343 size_t t = __tail;
1344 for (int i = 0; i < len; i++) {
1345 if (t >= __size) t = 0; // Wrap-around
1346 data[i] = __buffer[t]; // Copy entry
1347 t++; // Advance to next entry
1348 } // -x- for i -x-
1349 } // -x- mutex guard -x-
1350
1351 return data;
1352 } // -x- char* peek_to_array -x-
1353
1354 /*======================================================================*//**
1355 @brief
1356 Duplicate the specified number of characters from the utilized portion of the
1357 internal ring buffer, and return it as an @c std::string object.
1358 @exception std::invalid_argument If data length is -2 or below
1359 @exception std::overflow_error If the amount of data exceeds the amount of
1360 data in the ring buffer
1361 @returns Data from ring buffer
1362 @see append(const std::string, int)
1363 @see c_str
1364 @see peek
1365 @see peek_to_array
1366 @see peek_to_vector
1367 @see remove_to_string
1368 *///=========================================================================
1369 std::string peek_to_string(
1370 /// Amount of data to duplicate@n
1371 /// -1 = all data
1372 int len = -1,
1373 /// The previously instantiated std::string object to use (the default is to
1374 /// instantiate a new instance of std::string).
1375 std::string data = std::string()) {
1376
1377 // --------------------------------------------------------------------------
1378 // Syntax checks.
1379 // --------------------------------------------------------------------------
1380 if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
1381
1382 // --------------------------------------------------------------------------
1383 // Duplicate data from ring buffer. If no data is needed, then skip all the
1384 // mutex locking overhead, etc., and return the empty string.
1385 // --------------------------------------------------------------------------
1386 if (len != 0) {
1387 std::lock_guard<std::mutex> guard(__mutex);
1388 if (len == -1) len = __utilized();
1389 if (__utilized() < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds utilized space in ring buffer");
1390 data.resize(len); // Pre-allocate needed memory
1391 size_t t = __tail;
1392 for (int i = 0; i < len; i++) {
1393 if (t >= __size) t = 0; // Wrap-around
1394 data[i] = __buffer[t]; // Copy entry
1395 t++; // Advance to next entry
1396 } // -x- for i -x-
1397 } // -x- if len -x-
1398
1399 return data;
1400 } // -x- std::string peek_to_string -x-
1401
1402 /*======================================================================*//**
1403 @brief
1404 Duplicate the specified number of characters from the utilized portion of the
1405 internal ring buffer, and return it as an @c std::string object.
1406 @exception std::invalid_argument If data length is -2 or below
1407 @exception std::overflow_error If the amount of data exceeds the amount of
1408 data in the ring buffer
1409 @returns Data from ring buffer
1410 @see append(const std::vector<char>, int)
1411 @see append(const std::vector<unsigned char>, int)
1412 @see c_str
1413 @see peek
1414 @see peek_to_array
1415 @see peek_to_string
1416 @see remove_to_vector
1417 *///=========================================================================
1418 std::vector<char> peek_to_vector(
1419 /// Amount of data to duplicate@n
1420 /// -1 = all data
1421 int len = -1,
1422 /// The previously instantiated std::string object to use (the default is to
1423 /// instantiate a new instance of std::string).
1424 std::vector<char> data = std::vector<char>()) {
1425
1426 // --------------------------------------------------------------------------
1427 // Syntax checks.
1428 // --------------------------------------------------------------------------
1429 if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
1430
1431 // --------------------------------------------------------------------------
1432 // Duplicate data from ring buffer. If no data is needed, then skip all the
1433 // mutex locking overhead, etc., and return the empty string.
1434 // --------------------------------------------------------------------------
1435 if (len != 0) {
1436 std::lock_guard<std::mutex> guard(__mutex);
1437 if (len == -1) len = __utilized();
1438 if (__utilized() < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds utilized space in ring buffer");
1439 data.resize(len); // Pre-allocate needed memory
1440 size_t t = __tail;
1441 for (int i = 0; i < len; i++) {
1442 if (t >= __size) t = 0; // Wrap-around
1443 data[i] = __buffer[t]; // Copy entry
1444 t++; // Advance to next entry
1445 } // -x- for i -x-
1446 } // -x- if len -x-
1447
1448 return data;
1449 } // -x- std::vector<char> peek_to_vector -x-
1450
1451 /*======================================================================*//**
1452 @brief
1453 Remove one character from the utilized portion of the internal ring buffer,
1454 and return it .
1455 @exception std::overflow_error If the amount of data exceeds the amount of
1456 data in the ring buffer
1457 @returns Data from ring buffer
1458 @see append(const char)
1459 @see c_str
1460 @see peek
1461 @see remove_to_array
1462 @see remove_to_string
1463 @see remove_to_vector
1464 *///=========================================================================
1465 char remove() {
1466
1467 // --------------------------------------------------------------------------
1468 // Remove data from ring buffer. If no data is needed, then skip all the
1469 // mutex locking overhead, etc., and return the empty string.
1470 // --------------------------------------------------------------------------
1471 char ch;
1472 { std::lock_guard<std::mutex> guard(__mutex);
1473 if (__utilized() == 0) throw std::overflow_error("Ring buffer is empty");
1474 if (__tail >= __size) __tail = 0; // Wrap-around
1475 ch = __buffer[__tail]; // Copy entry
1476 __tail++; // Advance to next entry
1477 } // -x- mutex guard -x-
1478
1479 // --------------------------------------------------------------------------
1480 // Ring buffer memory allocation maintenance.
1481 // --------------------------------------------------------------------------
1482 __adjust_xp();
1483
1484 return ch;
1485 } // -x- char remove -x-
1486
1487 /*======================================================================*//**
1488 @brief
1489 Remove the specified number of characters from the utilized portion of the
1490 internal ring buffer, and return them as a @c char* array.
1491 @post
1492 The string returned will need to be `free()`'d after it's no longer needed.
1493 @exception std::bad_alloc If @c malloc() fails to allocate memory
1494 @exception std::overflow_error If the amount of data exceeds the amount of
1495 data in the ring buffer
1496 @returns Data from ring buffer
1497 @see append(const char*, int)
1498 @see c_str
1499 @see peek_to_array
1500 @see remove
1501 @see remove_to_string
1502 @see remove_to_vector
1503 *///=========================================================================
1504 char* remove_to_array(
1505 /// Maximum quantity of data to move@n
1506 size_t len,
1507 /// Where to store the data@n
1508 /// @c nullptr = allocate the needed memory using @c malloc()
1509 char* data = nullptr) {
1510
1511 // --------------------------------------------------------------------------
1512 // Internal variables.
1513 // --------------------------------------------------------------------------
1514 if (data == nullptr) {
1515 data = (char*)malloc(len);
1516 if (data == nullptr) throw std::bad_alloc(); // Throw exception if data wasn't allocated by malloc
1517 } // -x- if !data -x-
1518
1519 // --------------------------------------------------------------------------
1520 // Remove data from ring buffer. If no data is needed, then skip all the
1521 // mutex locking overhead, etc., and return the empty string.
1522 // --------------------------------------------------------------------------
1523 { std::lock_guard<std::mutex> guard(__mutex);
1524 for (int i = 0; i < len; i++) {
1525 if (__tail >= __size) __tail = 0; // Wrap-around
1526 data[i] = __buffer[__tail]; // Copy entry
1527 __tail++; // Advance to next entry
1528 } // -x- for i -x-
1529 } // -x- mutex guard -x-
1530
1531 // --------------------------------------------------------------------------
1532 // Ring buffer memory allocation maintenance.
1533 // --------------------------------------------------------------------------
1534 __adjust_xp();
1535
1536 return data;
1537 } // -x- char* remove_to_array -x-
1538
1539 /*======================================================================*//**
1540 @brief
1541 Remove the specified number of characters from the utilized portion of the
1542 internal ring buffer, and return it as an @c std::string object.
1543 @exception std::invalid_argument If data length is -2 or below
1544 @exception std::overflow_error If the amount of data exceeds the amount of
1545 data in the ring buffer
1546 @returns Data from ring buffer
1547 @see append(const std::string, int)
1548 @see c_str
1549 @see peek_to_string
1550 @see remove
1551 @see remove_to_array
1552 @see remove_to_vector
1553 *///=========================================================================
1554 std::string remove_to_string(
1555 /// Amount of data to remove@n
1556 /// -1 = all data
1557 int len = -1,
1558 /// The previously instantiated std::string object to use (the default is to
1559 /// instantiate a new instance of std::string).
1560 std::string data = std::string()) {
1561
1562 // --------------------------------------------------------------------------
1563 // Syntax checks.
1564 // --------------------------------------------------------------------------
1565 if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
1566
1567 // --------------------------------------------------------------------------
1568 // Remove data from ring buffer. If no data is needed, then skip all the
1569 // mutex locking overhead, etc., and return the empty string.
1570 // --------------------------------------------------------------------------
1571 if (len > 0) {
1572 std::lock_guard<std::mutex> guard(__mutex);
1573 if (len == -1) len = __utilized();
1574 if (__utilized() < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds utilized space in ring buffer");
1575 data.resize(len); // Pre-allocate needed memory
1576 for (int i = 0; i < len; i++) {
1577 if (__tail >= __size) __tail = 0; // Wrap-around
1578 data[i] = __buffer[__tail]; // Copy entry
1579 __tail++; // Advance to next entry
1580 } // -x- for i -x-
1581 } // -x- if len -x-
1582
1583 // --------------------------------------------------------------------------
1584 // Ring buffer memory allocation maintenance.
1585 // --------------------------------------------------------------------------
1586 __adjust_xp();
1587
1588 return data;
1589 } // -x- std::string remove_to_string -x-
1590
1591 /*======================================================================*//**
1592 @brief
1593 Remove the specified number of characters from the utilized portion of the
1594 internal ring buffer, and return it as an @c std::string object.
1595 @exception std::invalid_argument If data length is -2 or below
1596 @exception std::overflow_error If the amount of data exceeds the amount of
1597 data in the ring buffer
1598 @returns Data from ring buffer
1599 @see append(const std::vector<char>, int)
1600 @see append(const std::vector<unsigned char>, int)
1601 @see c_str
1602 @see peek_to_vector
1603 @see remove
1604 @see remove_to_array
1605 @see remove_to_string
1606 *///=========================================================================
1607 std::vector<char> remove_to_vector(
1608 /// Amount of data to remove@n
1609 /// -1 = all data
1610 int len = -1,
1611 /// The previously instantiated std::string object to use (the default is to
1612 /// instantiate a new instance of std::string).
1613 std::vector<char> data = std::vector<char>()) {
1614
1615 // --------------------------------------------------------------------------
1616 // Syntax checks.
1617 // --------------------------------------------------------------------------
1618 if (len < -1) throw std::invalid_argument("Unrealistic data length of " + std::to_string(len));
1619
1620 // --------------------------------------------------------------------------
1621 // Remove data from ring buffer. If no data is needed, then skip all the
1622 // mutex locking overhead, etc., and return the empty string.
1623 // --------------------------------------------------------------------------
1624 if (len > 0) {
1625 std::lock_guard<std::mutex> guard(__mutex);
1626 if (len == -1) len = __utilized();
1627 if (__utilized() < len) throw std::overflow_error("Amount of data (len=" + std::to_string(len) + ") exceeds utilized space in ring buffer");
1628 data.resize(len); // Pre-allocate needed memory
1629 for (int i = 0; i < len; i++) {
1630 if (__tail >= __size) __tail = 0; // Wrap-around
1631 data[i] = __buffer[__tail]; // Copy entry
1632 __tail++; // Advance to next entry
1633 } // -x- for i -x-
1634 } // -x- if len -x-
1635
1636 // --------------------------------------------------------------------------
1637 // Ring buffer memory allocation maintenance.
1638 // --------------------------------------------------------------------------
1639 __adjust_xp();
1640
1641 return data;
1642 } // -x- std::vector<char> remove_to_vector -x-
1643
1644 /*======================================================================*//**
1645 @brief
1646 Flip the order of all entries in the ring buffer.
1647 @returns The same rring object so as to facilitate stacking
1648 *///=========================================================================
1649 rring* reverse() {
1650 char carry;
1651 { std::lock_guard<std::mutex> guard(__mutex);
1652 int h = __head;
1653 int t = __tail;
1654 for (int i = __utilized() / 2; i > 0; i--) {
1655 if (--h < 0) h = __size - 1; // Wrap-around
1656 if (t >= __size) t = 0; // Wrap-around
1657 carry = __buffer[h];
1658 __buffer[h] = __buffer[t];
1659 __buffer[t] = carry;
1660 t++; // Advance to next entry
1661 } // -x- for i -x-
1662 } // -x- mutex guard -x-
1663 return this;
1664 } // -x- rring* reverse -x-
1665
1666 /*======================================================================*//**
1667 @brief
1668 Alter the position of the internal head position, without changing any of the
1669 the pre-existing data in the underlying ring buffer.
1670 @exception std::overflow_error If the new position falls beyond the internal
1671 tail position (or, in other words, it exceeds
1672 the available free space in the ring buffer)
1673 @returns The same rring object so as to facilitate stacking
1674 @see get_available
1675 @see get_head
1676 @see get_tail
1677 @see get_utilized
1678 @see set_tail
1679 @qualifier ADVANCED
1680 *///=========================================================================
1681 rring* set_head(
1682 /// Position adjustment (may also be negative, but only for relative @c type)
1683 int offset,
1684 /// Type of adjustment@n
1685 /// TRUE = absolute (default)@n
1686 /// FALSE = relative (@c offset may also be negative)
1687 bool type = true) {
1688
1689 // --------------------------------------------------------------------------
1690 // Absolute position.
1691 // --------------------------------------------------------------------------
1692 if (type) {
1693 if (offset < 0) throw std::overflow_error("Head position adjustment of " + std::to_string(offset) + " cannot be negative");
1694 std::lock_guard<std::mutex> guard(__mutex);
1695 if (offset > __available()) throw std::overflow_error("Head position adjustment of " + std::to_string(offset) + " exceeds available space in ring buffer");
1696 __head = (__head + offset) % __size;
1697
1698 // --------------------------------------------------------------------------
1699 // Relative position.
1700 // --------------------------------------------------------------------------
1701 } else {
1702 if (offset > 0) { // Positive adjustment
1703 std::lock_guard<std::mutex> guard(__mutex);
1704 if ( offset > __available()) throw std::overflow_error("Head position adjustment of " + std::to_string(offset) + " exceeds available space in ring buffer");
1705 __head = (__head + offset) % __size;
1706 } else if (offset < 0) { // Negative adjustment
1707 std::lock_guard<std::mutex> guard(__mutex);
1708 if (0 - offset > __utilized() ) throw std::overflow_error("Head position adjustment of " + std::to_string(offset) + " exceeds utilized space in ring buffer");
1709 __head += offset;
1710 if (__head >= __size) __head += __size;
1711 } // -x- if offset -x-
1712 } // -x- if type -x-
1713
1714 return this;
1715 } // -x- rring* set_head -x-
1716
1717 /*======================================================================*//**
1718 @brief
1719 Configure the total size of the ring buffer. The new size cannot be smaller
1720 than the amount of data that's already present in the ring buffer (see the
1721 @ref get_utilized method to find out how much data is present).
1722 @exception std::overflow_error If the new size exceeds the amount of data
1723 that's already present in the ring buffer
1724 @returns The same rring object so as to facilitate stacking
1725 @see get_available
1726 @see get_size
1727 @see get_utilized
1728 *///=========================================================================
1729 rring* set_size(
1730 /// New size of ring buffer
1731 const size_t len) {
1732 { std::lock_guard<std::mutex> guard(__mutex);
1733 if (__utilized() < len) throw std::overflow_error("New size of ring buffer (len=" + std::to_string(len) + ") exceeds utilized amount");
1734 // TODO: Change (increase or decrease) total size of ring buffer, if possible,
1735 // which will entail moving data around if it wraps, and also throwing
1736 // an exception if the reduction is less than the utilized portion.
1737 __size = len;
1738 } // -x- mutex guard -x-
1739
1740 return this;
1741 } // -x- rring* set_size -x-
1742
1743 /*======================================================================*//**
1744 @brief
1745 Alter the position of the internal tail position, without changing any of the
1746 the pre-existing data in the underlying ring buffer.
1747 @exception std::overflow_error If the new position falls beyond the internal
1748 head position (or, in other words, it exceeds
1749 the available free space in the ring buffer)
1750 @returns The same rring object so as to facilitate stacking
1751 @see get_available
1752 @see get_head
1753 @see get_tail
1754 @see get_utilized
1755 @see set_head
1756 @qualifier ADVANCED
1757 *///=========================================================================
1758 rring* set_tail(
1759 /// Position adjustment (may also be negative, but only for relative @c type)
1760 int offset,
1761 /// Type of adjustment@n
1762 /// TRUE = absolute (default)@n
1763 /// FALSE = relative (@c offset may also be negative)
1764 bool type = true) {
1765
1766 // --------------------------------------------------------------------------
1767 // Absolute position.
1768 // --------------------------------------------------------------------------
1769 if (type) {
1770 if (offset < 0) throw std::overflow_error("Tail position adjustment of " + std::to_string(offset) + " cannot be negative");
1771 std::lock_guard<std::mutex> guard(__mutex);
1772 if (offset > __utilized()) throw std::overflow_error("Tail position adjustment of " + std::to_string(offset) + " exceeds utilized space in ring buffer");
1773 __tail = (__tail + offset) % __size;
1774
1775 // --------------------------------------------------------------------------
1776 // Relative position.
1777 // --------------------------------------------------------------------------
1778 } else {
1779 if (offset > 0) { // Positive position
1780 std::lock_guard<std::mutex> guard(__mutex);
1781 if ( offset > __utilized() ) throw std::overflow_error("Tail position adjustment of " + std::to_string(offset) + " exceeds utilized space in ring buffer");
1782 __tail = (__tail + offset) % __size;
1783 } else if (offset < 0) { // Negative adjustment
1784 std::lock_guard<std::mutex> guard(__mutex);
1785 if (0 - offset > __available()) throw std::overflow_error("Tail position adjustment of " + std::to_string(offset) + " exceeds available space in ring buffer");
1786 __tail += offset;
1787 if (__tail >= __size) __tail += __size;
1788 } // -x- if offset -x-
1789 } // -x- if type -x-
1790
1791 return this;
1792 } // -x- rring* set_tail -x-
1793
1794 /*======================================================================*//**
1795 @brief
1796 Specify whether the internal buffer's memory will be cleared by the
1797 destructor before de-allocating it (default is @c true upon instantiation).
1798
1799 @note
1800 This is an important security feature for when the ring buffer may have been
1801 storing private data because it prevents the leaking of private data to other
1802 process that coincidentally gain access to the same memory area (e.g., by way
1803 of future calls to `malloc()`).
1804 @returns The same rring object so as to facilitate stacking
1805 @see get_wipe_policy
1806 @qualifier ADVANCED
1807 *///=========================================================================
1808 rring* set_wipe_policy(
1809 /// TRUE = clear buffer memory in destructor@n
1810 /// FALSE = don't clear buffer memory in destructor
1811 const bool policy_flag) noexcept {
1812 { std::lock_guard<std::mutex> guard(__mutex);
1813 __wipe_policy = policy_flag;
1814 } // -x- mutex guard -x-
1815
1816 return this;
1817 } // -x- rring* set_wipe_policy -x-
1818
1819 /*======================================================================*//**
1820 @brief
1821 Configure the automatic expansion policy's incremental block size.
1822 @exception std::overflow_error If the new setting, which reduces the maximum
1823 quantity of blocks, exceeds the available
1824 memory and would otherwise result in losing
1825 some of the utilized entries
1826 @returns The same rring object so as to facilitate stacking
1827 @see get_xp_block_size
1828 @see set_xp_max
1829 @see set_xp_play
1830 @qualifier ADVANCED
1831 *///=========================================================================
1832 rring* set_xp_block_size(
1833 /// Maximum possible number of expansion blocks@n
1834 /// 0 = disable expansion policy
1835 const uint xp_block_size) {
1836 { std::lock_guard<std::mutex> guard(__mutex);
1837 if (xp_block_size == __xp_block_size) return this; // It's pointless to change to the same thing
1838
1839 // --------------------------------------------------------------------------
1840 // Internal variables.
1841 // --------------------------------------------------------------------------
1842 uint old_xp_used_entries = __xp_used * __xp_block_size;
1843 uint new_xp_used_entries = __xp_used * xp_block_size;
1844 size_t base_size = __size - old_xp_used_entries;
1845 size_t utilized = __utilized();
1846//std::cout << "__size=" << __size << " base_size=" << base_size << " old_xp_used_entries=" << old_xp_used_entries << " new_xp_used_entries=" << new_xp_used_entries << " utilized=" << utilized << std::endl;
1847
1848 // --------------------------------------------------------------------------
1849 // Check if size is too small, and throw an exception if it is.
1850 // --------------------------------------------------------------------------
1851 if (utilized > base_size + new_xp_used_entries) throw std::overflow_error("Reducing block size to " + std::to_string(xp_block_size) + " exceeds available memory in ring buffer");
1852
1853 // --------------------------------------------------------------------------
1854 // Update __xp_block_size and then call __adjust_xp to optimize memory block
1855 // re-allocation.
1856 // --------------------------------------------------------------------------
1857 __xp_block_size = xp_block_size;
1858 __adjust_xp(base_size + new_xp_used_entries);
1859
1860 } // -x- mutex guard -x-
1861
1862 return this;
1863 } // -x- rring* set_xp_block_size -x-
1864
1865 /*======================================================================*//**
1866 @brief
1867 Configure the automatic expansion policy's maximum number of blocks.
1868 @exception std::overflow_error If the new setting, which reduces the maximum
1869 quantity of blocks, exceeds the available
1870 memory and would otherwise result in losing
1871 some of the utilized entries
1872 @returns The same rring object so as to facilitate stacking
1873 @see get_xp_max
1874 @see set_xp_block_size
1875 @see set_xp_play
1876 @qualifier ADVANCED
1877 *///=========================================================================
1878 rring* set_xp_max(
1879 /// Maximum possible number of expansion blocks@n
1880 /// 0 = disable expansion policy
1881 const uint xp_max) {
1882 { std::lock_guard<std::mutex> guard(__mutex);
1883
1884 // --------------------------------------------------------------------------
1885 // Increasing the maximum, or keeping the maximum the same, is easy, because
1886 // there's no risk of losing any utilized data.
1887 // --------------------------------------------------------------------------
1888 if (xp_max >= __xp_max) {
1889 __xp_max = xp_max;
1890
1891 // --------------------------------------------------------------------------
1892 // Decreasing the maximum requires making sure we don't lose data.
1893 // --------------------------------------------------------------------------
1894 } else {
1895 size_t utilized = __utilized();
1896 uint entries = __size + ((__xp_max - __xp_used) * __xp_block_size) - utilized;// + (utilized % __xp_block_size != 0);
1897std::cout << "__xp_max=" << __xp_max << " xp_max=" << xp_max << "*" << xp_max * __xp_block_size << " entries=" << entries << std::endl;
1898 if (entries > xp_max * __xp_block_size) throw std::overflow_error("Reducing maximum blocks to " + std::to_string(xp_max) + " exceeds available blocks in ring buffer");
1899 __xp_max = xp_max;
1900 } // -x- if xp_max -x-
1901 } // -x- mutex guard -x-
1902
1903 return this;
1904 } // -x- rring* set_xp_max -x-
1905
1906 /*======================================================================*//**
1907 @brief
1908 Configure the automatic expansion policy's quantity of available blocks that
1909 must be present before attempting a reduction of ring buffer memory.
1910 @returns The same rring object so as to facilitate stacking
1911 @see get_xp_play
1912 @see set_xp_block_size
1913 @see set_xp_max
1914 @qualifier ADVANCED
1915 *///=========================================================================
1916 rring* set_xp_play(
1917 /// Number of available blocks must be present before attempting reduction
1918 const uint xp_play) noexcept {
1919 { std::lock_guard<std::mutex> guard(__mutex);
1920 __xp_play = xp_play; // Changing this setting doesn't directly impact any data, so just save it
1921 } // -x- mutex guard -x-
1922
1923 return this;
1924 } // -x- rring* set_xp_play -x-
1925
1926 /*======================================================================*//**
1927 @brief
1928 Convert the entire contents of this ring buffer to an ASCIIZ string in the
1929 form of a @c char* array.
1930 @post
1931 The string returned will need to be `free()`'d after it's no longer needed.
1932
1933 @code{.cpp}
1934 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1935 #include <randolf/rring>
1936
1937 int main(int argc, char *argv[]) {
1938 randolf::rring rb(1024);
1939 rb.append("This is an example.");
1940 char* c = rb;
1941 std::cout << "\"" << c << "\"" << std::endl;
1942 free(c);
1943 return EXIT_SUCCESS;
1944 } // -x- int main -x-
1945 @endcode
1946
1947 @exception std::bad_alloc If @c malloc() fails to allocate memory
1948 @returns Contents of this ring buffer as an ASCII string as a @c char* array.
1949 @see c_str
1950 *///=========================================================================
1951 operator char*() noexcept { return c_str(); } // -x- operator char*() -x-
1952
1953 /*======================================================================*//**
1954 @brief
1955 Convert the entire contents of this ring buffer to an @c std::string object.
1956
1957 @code{.cpp}
1958 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1959 #include <string> // std::string
1960 #include <randolf/rring>
1961
1962 int main(int argc, char *argv[]) {
1963 randolf::rring rb(1024);
1964 rb.append("This is an example.");
1965 std::string s = rb;
1966 std::cout << "\"" << s << "\"" << std::endl;
1967 return EXIT_SUCCESS;
1968 } // -x- int main -x-
1969 @endcode
1970
1971 @returns Contents of this ring buffer as an std::string object
1972 @see peek_to_string
1973 *///=========================================================================
1974 operator std::string() noexcept { return peek_to_string(); } // -x- operator std::string -x-
1975
1976 /*======================================================================*//**
1977 @brief
1978 Convert the entire contents of this ring buffer to an @c std::vector<char>
1979 object.
1980
1981 @code{.cpp}
1982 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1983 #include <string> // std::string
1984 #include <vector> // std::vector
1985 #include <randolf/rring>
1986
1987 int main(int argc, char *argv[]) {
1988 randolf::rring rb(1024);
1989 rb.append("This is an example.");
1990 std::vector<char> v = rb;
1991 std::cout << "\"" << std::string(v.begin(), v.end()) << "\"" << std::endl;
1992 return EXIT_SUCCESS;
1993 } // -x- int main -x-
1994 @endcode
1995
1996 @returns Contents of this ring buffer as an std::vector<char> object
1997 @see peek_to_vector
1998 *///=========================================================================
1999 operator std::vector<char>() noexcept { return peek_to_vector(); } // -x- operator std::vector<char> -x-
2000
2001 /*======================================================================*//**
2002 @brief
2003 Array-style access to the utilized portion of the ring buffer's contents,
2004 which yields the same result as that of the @ref at() method.
2005
2006 The first element is at index 0.
2007 @throws std::out_of_range if the index is out-of-range
2008 @returns char
2009 @see at
2010 *///=========================================================================
2011 char operator[](
2012 /// Index of character to access (0 = first element; negative index values
2013 /// are calculated in reverse, starting with -1 as the final position)
2014 int index) {
2015 std::lock_guard<std::mutex> guard(__mutex);
2016
2017 // --------------------------------------------------------------------------
2018 // Internal variables.
2019 // --------------------------------------------------------------------------
2020 int MAX = __utilized();
2021
2022 // --------------------------------------------------------------------------
2023 // Syntax checks.
2024 // --------------------------------------------------------------------------
2025 if (index >= MAX || 0 - index > MAX) throw std::out_of_range("index (position=" + std::to_string(index) + ") falls outside of the utilized portion (length=" + std::to_string(MAX) + ") of the buffer");
2026
2027 // --------------------------------------------------------------------------
2028 // Calculate character's position.
2029 // --------------------------------------------------------------------------
2030 index = index >= 0 ? __tail + index : __head + index;
2031 if (index >= __size) index -= __size;
2032
2033 return __buffer[index];
2034 } // -x- char operator[] -x-
2035
2036 /*======================================================================*//**
2037 @brief
2038 Support convenient streaming usage with std::cout, std::cerr, and friends.
2039
2040 @code{.cpp}
2041 #include <iostream> // std::cout, std::cerr, std::endl, etc.
2042 #include <string> // std::string
2043 #include <randolf/rring>
2044
2045 int main(int argc, char *argv[]) {
2046 randolf::rring rb(1024);
2047 rb.append("This is an example.");
2048 std::cout << "\"" << rb << "\"" << std::endl;
2049 return EXIT_SUCCESS;
2050 } // -x- int main -x-
2051 @endcode
2052
2053 @returns Contents of this ring buffer as an std::string object (which will be
2054 de-allocated by the std::string object's destructor upon going out
2055 of scope)
2056 @see peek_to_string
2057 *///=========================================================================
2058 friend std::ostream& operator<< (
2059 /// Output stream (provided automatically by std::cout and std::cerr)
2060 std::ostream& o,
2061 /// Object class (matched by compiler)
2062 rring& c) noexcept { return o << c.peek_to_string(); } // -x- std::ostream& operator<< -x-
2063
2064 }; // -x- class rring -x-
2065
2066}; // -x- namespace randolf -x-