| 653 | } |
| 654 | chunk.isLastChunk = isLast; |
| 655 | |
| 656 | _streaming.decodeOffsetBytes.store(offset + gotBytes, std::memory_order_release); |
| 657 | if (isLast) |
| 658 | { |
| 659 | _streaming.eofReached.store(true, std::memory_order_release); |
| 660 | } |
| 661 | _streaming.queue.Push(std::move(chunk)); |
| 662 | } |
| 663 | |
| 664 | void WaveOAL::KickAsyncLoad() |
| 665 | { |
| 666 | // Phase 3d: dispatch DecodePcm to a TaskPool worker if the wave |
| 667 | // is large enough to benefit from async decode, otherwise decode |
| 668 | // synchronously on the calling (main) thread. Idempotent — |
| 669 | // repeated calls while a decode is in flight are no-ops via CAS. |
| 670 | // |
| 671 | // The threshold matches the Phase A baseline shape: music tracks |
| 672 | // (>=10 MB PCM) dominate the main-thread hitch and reliably want |
| 673 | // async; voice lines and SFX (<1 MB) decode in 1-10 ms each and |
| 674 | // would suffer a 16-33 ms playback delay if forced through the |
| 675 | // worker round-trip. 1 MB cleanly separates the two populations. |
| 676 | constexpr unsigned int kAsyncThresholdBytes = 1u * 1024u * 1024u; |
| 677 | |
| 678 | if (_loaded || _loadError) |
| 679 | { |
| 680 | return; |
| 681 | } |
| 682 | |
| 683 | int expected = static_cast<int>(LoadState::NotStarted); |
| 684 | if (!_loadState.compare_exchange_strong(expected, static_cast<int>(LoadState::DecodePending), |
| 685 | std::memory_order_acq_rel)) |
| 686 | { |
| 687 | // Decode already in flight, or wave has finished loading / |
| 688 | // hit a failure. Either way, nothing to do here. |
| 689 | return; |
| 690 | } |
| 691 | |
| 692 | auto* pool = ::Poseidon::GetGlobalTaskPool(); |
| 693 | if (_state.size < kAsyncThresholdBytes || !pool) |
| 694 | { |
| 695 | // Small asset or no worker pool (server / tools / very early |
| 696 | // startup) — decode synchronously. DecodePcm sets the state |
| 697 | // to UploadPending on success, leaving us at the same place |
| 698 | // as the async path's worker completion. |
| 699 | LOG_DEBUG(Audio, "WaveOAL::KickAsyncLoad sync-fallback: {} ({} bytes, pool={})", |
| 700 | static_cast<const char*>(Name()), _state.size, pool ? "live" : "null"); |
| 701 | DecodePcm(); |
| 702 | return; |
| 703 | } |
| 704 | |
| 705 | // Async dispatch. AddRef keeps the wave alive across the decode |
| 706 | // — TaskPool::Submit takes the lambda by value, so the captured |
| 707 | // `this` plus the explicit refcount bump guarantees the object |
| 708 | // outlives the worker even if every other holder drops their Ref |
| 709 | // before decode completes. |
| 710 | LOG_DEBUG(Audio, "WaveOAL::KickAsyncLoad async-dispatch: {} ({} bytes)", |
| 711 | static_cast<const char*>(Name()), _state.size); |
| 712 | AddRef(); |
nothing calls this directly
no test coverage detected