You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ringbuffer.hpp 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #pragma once
  2. #include <atomic>
  3. #include <dsp/common.hpp>
  4. namespace rack {
  5. namespace dsp {
  6. /** Lock-free queue with fixed size and no allocations.
  7. If S is not a power of 2, performance might be reduced, and the index could overflow in a thousand years, but it should usually be fine for your purposes.
  8. Supports only a single producer and consumer.
  9. To my knowledge, nobody has invented a 100% correct multiple producer/consumer lock-free ring buffer for x86_64.
  10. */
  11. template <typename T, size_t S>
  12. struct RingBuffer {
  13. std::atomic<size_t> start{0};
  14. std::atomic<size_t> end{0};
  15. T data[S];
  16. /** Adds an element to the end of the buffer.
  17. */
  18. void push(T t) {
  19. size_t i = end % S;
  20. data[i] = t;
  21. end++;
  22. }
  23. /** Copies an array to the end of the buffer.
  24. `n` must be at most S.
  25. */
  26. void pushBuffer(const T* t, int n) {
  27. size_t i = end % S;
  28. size_t e1 = i + n;
  29. size_t e2 = (e1 < S) ? e1 : S;
  30. std::memcpy(&data[i], t, sizeof(T) * (e2 - i));
  31. if (e1 > S) {
  32. std::memcpy(data, &t[S - i], sizeof(T) * (e1 - S));
  33. }
  34. end += n;
  35. }
  36. /** Removes and returns an element from the start of the buffer.
  37. */
  38. T shift() {
  39. size_t i = start % S;
  40. T t = data[i];
  41. start++;
  42. return t;
  43. }
  44. /** Removes and copies an array from the start of the buffer.
  45. `n` must be at most S.
  46. */
  47. void shiftBuffer(T* t, size_t n) {
  48. size_t i = start % S;
  49. size_t s1 = i + n;
  50. size_t s2 = (s1 < S) ? s1 : S;
  51. std::memcpy(t, &data[i], sizeof(T) * (s2 - i));
  52. if (s1 > S) {
  53. std::memcpy(&t[S - i], data, sizeof(T) * (s1 - S));
  54. }
  55. start += n;
  56. }
  57. void clear() {
  58. start = end.load();
  59. }
  60. bool empty() const {
  61. return start >= end;
  62. }
  63. bool full() const {
  64. return end - start >= S;
  65. }
  66. size_t size() const {
  67. return end - start;
  68. }
  69. size_t capacity() const {
  70. return S - size();
  71. }
  72. };
  73. /** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory.
  74. This is not thread-safe.
  75. */
  76. template <typename T, size_t S>
  77. struct DoubleRingBuffer {
  78. std::atomic<size_t> start{0};
  79. std::atomic<size_t> end{0};
  80. T data[2 * S];
  81. void push(T t) {
  82. size_t i = end % S;
  83. data[i] = t;
  84. data[i + S] = t;
  85. end++;
  86. }
  87. T shift() {
  88. size_t i = start % S;
  89. T t = data[i];
  90. start++;
  91. return t;
  92. }
  93. void clear() {
  94. start = end.load();
  95. }
  96. bool empty() const {
  97. return start >= end;
  98. }
  99. bool full() const {
  100. return end - start >= S;
  101. }
  102. size_t size() const {
  103. return end - start;
  104. }
  105. size_t capacity() const {
  106. return S - size();
  107. }
  108. /** Returns a pointer to S consecutive elements for appending.
  109. If any data is appended, you must call endIncr afterwards.
  110. Pointer is invalidated when any other method is called.
  111. */
  112. T* endData() {
  113. size_t i = end % S;
  114. return &data[i];
  115. }
  116. void endIncr(size_t n) {
  117. size_t i = end % S;
  118. size_t e1 = i + n;
  119. size_t e2 = (e1 < S) ? e1 : S;
  120. // Copy data forward
  121. std::memcpy(&data[S + i], &data[i], sizeof(T) * (e2 - i));
  122. if (e1 > S) {
  123. // Copy data backward from the doubled block to the main block
  124. std::memcpy(data, &data[S], sizeof(T) * (e1 - S));
  125. }
  126. end += n;
  127. }
  128. /** Returns a pointer to S consecutive elements for consumption
  129. If any data is consumed, call startIncr afterwards.
  130. */
  131. const T* startData() const {
  132. size_t i = start % S;
  133. return &data[i];
  134. }
  135. void startIncr(size_t n) {
  136. start += n;
  137. }
  138. };
  139. /** A cyclic buffer which maintains a valid linear array of size S by sliding along a larger block of size N.
  140. This is not thread-safe.
  141. The linear array of S elements are moved back to the start of the block once it outgrows past the end.
  142. This happens every N - S pushes, so the push() time is O(1 + S / (N - S)).
  143. For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer.
  144. Not thread-safe.
  145. */
  146. template <typename T, size_t S, size_t N>
  147. struct AppleRingBuffer {
  148. size_t start = 0;
  149. size_t end = 0;
  150. T data[N];
  151. void returnBuffer() {
  152. // move end block to beginning
  153. // may overlap, but memmove handles that correctly
  154. size_t s = size();
  155. std::memmove(data, &data[start], sizeof(T) * s);
  156. start = 0;
  157. end = s;
  158. }
  159. void push(T t) {
  160. if (end + 1 > N) {
  161. returnBuffer();
  162. }
  163. data[end++] = t;
  164. }
  165. T shift() {
  166. return data[start++];
  167. }
  168. bool empty() const {
  169. return start == end;
  170. }
  171. bool full() const {
  172. return end - start == S;
  173. }
  174. size_t size() const {
  175. return end - start;
  176. }
  177. size_t capacity() const {
  178. return S - size();
  179. }
  180. /** Returns a pointer to S consecutive elements for appending, requesting to append n elements.
  181. */
  182. T* endData(size_t n) {
  183. if (end + n > N) {
  184. returnBuffer();
  185. }
  186. return &data[end];
  187. }
  188. /** Actually increments the end position
  189. Must be called after endData(), and `n` must be at most the `n` passed to endData()
  190. */
  191. void endIncr(size_t n) {
  192. end += n;
  193. }
  194. /** Returns a pointer to S consecutive elements for consumption
  195. If any data is consumed, call startIncr afterwards.
  196. */
  197. const T* startData() const {
  198. return &data[start];
  199. }
  200. void startIncr(size_t n) {
  201. // This is valid as long as n < S
  202. start += n;
  203. }
  204. };
  205. } // namespace dsp
  206. } // namespace rack