Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_memory_governor.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <limits>
6#include <memory>
7#include <mutex>
8#include <optional>
9#include <type_traits>
10#include <utility>
11
12namespace pulp::audio {
13
15
17public:
19 bool valid() const noexcept { return identity_ != nullptr; }
21 SampleMemoryGovernorEpoch) noexcept = default;
22
23private:
25 friend class SampleMemoryLease;
26
27 explicit SampleMemoryGovernorEpoch(const void* identity) noexcept
28 : identity_(identity) {}
29
30 const void* identity_ = nullptr;
31};
32
33static_assert(std::is_trivially_copyable_v<SampleMemoryGovernorEpoch>);
34
35enum class SampleMemoryCategory : std::uint8_t {
38};
39
41 std::uint64_t capacity_bytes = 0;
42 std::uint64_t current_preload_bytes = 0;
43 std::uint64_t current_page_bytes = 0;
44 std::uint64_t current_total_bytes = 0;
45 std::uint64_t peak_preload_bytes = 0;
46 std::uint64_t peak_page_bytes = 0;
47 std::uint64_t peak_total_bytes = 0;
48 std::uint64_t rejected_preload_count = 0;
49 std::uint64_t rejected_preload_bytes = 0;
50 std::uint64_t rejected_page_count = 0;
51 std::uint64_t rejected_page_bytes = 0;
52 std::uint64_t invalid_request_count = 0;
53};
54
55namespace detail {
56
58 explicit SampleMemoryGovernorState(std::uint64_t capacity) noexcept
59 : stats{.capacity_bytes = capacity} {}
60
61 std::mutex mutex;
64};
65
66inline void saturating_add(std::uint64_t& target, std::uint64_t value) noexcept {
67 target = value > std::numeric_limits<std::uint64_t>::max() - target
68 ? std::numeric_limits<std::uint64_t>::max()
69 : target + value;
70}
71
72} // namespace detail
73
80public:
81 SampleMemoryLease() = default;
84
86 : state_(std::move(other.state_)),
87 category_(other.category_),
88 bytes_(std::exchange(other.bytes_, 0)) {}
89
91 if (this == &other) return *this;
92 reset();
93 state_ = std::move(other.state_);
94 category_ = other.category_;
95 bytes_ = std::exchange(other.bytes_, 0);
96 return *this;
97 }
98
100
101 explicit operator bool() const noexcept { return state_ != nullptr && bytes_ != 0; }
102 SampleMemoryCategory category() const noexcept { return category_; }
103 std::uint64_t bytes() const noexcept { return bytes_; }
104
106 return SampleMemoryGovernorEpoch(state_.get());
107 }
108
109 bool issued_by(const SampleMemoryGovernorHandle& governor) const noexcept;
110
111 void reset() noexcept {
112 if (!state_ || bytes_ == 0) {
113 state_.reset();
114 bytes_ = 0;
115 return;
116 }
117
118 {
119 const std::scoped_lock lock(state_->mutex);
120 auto& stats = state_->stats;
121 auto& category_bytes = category_ == SampleMemoryCategory::Preload
122 ? stats.current_preload_bytes
123 : stats.current_page_bytes;
124 category_bytes -= std::min(category_bytes, bytes_);
125 stats.current_total_bytes -= std::min(stats.current_total_bytes, bytes_);
126 }
127 state_.reset();
128 bytes_ = 0;
129 }
130
131private:
134
135 SampleMemoryLease(std::shared_ptr<detail::SampleMemoryGovernorState> state,
137 std::uint64_t bytes) noexcept
138 : state_(std::move(state)), category_(category), bytes_(bytes) {}
139
140 std::shared_ptr<detail::SampleMemoryGovernorState> state_;
142 std::uint64_t bytes_ = 0;
143};
144
152
162
166public:
168
169 explicit operator bool() const noexcept { return prepared(); }
170 bool prepared() const noexcept {
171 if (!state_) return false;
172 const std::scoped_lock lock(state_->mutex);
173 return state_->accepting_reservations;
174 }
175
177 std::uint64_t bytes) const noexcept {
178 if (!state_) return {SampleMemoryReserveStatus::NotPrepared, {}};
179
180 const std::scoped_lock lock(state_->mutex);
181 auto& stats = state_->stats;
182 if (!state_->accepting_reservations)
184 if (category != SampleMemoryCategory::Preload &&
185 category != SampleMemoryCategory::Page) {
186 detail::saturating_add(stats.invalid_request_count, 1);
188 }
189 if (bytes == 0) {
190 detail::saturating_add(stats.invalid_request_count, 1);
192 }
193 if (stats.current_total_bytes > stats.capacity_bytes ||
194 bytes > stats.capacity_bytes - stats.current_total_bytes) {
195 auto& rejected_count = category == SampleMemoryCategory::Preload
196 ? stats.rejected_preload_count
197 : stats.rejected_page_count;
198 auto& rejected_bytes = category == SampleMemoryCategory::Preload
199 ? stats.rejected_preload_bytes
200 : stats.rejected_page_bytes;
201 detail::saturating_add(rejected_count, 1);
202 detail::saturating_add(rejected_bytes, bytes);
204 }
205
206 auto& category_bytes = category == SampleMemoryCategory::Preload
207 ? stats.current_preload_bytes
208 : stats.current_page_bytes;
209 auto& category_peak = category == SampleMemoryCategory::Preload
210 ? stats.peak_preload_bytes
211 : stats.peak_page_bytes;
212 category_bytes += bytes;
213 stats.current_total_bytes += bytes;
214 category_peak = std::max(category_peak, category_bytes);
215 stats.peak_total_bytes = std::max(stats.peak_total_bytes,
216 stats.current_total_bytes);
217 return {
219 SampleMemoryLease(state_, category, bytes),
220 };
221 }
222
224 if (!state_) return {};
225 const std::scoped_lock lock(state_->mutex);
226 return state_->stats;
227 }
228
229 bool issued(const SampleMemoryLease& lease) const noexcept {
230 return state_ != nullptr && lease.state_ == state_;
231 }
232
234 return SampleMemoryGovernorEpoch(state_.get());
235 }
236
237private:
239
241 std::shared_ptr<detail::SampleMemoryGovernorState> state) noexcept
242 : state_(std::move(state)) {}
243
244 std::shared_ptr<detail::SampleMemoryGovernorState> state_;
245};
246
248 const SampleMemoryGovernorHandle& governor) const noexcept {
249 return governor.issued(*this);
250}
251
254inline std::optional<std::uint64_t> checked_sample_storage_bytes(
255 std::uint32_t channels,
256 std::uint64_t frames_per_buffer,
257 std::uint32_t buffer_count = 1,
258 std::uint32_t bytes_per_sample = sizeof(float)) noexcept {
259 if (channels == 0 || frames_per_buffer == 0 || buffer_count == 0 ||
260 bytes_per_sample == 0) {
261 return std::nullopt;
262 }
263
264 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max();
265 std::uint64_t bytes = channels;
266 for (const auto factor : {
267 frames_per_buffer,
268 static_cast<std::uint64_t>(buffer_count),
269 static_cast<std::uint64_t>(bytes_per_sample),
270 }) {
271 if (factor > maximum / bytes) return std::nullopt;
272 bytes *= factor;
273 }
274 return bytes;
275}
276
281public:
286 SampleMemoryGovernor& operator=(SampleMemoryGovernor&&) noexcept = delete;
287
288 bool prepare(std::uint64_t capacity_bytes) {
289 if (capacity_bytes == 0) return false;
290 if (state_) {
291 const std::scoped_lock lock(state_->mutex);
292 if (state_->stats.current_total_bytes != 0) return false;
293 }
294 try {
295 auto replacement =
296 std::make_shared<detail::SampleMemoryGovernorState>(capacity_bytes);
297 if (state_) {
298 const std::scoped_lock lock(state_->mutex);
299 if (state_->stats.current_total_bytes != 0) return false;
300 state_->accepting_reservations = false;
301 }
302 state_ = std::move(replacement);
303 return true;
304 } catch (...) {
305 return false;
306 }
307 }
308
310 bool release() noexcept {
311 if (!state_) return true;
312 {
313 const std::scoped_lock lock(state_->mutex);
314 if (state_->stats.current_total_bytes != 0) return false;
315 state_->accepting_reservations = false;
316 }
317 state_.reset();
318 return true;
319 }
320
321 bool prepared() const noexcept { return handle().prepared(); }
322
324 return SampleMemoryGovernorHandle(state_);
325 }
326
328 std::uint64_t bytes) noexcept {
329 return handle().reserve(category, bytes);
330 }
331
333 return handle().stats();
334 }
335
336private:
337 std::shared_ptr<detail::SampleMemoryGovernorState> state_;
338};
339
340} // namespace pulp::audio
Definition sample_memory_governor.hpp:16
bool valid() const noexcept
Definition sample_memory_governor.hpp:19
friend class SampleMemoryLease
Definition sample_memory_governor.hpp:25
friend class SampleMemoryGovernorHandle
Definition sample_memory_governor.hpp:24
friend bool operator==(SampleMemoryGovernorEpoch, SampleMemoryGovernorEpoch) noexcept=default
Copyable lifetime-safe reservation/stats endpoint.
Definition sample_memory_governor.hpp:165
SampleMemoryGovernorStats stats() const noexcept
Definition sample_memory_governor.hpp:223
bool prepared() const noexcept
Definition sample_memory_governor.hpp:170
SampleMemoryReserveResult reserve(SampleMemoryCategory category, std::uint64_t bytes) const noexcept
Definition sample_memory_governor.hpp:176
bool issued(const SampleMemoryLease &lease) const noexcept
Definition sample_memory_governor.hpp:229
friend class SampleMemoryGovernor
Definition sample_memory_governor.hpp:238
SampleMemoryGovernorEpoch epoch() const noexcept
Definition sample_memory_governor.hpp:233
SampleMemoryGovernorHandle handle() const noexcept
Definition sample_memory_governor.hpp:323
bool prepared() const noexcept
Definition sample_memory_governor.hpp:321
SampleMemoryGovernor & operator=(const SampleMemoryGovernor &)=delete
SampleMemoryGovernorStats stats() const noexcept
Definition sample_memory_governor.hpp:332
SampleMemoryGovernor(const SampleMemoryGovernor &)=delete
bool prepare(std::uint64_t capacity_bytes)
Definition sample_memory_governor.hpp:288
SampleMemoryReserveResult reserve(SampleMemoryCategory category, std::uint64_t bytes) noexcept
Definition sample_memory_governor.hpp:327
bool release() noexcept
Drops the facade only when no outstanding leases remain.
Definition sample_memory_governor.hpp:310
SampleMemoryGovernor(SampleMemoryGovernor &&) noexcept=default
Move-only reservation against one shared sampler-memory cap.
Definition sample_memory_governor.hpp:79
~SampleMemoryLease()
Definition sample_memory_governor.hpp:99
SampleMemoryLease(SampleMemoryLease &&other) noexcept
Definition sample_memory_governor.hpp:85
SampleMemoryLease & operator=(SampleMemoryLease &&other) noexcept
Definition sample_memory_governor.hpp:90
SampleMemoryLease & operator=(const SampleMemoryLease &)=delete
std::uint64_t bytes() const noexcept
Definition sample_memory_governor.hpp:103
friend class SampleMemoryGovernorHandle
Definition sample_memory_governor.hpp:133
SampleMemoryCategory category() const noexcept
Definition sample_memory_governor.hpp:102
friend class SampleMemoryGovernor
Definition sample_memory_governor.hpp:132
SampleMemoryLease(const SampleMemoryLease &)=delete
bool issued_by(const SampleMemoryGovernorHandle &governor) const noexcept
Definition sample_memory_governor.hpp:247
SampleMemoryGovernorEpoch epoch() const noexcept
Definition sample_memory_governor.hpp:105
void reset() noexcept
Definition sample_memory_governor.hpp:111
Definition impulse_response.hpp:69
void saturating_add(std::uint64_t &target, std::uint64_t value) noexcept
Definition sample_memory_governor.hpp:66
Definition analyzer_provider.hpp:12
SampleMemoryCategory
Definition sample_memory_governor.hpp:35
@ Preload
Definition sample_memory_governor.hpp:36
@ Page
Definition sample_memory_governor.hpp:37
@ NotPrepared
Definition sample_heritage_bus_dsp.hpp:21
std::optional< std::uint64_t > checked_sample_storage_bytes(std::uint32_t channels, std::uint64_t frames_per_buffer, std::uint32_t buffer_count=1, std::uint32_t bytes_per_sample=sizeof(float)) noexcept
Checked planar sample-storage formula shared by preload and page admission.
Definition sample_memory_governor.hpp:254
SampleMemoryReserveStatus
Definition sample_memory_governor.hpp:145
@ Acquired
Definition sample_memory_governor.hpp:146
@ InvalidByteCount
Definition sample_memory_governor.hpp:148
@ InvalidCategory
Definition sample_memory_governor.hpp:149
@ NotPrepared
Definition sample_memory_governor.hpp:147
@ BudgetExceeded
Definition sample_memory_governor.hpp:150
constexpr std::size_t bytes_per_sample(SampleFormat format) noexcept
Bytes per sample for format. Returns 0 for unknown values.
Definition sample_converter.hpp:61
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
STL namespace.
Definition sample_memory_governor.hpp:40
std::uint64_t current_total_bytes
Definition sample_memory_governor.hpp:44
std::uint64_t current_page_bytes
Definition sample_memory_governor.hpp:43
std::uint64_t peak_page_bytes
Definition sample_memory_governor.hpp:46
std::uint64_t peak_total_bytes
Definition sample_memory_governor.hpp:47
std::uint64_t peak_preload_bytes
Definition sample_memory_governor.hpp:45
std::uint64_t rejected_preload_count
Definition sample_memory_governor.hpp:48
std::uint64_t rejected_page_count
Definition sample_memory_governor.hpp:50
std::uint64_t rejected_preload_bytes
Definition sample_memory_governor.hpp:49
std::uint64_t current_preload_bytes
Definition sample_memory_governor.hpp:42
std::uint64_t rejected_page_bytes
Definition sample_memory_governor.hpp:51
std::uint64_t invalid_request_count
Definition sample_memory_governor.hpp:52
std::uint64_t capacity_bytes
Definition sample_memory_governor.hpp:41
Definition sample_memory_governor.hpp:153
SampleMemoryLease lease
Definition sample_memory_governor.hpp:155
bool acquired() const noexcept
Definition sample_memory_governor.hpp:157
SampleMemoryReserveStatus status
Definition sample_memory_governor.hpp:154
SampleMemoryGovernorState(std::uint64_t capacity) noexcept
Definition sample_memory_governor.hpp:58
bool accepting_reservations
Definition sample_memory_governor.hpp:63
std::mutex mutex
Definition sample_memory_governor.hpp:61
SampleMemoryGovernorStats stats
Definition sample_memory_governor.hpp:62