| 182 | } |
| 183 | |
| 184 | void WaveOAL::DecodePcm() |
| 185 | { |
| 186 | // Worker-thread-safe (Phase 3d): no AL calls, only stream reads |
| 187 | // into `_pendingPcm`. Touches `_stream`, `_state.size`, `_loadError`, |
| 188 | // and `_loadState` — none of which the main thread mutates while |
| 189 | // a decode is in flight (Phase 3d enforces that contract via the |
| 190 | // state machine: DecodePending is the only state where the worker |
| 191 | // may touch the wave, and the main thread never writes _stream |
| 192 | // or _pendingPcm in that window). |
| 193 | if (!_stream || _loaded || _loadError) |
| 194 | { |
| 195 | return; |
| 196 | } |
| 197 | |
| 198 | const auto _perfDecodeStart = ::Poseidon::Dev::Perf::Now(); |
| 199 | |
| 200 | _loadState.store(static_cast<int>(LoadState::DecodePending), std::memory_order_release); |
| 201 | |
| 202 | // PCM cache (asset-loader plan Phase B): a manifest-preloaded or |
| 203 | // previously-decoded wave skips the stream read + decode entirely. |
| 204 | // Streamed waves (OGG) never participate — they consume the stream |
| 205 | // chunk-wise and the full-PCM blob would be music-sized. |
| 206 | if (!_isStreamed && _sys) |
| 207 | { |
| 208 | if (auto hit = _sys->_pcmCache.Find(static_cast<const char*>(Name()))) |
| 209 | { |
| 210 | _pendingPcm.assign(hit->pcm.begin(), hit->pcm.end()); |
| 211 | _loadState.store(static_cast<int>(LoadState::UploadPending), std::memory_order_release); |
| 212 | ::Poseidon::Dev::Perf::EmitTraceEventAssetNum(Poseidon::Foundation::LogCategory::Audio, |
| 213 | "WaveOAL::DecodeCacheHit", _perfDecodeStart, |
| 214 | static_cast<const char*>(Name()), "bytes", |
| 215 | static_cast<long long>(_state.size)); |
| 216 | return; |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | _pendingPcm.assign(static_cast<size_t>(_state.size), 0); |
| 221 | _stream->GetData(_pendingPcm.data(), 0, static_cast<int>(_state.size)); |
| 222 | |
| 223 | if (!_isStreamed && _sys && _state.size <= Audio::PcmCache::kMaxEntryBytes) |
| 224 | { |
| 225 | auto entry = std::make_shared<Audio::PcmCache::Entry>(); |
| 226 | _stream->GetFormat(entry->fmt); |
| 227 | entry->uncompressedSize = _state.size; |
| 228 | entry->pcm = _pendingPcm; |
| 229 | _sys->_pcmCache.Insert(static_cast<const char*>(Name()), std::move(entry)); |
| 230 | } |
| 231 | |
| 232 | _loadState.store(static_cast<int>(LoadState::UploadPending), std::memory_order_release); |
| 233 | |
| 234 | // PERF event tags which thread ran the decode — the validation |
| 235 | // story for Phase 3d is "Decode runs on tid != 1 for large |
| 236 | // assets". Without this emission the trace can't tell us |
| 237 | // whether async dispatch actually engaged. |
| 238 | ::Poseidon::Dev::Perf::EmitTraceEventAssetNum(Poseidon::Foundation::LogCategory::Audio, "WaveOAL::Decode", _perfDecodeStart, |
| 239 | static_cast<const char*>(Name()), "bytes", |
| 240 | static_cast<long long>(_state.size)); |
| 241 | } |