Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
slot.hpp
Go to the documentation of this file.
1#pragma once
2
36
38
39#include <atomic>
40#include <cassert>
41#include <cstddef>
42#include <memory>
43#include <thread>
44#include <utility>
45#include <vector>
46
47namespace pulp::runtime {
48
83template <typename T>
84class Slot {
85public:
93 class ReadGuard {
94 public:
95 ReadGuard() noexcept = default;
96 ReadGuard(const ReadGuard&) = delete;
97 ReadGuard& operator=(const ReadGuard&) = delete;
98
100 : owner_(other.owner_), value_(other.value_) {
101 other.owner_ = nullptr;
102 other.value_ = nullptr;
103 }
104
106 if (owner_ != nullptr)
107 owner_->readers_.fetch_sub(1, std::memory_order_seq_cst);
108 }
109
110 T* get() const noexcept { return value_; }
111 T& operator*() const noexcept { return *value_; }
112 T* operator->() const noexcept { return value_; }
113 explicit operator bool() const noexcept { return value_ != nullptr; }
114
115 private:
116 friend class Slot;
117 ReadGuard(const Slot* owner, T* value) noexcept : owner_(owner), value_(value) {}
118
119 const Slot* owner_ = nullptr;
120 T* value_ = nullptr;
121 };
122
123 Slot() = default;
124 Slot(const Slot&) = delete;
125 Slot& operator=(const Slot&) = delete;
126
130
135 [[nodiscard]] ReadGuard read() const noexcept {
136 readers_.fetch_add(1, std::memory_order_seq_cst);
137 // seq_cst pairs with the store in publish() and the load in
138 // reclaim_if_quiescent(): either a publisher observes this pin and
139 // declines to free, or this load observes the publisher's new pointer.
140 T* value = live_raw_.load(std::memory_order_seq_cst);
141 if (value == nullptr) {
142 readers_.fetch_sub(1, std::memory_order_seq_cst);
143 return ReadGuard{};
144 }
145 return ReadGuard{this, value};
146 }
147
156 if (live_)
157 retired_.reserve(retired_.size() + 1);
158 }
159
162 void publish_prepared(std::shared_ptr<T> next) noexcept {
163 assert((!live_ || retired_.size() < retired_.capacity()) &&
164 "Slot::publish_prepared requires prepare_publish");
165 live_raw_.store(next.get(), std::memory_order_seq_cst);
166 if (live_)
167 retired_.push_back(std::move(live_));
168 live_ = std::move(next);
170 }
171
172 void publish(std::shared_ptr<T> next) {
173 // Reserve before changing live_raw_: allocation failure must leave the
174 // currently-published pointer and owner coherent.
176 publish_prepared(std::move(next));
177 }
178
179 void publish(std::unique_ptr<T> next) { publish(std::shared_ptr<T>(std::move(next))); }
180
186 void unpublish() { publish(std::shared_ptr<T>{}); }
187
192 if (readers_.load(std::memory_order_seq_cst) == 0)
193 retired_.clear();
194 }
195
199 while (readers_.load(std::memory_order_seq_cst) != 0)
200 std::this_thread::yield();
201 retired_.clear();
202 }
203
207 const std::shared_ptr<T>& live() const noexcept { return live_; }
208
210 std::size_t retired_count() const noexcept { return retired_.size(); }
211
213 bool has_value() const noexcept {
214 return live_raw_.load(std::memory_order_acquire) != nullptr;
215 }
216
217private:
218 // Readers dereference `live_raw_` only. `live_` and `retired_` keep the
219 // pointed-to objects alive and are touched by the publisher thread alone.
220 std::shared_ptr<T> live_;
221 std::atomic<T*> live_raw_{nullptr};
222 std::vector<std::shared_ptr<T>> retired_;
223 mutable std::atomic<std::uint32_t> readers_{0};
224};
225
253template <typename T, std::size_t RetireCapacity = 8>
254class Handoff {
255public:
256 Handoff() = default;
257 Handoff(const Handoff&) = delete;
258 Handoff& operator=(const Handoff&) = delete;
259
262 delete pending_.exchange(nullptr, std::memory_order_acquire);
263 while (T* raw = pop_retired_raw()) delete raw;
264 }
265
271 bool publish(std::unique_ptr<T> next) {
272 if (!next) return false;
273 // Release ordering so the value's contents happen-before the audio
274 // thread's acquire in try_consume().
275 T* raw = next.release();
276 delete pending_.exchange(raw, std::memory_order_acq_rel);
277 return true;
278 }
279
284 [[nodiscard]] std::unique_ptr<T> try_consume() noexcept {
285 return std::unique_ptr<T>(pending_.exchange(nullptr, std::memory_order_acquire));
286 }
287
297 [[nodiscard]] bool retire(std::unique_ptr<T>& displaced) noexcept {
298 if (!displaced) return true; // nothing to park
299 if (!retired_.try_push(displaced.get())) return false;
300 (void)displaced.release(); // ownership transferred to the ring
301 return true;
302 }
303
307 bool has_retire_capacity(std::size_t n = 1) const noexcept {
308 return retired_.size_approx() + n <= RetireCapacity;
309 }
310
312 std::size_t drain_retired() {
313 std::size_t freed = 0;
314 while (T* raw = pop_retired_raw()) {
315 std::unique_ptr<T> owned(raw);
316 ++freed;
317 }
318 return freed;
319 }
320
323 std::unique_ptr<T> drain_retired_one() { return std::unique_ptr<T>(pop_retired_raw()); }
324
325 bool has_pending() const noexcept {
326 return pending_.load(std::memory_order_acquire) != nullptr;
327 }
328 bool has_retired() const noexcept { return retired_.size_approx() > 0; }
329
330 static constexpr std::size_t retire_capacity() noexcept { return RetireCapacity; }
331
332private:
333 T* pop_retired_raw() {
334 auto popped = retired_.try_pop();
335 return popped ? *popped : nullptr;
336 }
337
338 // Producer -> consumer: latest value awaiting pickup.
339 std::atomic<T*> pending_{nullptr};
340 // Consumer -> producer: displaced values awaiting off-thread deletion.
341 // A ring rather than a single slot so rapid swaps survive a missed drain.
342 SpscQueue<T*, RetireCapacity> retired_;
343};
344
345} // namespace pulp::runtime
Handoff(const Handoff &)=delete
std::unique_ptr< T > drain_retired_one()
Producer / worker thread.
Definition slot.hpp:323
bool retire(std::unique_ptr< T > &displaced) noexcept
Audio-thread-callable.
Definition slot.hpp:297
bool has_retired() const noexcept
Definition slot.hpp:328
bool has_retire_capacity(std::size_t n=1) const noexcept
Room in the retire ring for n more values.
Definition slot.hpp:307
~Handoff()
Reclaims anything still parked. Both threads must have stopped.
Definition slot.hpp:261
bool has_pending() const noexcept
Definition slot.hpp:325
std::size_t drain_retired()
Producer / worker thread. Free every parked value. Returns the count.
Definition slot.hpp:312
Handoff & operator=(const Handoff &)=delete
static constexpr std::size_t retire_capacity() noexcept
Definition slot.hpp:330
bool publish(std::unique_ptr< T > next)
Producer thread.
Definition slot.hpp:271
std::unique_ptr< T > try_consume() noexcept
Audio-thread-callable.
Definition slot.hpp:284
~ReadGuard()
Definition slot.hpp:105
T * operator->() const noexcept
Definition slot.hpp:112
T * get() const noexcept
Definition slot.hpp:110
ReadGuard() noexcept=default
friend class Slot
Definition slot.hpp:116
T & operator*() const noexcept
Definition slot.hpp:111
void publish(std::unique_ptr< T > next)
Definition slot.hpp:179
std::size_t retired_count() const noexcept
Number of displaced values still awaiting reclamation.
Definition slot.hpp:210
void prepare_publish()
Publisher thread.
Definition slot.hpp:154
void reclaim_if_quiescent()
Publisher thread.
Definition slot.hpp:191
void unpublish()
Publisher thread.
Definition slot.hpp:186
ReadGuard read() const noexcept
Audio-thread-callable.
Definition slot.hpp:135
void wait_and_clear()
Publisher thread, teardown only.
Definition slot.hpp:198
Slot(const Slot &)=delete
const std::shared_ptr< T > & live() const noexcept
Publisher thread only.
Definition slot.hpp:207
bool has_value() const noexcept
True once something has been published.
Definition slot.hpp:213
Slot & operator=(const Slot &)=delete
void publish_prepared(std::shared_ptr< T > next) noexcept
Publish after prepare_publish().
Definition slot.hpp:162
void publish(std::shared_ptr< T > next)
Definition slot.hpp:172
~Slot()
Blocks until every reader has left, then frees everything.
Definition slot.hpp:129
Definition generated_event_source.hpp:33
@ other
Definition udev_monitor.hpp:26