Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spsc_ring_index.hpp
Go to the documentation of this file.
1#pragma once
2
3// SpscRingIndex — index-pair cursor management for a single-producer
4// single-consumer (SPSC) ring buffer whose storage the CALLER owns.
5//
6// This class does NOT own a storage buffer. Instead it tracks the read and
7// write positions inside a caller-owned circular buffer (a contiguous array
8// the caller allocates separately — e.g. an `std::vector<float>` for an audio
9// sample ring, a `std::vector<uint8_t>` for a byte stream, etc.). The caller
10// uses the index ranges returned by `prepare_to_write()` / `prepare_to_read()`
11// to copy into / out of its own buffer; SpscRingIndex only deals with the
12// modular arithmetic of "where to read/write next, and how many items can
13// be touched without wrapping past the producer/consumer cursor."
14//
15// Because a single linear range of items in the underlying buffer may wrap
16// around the end of the array, `prepare_to_*` returns **two** index ranges
17// (each `start` + `size`). The second range is empty (`size_2 == 0`) when
18// no wrap occurs. Callers iterate or copy both ranges in sequence.
19//
20// Lock-free for one producer thread + one consumer thread (SPSC) — the
21// reader and writer cursors are independent `std::atomic<int>` values with
22// acquire/release ordering. Multi-producer / multi-consumer usage requires
23// external synchronization.
24//
25// Pick this over its two siblings when the payload is bulk samples/bytes you
26// want to memcpy in place rather than element-wise values you want to push and
27// pop:
28//
29// * `SpscRingIndex` — cursors only; you own the array and copy into the two
30// returned index ranges. Zero-copy bulk transfer.
31// * `SpscQueue<T,N>` — a real queue: owns its storage, moves elements in and
32// out one at a time.
33// * `TripleBuffer<T>` — no queue at all: publishes a single latest value,
34// dropping anything the consumer did not pick up.
35
36#include <algorithm>
37#include <atomic>
38#include <cstddef>
39
40namespace pulp::runtime {
41
43public:
49 explicit SpscRingIndex(int capacity) noexcept
50 : capacity_(capacity < 1 ? 1 : capacity) {}
51
53 int capacity() const noexcept { return capacity_; }
54
56 int num_ready() const noexcept {
57 const int w = write_pos_.load(std::memory_order_acquire);
58 const int r = read_pos_.load(std::memory_order_acquire);
59 const int diff = w - r;
60 return diff >= 0 ? diff : diff + capacity_;
61 }
62
65 int free_space() const noexcept {
66 return capacity_ - num_ready() - 1;
67 }
68
71 void reset() noexcept {
72 read_pos_.store(0, std::memory_order_relaxed);
73 write_pos_.store(0, std::memory_order_relaxed);
74 }
75
82 void prepare_to_write(int num_to_write,
83 int& start_1, int& size_1,
84 int& start_2, int& size_2) const noexcept {
85 const int w = write_pos_.load(std::memory_order_relaxed);
86 const int r = read_pos_.load(std::memory_order_acquire);
87 const int free = (r > w ? r - w : capacity_ - w + r) - 1;
88 const int can_write = std::min(num_to_write < 0 ? 0 : num_to_write,
89 free < 0 ? 0 : free);
90
91 if (can_write <= 0) {
92 start_1 = w;
93 size_1 = 0;
94 start_2 = 0;
95 size_2 = 0;
96 return;
97 }
98
99 start_1 = w;
100 const int contiguous = capacity_ - w;
101 if (can_write <= contiguous) {
102 size_1 = can_write;
103 start_2 = 0;
104 size_2 = 0;
105 } else {
106 size_1 = contiguous;
107 start_2 = 0;
108 size_2 = can_write - contiguous;
109 }
110 }
111
116 void finish_write(int num_written) noexcept {
117 if (num_written <= 0) return;
118 const int w = write_pos_.load(std::memory_order_relaxed);
119 // True modulo wrap. The previous `next -= capacity_` only handled
120 // a single overflow span, so an oversized `num_written` (caller
121 // error past the prepared sum) could leave `next` >= capacity_
122 // and subsequent prepare_* calls would return invalid indices.
123 int next = (w + num_written) % capacity_;
124 if (next < 0) next += capacity_;
125 write_pos_.store(next, std::memory_order_release);
126 }
127
131 void prepare_to_read(int num_to_read,
132 int& start_1, int& size_1,
133 int& start_2, int& size_2) const noexcept {
134 const int r = read_pos_.load(std::memory_order_relaxed);
135 const int w = write_pos_.load(std::memory_order_acquire);
136 const int available = (w >= r ? w - r : capacity_ - r + w);
137 const int can_read = std::min(num_to_read < 0 ? 0 : num_to_read,
138 available);
139
140 if (can_read <= 0) {
141 start_1 = r;
142 size_1 = 0;
143 start_2 = 0;
144 size_2 = 0;
145 return;
146 }
147
148 start_1 = r;
149 const int contiguous = capacity_ - r;
150 if (can_read <= contiguous) {
151 size_1 = can_read;
152 start_2 = 0;
153 size_2 = 0;
154 } else {
155 size_1 = contiguous;
156 start_2 = 0;
157 size_2 = can_read - contiguous;
158 }
159 }
160
162 void finish_read(int num_read) noexcept {
163 if (num_read <= 0) return;
164 const int r = read_pos_.load(std::memory_order_relaxed);
165 // True modulo wrap — mirror finish_write so an oversized num_read
166 // does not leave the cursor out of range.
167 int next = (r + num_read) % capacity_;
168 if (next < 0) next += capacity_;
169 read_pos_.store(next, std::memory_order_release);
170 }
171
172 SpscRingIndex(const SpscRingIndex&) = delete;
176
177private:
178 const int capacity_;
179 std::atomic<int> read_pos_{0};
180 std::atomic<int> write_pos_{0};
181};
182
183
184} // namespace pulp::runtime
SpscRingIndex(SpscRingIndex &&)=delete
void finish_write(int num_written) noexcept
Advance the write cursor by num_written items.
Definition spsc_ring_index.hpp:116
int num_ready() const noexcept
Number of items currently available to read.
Definition spsc_ring_index.hpp:56
int free_space() const noexcept
Free space (in items) available for new writes.
Definition spsc_ring_index.hpp:65
void prepare_to_read(int num_to_read, int &start_1, int &size_1, int &start_2, int &size_2) const noexcept
Plan a read of up to num_to_read items.
Definition spsc_ring_index.hpp:131
SpscRingIndex(const SpscRingIndex &)=delete
SpscRingIndex & operator=(const SpscRingIndex &)=delete
int capacity() const noexcept
Total size of the managed buffer in items.
Definition spsc_ring_index.hpp:53
void reset() noexcept
Reset the cursors back to empty.
Definition spsc_ring_index.hpp:71
SpscRingIndex(int capacity) noexcept
Construct an SpscRingIndex that manages indices in [0, capacity).
Definition spsc_ring_index.hpp:49
SpscRingIndex & operator=(SpscRingIndex &&)=delete
void finish_read(int num_read) noexcept
Advance the read cursor by num_read items.
Definition spsc_ring_index.hpp:162
void prepare_to_write(int num_to_write, int &start_1, int &size_1, int &start_2, int &size_2) const noexcept
Plan a write of up to num_to_write items.
Definition spsc_ring_index.hpp:82
Definition generated_event_source.hpp:33