Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
state_restore_gate.hpp
Go to the documentation of this file.
1#pragma once
2
38
39#include <pulp/audio/buffer.hpp>
40
41#include <algorithm>
42#include <cstdint>
43#include <atomic>
44#include <mutex>
45#include <thread>
46
47namespace pulp::format {
48
53template <typename Sample>
55 const std::size_t frames = out.num_samples();
56 for (std::size_t ch = 0; ch < out.num_channels(); ++ch) {
57 auto o = out.channel(ch);
58 for (std::size_t n = 0; n < frames; ++n) o[n] = Sample{0};
59 }
60}
61
71template <typename Sample>
74 const std::size_t channels = std::min(out.num_channels(), in.num_channels());
75 const std::size_t frames = out.num_samples();
76 for (std::size_t ch = 0; ch < channels; ++ch) {
77 auto o = out.channel(ch);
78 auto i = in.channel(ch);
79 for (std::size_t n = 0; n < frames; ++n) o[n] = i[n];
80 }
81 for (std::size_t ch = channels; ch < out.num_channels(); ++ch) {
82 auto o = out.channel(ch);
83 for (std::size_t n = 0; n < frames; ++n) o[n] = Sample{0};
84 }
85}
86
88public:
91 class [[nodiscard]] RenderLock {
92 public:
93 RenderLock(const RenderLock&) = delete;
94 RenderLock& operator=(const RenderLock&) = delete;
96 if (!gate_)
97 return;
98 const std::uint32_t prev = gate_->state_.fetch_sub(1, std::memory_order_acq_rel);
99 // Only a render that finishes while a restore waits pays for the
100 // wake-up; an uncontended block is one atomic decrement.
101 if ((prev & kRestoring) != 0)
102 gate_->state_.notify_all();
103 }
104
105 explicit operator bool() const noexcept {
106 return gate_ != nullptr;
107 }
108
109 private:
110 friend class StateRestoreGate;
111 explicit RenderLock(StateRestoreGate* gate) noexcept : gate_(gate) {}
112 StateRestoreGate* gate_;
113 };
114
117 class [[nodiscard]] RestoreLock {
118 public:
119 RestoreLock(const RestoreLock&) = delete;
122 gate_.state_.fetch_and(~kRestoring, std::memory_order_release);
123 }
124
125 private:
126 friend class StateRestoreGate;
127 RestoreLock(StateRestoreGate& gate, std::unique_lock<std::mutex> writer) noexcept
128 : gate_(gate), writer_(std::move(writer)) {}
129 StateRestoreGate& gate_;
130 // Released after the flag is cleared (members destroy after the body),
131 // so a second restorer can never observe a half-released gate.
132 std::unique_lock<std::mutex> writer_;
133 };
134
139 const std::uint32_t prev = state_.fetch_add(1, std::memory_order_acquire);
140 if ((prev & kRestoring) != 0) {
141 state_.fetch_sub(1, std::memory_order_relaxed);
142 contended_blocks_.fetch_add(1, std::memory_order_relaxed);
143 return RenderLock(nullptr);
144 }
145 return RenderLock(this);
146 }
147
153 std::unique_lock<std::mutex> writer(writer_mutex_);
154 std::uint32_t current = state_.fetch_or(kRestoring, std::memory_order_acq_rel) | kRestoring;
155 // A render in flight usually finishes within microseconds, so spin
156 // briefly; after that, sleep until a finishing render wakes us rather
157 // than burning a core for the rest of a long block.
158 for (int spin = 0; spin < kSpinsBeforeWait && (current & kRenderMask) != 0; ++spin) {
159 std::this_thread::yield();
160 current = state_.load(std::memory_order_acquire);
161 }
162 while ((current & kRenderMask) != 0) {
163 state_.wait(current, std::memory_order_acquire);
164 current = state_.load(std::memory_order_acquire);
165 }
166 return RestoreLock(*this, std::move(writer));
167 }
168
172 std::uint64_t contended_blocks() const noexcept {
173 return contended_blocks_.load(std::memory_order_relaxed);
174 }
175
176 private:
177 static constexpr std::uint32_t kRestoring = 0x8000'0000u;
178 static constexpr std::uint32_t kRenderMask = ~kRestoring;
179 static constexpr int kSpinsBeforeWait = 64;
180 static_assert(std::atomic<std::uint32_t>::is_always_lock_free,
181 "the render side of the gate must be lock-free");
182
183 // High bit: a restore holds (or is acquiring) the gate. Low bits: renders
184 // currently inside process().
185 std::atomic<std::uint32_t> state_{0};
186 std::mutex writer_mutex_;
187 std::atomic<std::uint64_t> contended_blocks_{0};
188};
189
190} // namespace pulp::format
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
std::size_t num_samples() const
Definition buffer.hpp:84
std::size_t num_channels() const
Definition buffer.hpp:83
std::span< SampleType > channel(std::size_t index)
Get a span over all samples in one channel.
Definition buffer.hpp:58
Audio-thread pin.
Definition state_restore_gate.hpp:91
~RenderLock()
Definition state_restore_gate.hpp:95
friend class StateRestoreGate
Definition state_restore_gate.hpp:110
RenderLock & operator=(const RenderLock &)=delete
RenderLock(const RenderLock &)=delete
Control-thread hold.
Definition state_restore_gate.hpp:117
RestoreLock & operator=(const RestoreLock &)=delete
RestoreLock(const RestoreLock &)=delete
~RestoreLock()
Definition state_restore_gate.hpp:121
friend class StateRestoreGate
Definition state_restore_gate.hpp:126
Definition state_restore_gate.hpp:87
std::uint64_t contended_blocks() const noexcept
Blocks that rendered silence because a restore held the gate.
Definition state_restore_gate.hpp:172
RestoreLock lock_for_restore()
Control-thread entry.
Definition state_restore_gate.hpp:152
RenderLock lock_for_render() noexcept
Audio-thread entry.
Definition state_restore_gate.hpp:138
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
void silence_block(audio::BufferView< Sample > &out)
Zero an output bus for a block the plug-in did not render.
Definition state_restore_gate.hpp:54
void passthrough_block(audio::BufferView< Sample > &out, const audio::BufferView< const Sample > &in)
Copy input to output for a block the plug-in did not render.
Definition state_restore_gate.hpp:72