126 static_assert(std::is_default_constructible_v<Event> &&
127 std::is_nothrow_copy_assignable_v<Event>,
128 "AsyncBlockWorker events are copied into fixed rings");
145 bool emit(std::uint32_t offset,
const Event& event)
noexcept {
146 return owner_->push_out_event(
start + std::min(offset,
frames ?
frames - 1 : 0u),
169 if (!job ||
config.max_block == 0 ||
config.output_channels == 0 ||
173 if (config_.max_chunk == 0 || config_.max_chunk > config_.max_block)
174 config_.max_chunk = config_.max_block;
175 job_ = std::move(job);
181 const std::uint64_t needed =
182 (
static_cast<std::uint64_t
>(config_.max_latency_blocks) + 4) * config_.max_block;
184 while (capacity_ < needed)
186 input_.assign(
static_cast<std::size_t
>(config_.input_channels) * capacity_, 0.0f);
187 output_.assign(
static_cast<std::size_t
>(config_.output_channels) * capacity_, 0.0f);
188 input_ptrs_.assign(config_.input_channels,
nullptr);
189 output_ptrs_.assign(config_.output_channels,
nullptr);
190 events_in_.assign(round_up(config_.max_events_in), Stamped{});
191 events_out_.assign(round_up(config_.max_events_out), Stamped{});
192 chunk_events_.assign(events_in_.size(),
TimedEvent{});
194 latency_blocks_ = config_.latency_blocks;
195 requested_latency_blocks_.store(config_.latency_blocks, std::memory_order_relaxed);
196 worker_latency_.store(config_.worker_latency_samples, std::memory_order_relaxed);
197 applied_worker_latency_ = config_.worker_latency_samples;
200 if (config_.run_worker_thread) {
201 stop_.store(
false, std::memory_order_relaxed);
202 thread_ = std::thread([
this] { run(); });
209 if (thread_.joinable()) {
210 stop_.store(
true, std::memory_order_release);
227 return requested_latency_blocks_.load(std::memory_order_acquire) * config_.max_block +
228 worker_latency_.load(std::memory_order_acquire);
234 if (blocks > config_.max_latency_blocks)
236 requested_latency_blocks_.store(blocks, std::memory_order_release);
242 worker_latency_.store(samples, std::memory_order_release);
248 listener_ = listener;
249 listener_context_ = context;
256 template <
typename Emit>
258 std::uint32_t frames, std::span<const TimedEvent>
events, Emit&& emit,
260 for (std::size_t c = 0; c < output.num_channels(); ++c)
261 std::fill_n(output.channel_ptr(c), std::min<std::size_t>(frames, output.num_samples()),
263 if (!prepared_ || frames == 0 || frames > config_.max_block)
265 apply_latency_changes();
268 const std::uint64_t submitted = submitted_.load(std::memory_order_relaxed);
269 const std::uint64_t produced_now = produced_.load(std::memory_order_acquire);
270 if (submitted + frames - produced_now > capacity_ - config_.max_block) {
273 skip_to_.store(submitted, std::memory_order_release);
275 for (std::uint32_t c = 0; c < config_.input_channels; ++c) {
276 float* ring = input_.data() +
static_cast<std::size_t
>(c) * capacity_;
277 const float* source = c < input.num_channels() && input.num_samples() >= frames
278 ? input.channel_ptr(c)
280 for (std::uint32_t i = 0; i < frames; ++i)
281 ring[(submitted + i) & (capacity_ - 1)] = source ? source[i] : 0.0f;
283 for (
const auto& timed :
events)
284 push_in_event(submitted + std::min(timed.offset, frames - 1), timed.event);
285 submitted_.store(submitted + frames, std::memory_order_release);
289 const std::int64_t window_start =
290 static_cast<std::int64_t
>(delivered_) -
291 static_cast<std::int64_t
>(latency_blocks_) * config_.max_block;
292 const std::int64_t window_end = window_start + frames;
293 delivered_ += frames;
298 detector_.observe(frames, config_.sample_rate, PullRateDetector::Clock::now());
299 fast_pull_.store(fast, std::memory_order_relaxed);
302 if (wait && window_end > 0)
303 wait_until_produced(
static_cast<std::uint64_t
>(window_end));
305 const std::uint64_t produced = produced_.load(std::memory_order_acquire);
306 const std::uint64_t valid_from = valid_from_.load(std::memory_order_acquire);
307 std::uint64_t missed = 0;
308 for (std::int64_t p = std::max<std::int64_t>(window_start, 0); p < window_end; ++p) {
309 const auto position =
static_cast<std::uint64_t
>(p);
310 const auto target =
static_cast<std::size_t
>(p - window_start);
311 if (position < played_until_)
313 if (position >= produced || position < valid_from) {
317 const std::size_t index = position & (capacity_ - 1);
318 for (std::size_t c = 0; c < output.num_channels() && c < config_.output_channels; ++c)
319 output.channel_ptr(c)[target] = output_[c * capacity_ + index];
322 miss_frames_.fetch_add(missed, std::memory_order_relaxed);
324 drain_out_events(window_start, window_end, emit);
331 std::uint32_t
pump(std::uint32_t max_chunks = 0) noexcept {
334 std::uint32_t chunks = 0;
336 if (max_chunks && chunks >= max_chunks)
338 std::uint64_t produced = produced_.load(std::memory_order_relaxed);
339 const std::uint64_t skip = skip_to_.load(std::memory_order_acquire);
340 if (skip > produced) {
343 overrun_frames_.fetch_add(skip - produced, std::memory_order_relaxed);
344 discard_in_events_before(skip);
345 valid_from_.store(skip, std::memory_order_release);
346 produced_.store(skip, std::memory_order_release);
349 const std::uint64_t submitted = submitted_.load(std::memory_order_acquire);
350 if (submitted <= produced)
352 std::uint64_t frames = std::min<std::uint64_t>(submitted - produced, config_.max_chunk);
353 const std::uint64_t index = produced & (capacity_ - 1);
354 frames = std::min<std::uint64_t>(frames, capacity_ - index);
355 render_chunk(produced,
static_cast<std::uint32_t
>(frames),
356 static_cast<std::size_t
>(index));
357 produced_.store(produced + frames, std::memory_order_release);
358 produced_frames_.fetch_add(frames, std::memory_order_relaxed);
367 s.produced_frames = produced_frames_.load(std::memory_order_relaxed);
368 s.miss_frames = miss_frames_.load(std::memory_order_relaxed);
369 s.overrun_frames = overrun_frames_.load(std::memory_order_relaxed);
370 s.waits = waits_.load(std::memory_order_relaxed);
371 s.wait_timeouts = wait_timeouts_.load(std::memory_order_relaxed);
372 s.events_in_dropped = events_in_dropped_.load(std::memory_order_relaxed);
373 s.events_out_dropped = events_out_dropped_.load(std::memory_order_relaxed);
374 s.late_events = late_events_.load(std::memory_order_relaxed);
375 s.latency_changes = latency_changes_.load(std::memory_order_relaxed);
376 s.fast_pull = fast_pull_.load(std::memory_order_relaxed);
382 std::uint64_t position = 0;
386 static std::size_t round_up(std::uint32_t n) {
387 std::size_t size = 1;
388 while (size < std::max<std::uint32_t>(n, 1))
393 void reset_counters() noexcept {
394 submitted_.store(0, std::memory_order_relaxed);
395 produced_.store(0, std::memory_order_relaxed);
396 skip_to_.store(0, std::memory_order_relaxed);
397 valid_from_.store(0, std::memory_order_relaxed);
400 in_write_.store(0, std::memory_order_relaxed);
401 in_read_.store(0, std::memory_order_relaxed);
402 out_write_.store(0, std::memory_order_relaxed);
403 out_read_.store(0, std::memory_order_relaxed);
405 {&produced_frames_, &miss_frames_, &overrun_frames_, &waits_, &wait_timeouts_,
406 &events_in_dropped_, &events_out_dropped_, &late_events_, &latency_changes_})
407 counter->store(0, std::memory_order_relaxed);
408 fast_pull_.store(
false, std::memory_order_relaxed);
411 void apply_latency_changes() noexcept {
412 const auto blocks = requested_latency_blocks_.load(std::memory_order_acquire);
413 const auto worker = worker_latency_.load(std::memory_order_acquire);
414 if (blocks == latency_blocks_ && worker == applied_worker_latency_)
416 if (blocks > latency_blocks_) {
419 const auto played =
static_cast<std::int64_t
>(delivered_) -
420 static_cast<std::int64_t
>(latency_blocks_) * config_.max_block;
422 played_until_ = std::max(played_until_,
static_cast<std::uint64_t
>(played));
424 latency_blocks_ = blocks;
425 applied_worker_latency_ = worker;
426 latency_changes_.fetch_add(1, std::memory_order_relaxed);
428 listener_(listener_context_);
431 void wait_until_produced(std::uint64_t position)
noexcept {
432 if (produced_.load(std::memory_order_acquire) >= position)
434 waits_.fetch_add(1, std::memory_order_relaxed);
435 if (!config_.run_worker_thread) {
438 const bool done = done_.wait_for(
439 [&] {
return produced_.load(std::memory_order_acquire) >= position; },
440 std::chrono::duration_cast<runtime::WakeSignal::Clock::duration>(
441 config_.wait_deadline));
443 wait_timeouts_.fetch_add(1, std::memory_order_relaxed);
447 void push_in_event(std::uint64_t position,
const Event& event)
noexcept {
448 const auto write = in_write_.load(std::memory_order_relaxed);
449 if (write - in_read_.load(std::memory_order_acquire) >= events_in_.size()) {
450 events_in_dropped_.fetch_add(1, std::memory_order_relaxed);
453 events_in_[
write & (events_in_.size() - 1)] = Stamped{position,
event};
454 in_write_.store(write + 1, std::memory_order_release);
457 void discard_in_events_before(std::uint64_t position)
noexcept {
458 auto read = in_read_.load(std::memory_order_relaxed);
459 const auto write = in_write_.load(std::memory_order_acquire);
460 while (read != write && events_in_[read & (events_in_.size() - 1)].position < position)
462 in_read_.store(read, std::memory_order_release);
465 bool push_out_event(std::uint64_t position,
const Event& event)
noexcept {
466 const auto write = out_write_.load(std::memory_order_relaxed);
467 if (write - out_read_.load(std::memory_order_acquire) >= events_out_.size()) {
468 events_out_dropped_.fetch_add(1, std::memory_order_relaxed);
471 events_out_[
write & (events_out_.size() - 1)] = Stamped{position,
event};
472 out_write_.store(write + 1, std::memory_order_release);
476 template <
typename Emit>
477 void drain_out_events(std::int64_t window_start, std::int64_t window_end, Emit& emit)
noexcept {
478 auto read = out_read_.load(std::memory_order_relaxed);
479 const auto write = out_write_.load(std::memory_order_acquire);
480 while (read != write) {
481 const auto& stamped = events_out_[
read & (events_out_.size() - 1)];
482 const auto position =
static_cast<std::int64_t
>(stamped.position);
483 if (position >= window_end)
485 std::uint32_t offset = 0;
486 if (position >= window_start)
487 offset =
static_cast<std::uint32_t
>(position - window_start);
489 late_events_.fetch_add(1, std::memory_order_relaxed);
490 emit(offset, stamped.event);
493 out_read_.store(read, std::memory_order_release);
496 void render_chunk(std::uint64_t start, std::uint32_t frames, std::size_t index)
noexcept {
497 for (std::uint32_t c = 0;
c < config_.input_channels; ++
c)
498 input_ptrs_[c] = input_.data() +
static_cast<std::size_t
>(
c) * capacity_ + index;
499 for (std::uint32_t c = 0;
c < config_.output_channels; ++
c) {
500 output_ptrs_[
c] = output_.data() +
static_cast<std::size_t
>(
c) * capacity_ + index;
501 std::fill_n(output_ptrs_[c], frames, 0.0f);
504 std::size_t
count = 0;
505 auto read = in_read_.load(std::memory_order_relaxed);
506 const auto write = in_write_.load(std::memory_order_acquire);
507 while (read != write && count < chunk_events_.size()) {
508 const auto& stamped = events_in_[
read & (events_in_.size() - 1)];
509 if (stamped.position >= start + frames)
512 static_cast<std::uint32_t
>(stamped.position >
start ? stamped.position -
start : 0),
516 in_read_.store(read, std::memory_order_release);
521 chunk.frames = frames;
522 chunk.input = BufferView<const float>(input_ptrs_.data(), config_.input_channels, frames);
523 chunk.output = BufferView<float>(output_ptrs_.data(), config_.output_channels, frames);
524 chunk.events = std::span<const TimedEvent>(chunk_events_.data(), count);
528 void run() noexcept {
529 while (!stop_.load(std::memory_order_acquire)) {
532 return stop_.load(std::memory_order_acquire) ||
533 submitted_.load(std::memory_order_acquire) >
534 produced_.load(std::memory_order_relaxed);
536 std::chrono::seconds(1));
537 if (stop_.load(std::memory_order_acquire))
543 AsyncBlockWorkerConfig config_{};
545 bool prepared_ =
false;
546 std::uint64_t capacity_ = 0;
547 std::vector<float> input_;
548 std::vector<float> output_;
549 std::vector<const float*> input_ptrs_;
550 std::vector<float*> output_ptrs_;
551 std::vector<Stamped> events_in_;
552 std::vector<Stamped> events_out_;
553 std::vector<TimedEvent> chunk_events_;
557 std::atomic<std::uint64_t> submitted_{0};
558 std::atomic<std::uint64_t> produced_{0};
559 std::atomic<std::uint64_t> skip_to_{0};
560 std::atomic<std::uint64_t> valid_from_{0};
561 std::uint64_t delivered_ = 0;
562 std::uint64_t played_until_ = 0;
563 std::atomic<std::uint64_t> in_write_{0}, in_read_{0}, out_write_{0}, out_read_{0};
565 std::uint32_t latency_blocks_ = 0;
566 std::uint32_t applied_worker_latency_ = 0;
567 std::atomic<std::uint32_t> requested_latency_blocks_{0};
568 std::atomic<std::uint32_t> worker_latency_{0};
570 void* listener_context_ =
nullptr;
571 PullRateDetector detector_;
573 std::atomic<std::uint64_t> produced_frames_{0}, miss_frames_{0}, overrun_frames_{0}, waits_{0},
574 wait_timeouts_{0}, events_in_dropped_{0}, events_out_dropped_{0}, late_events_{0},
576 std::atomic<bool> fast_pull_{
false};
578 runtime::WakeSignal work_;
579 runtime::WakeSignal done_;
580 std::atomic<bool> stop_{
false};