From 9bdc01abfb9d12c66ec786a70f8448dd6fc6c133 Mon Sep 17 00:00:00 2001 From: "Stephen F. Booth" Date: Mon, 1 Dec 2025 08:42:21 -0600 Subject: [PATCH 1/6] Make `jthread` support conditional on C++ `201911L` --- .../CSFBAudioEngine/Player/AudioPlayerNode.h | 24 ++++++++++ .../CSFBAudioEngine/Player/AudioPlayerNode.mm | 45 +++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h index 4ca7eaa15..6498a0f74 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h @@ -10,7 +10,9 @@ #import #import #import +#if __has_include() #import +#endif /* __has_include() */ #import #import @@ -82,12 +84,20 @@ class AudioPlayerNode final { mutable CXXUnfairLock::UnfairLock queueLock_; /// Thread used for decoding +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L std::jthread decodingThread_; +#else + std::thread decodingThread_; +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ /// Dispatch semaphore used for communication with the decoding thread dispatch_semaphore_t decodingSemaphore_ {}; /// Thread used for event processing +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L std::jthread eventThread_; +#else + std::thread eventThread_; +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ /// Dispatch semaphore used for communication with the event processing thread dispatch_semaphore_t eventSemaphore_ {}; @@ -214,13 +224,23 @@ class AudioPlayerNode final { unmuteAfterDequeue = 1u << 3, /// The audio ring buffer requires a non-threadsafe reset ringBufferNeedsReset = 1u << 4, +#if !(defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L) + /// The decoding thread should exit + stopDecodingThread = 1u << 5, + /// The event thread should exit + stopEventThread = 1u << 6, +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ }; // MARK: - Decoding /// Dequeues and processes decoders from the decoder queue /// - note: This is the thread entry point for the decoding thread +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L void ProcessDecoders(std::stop_token stoken) noexcept; +#else + void ProcessDecoders() noexcept; +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ /// Writes an error event to `decodeEventRingBuffer_` and signals `eventSemaphore_` void SubmitDecodingErrorEvent(NSError *error) noexcept; @@ -282,7 +302,11 @@ class AudioPlayerNode final { /// Sequences events from from `decodeEventRingBuffer_` and `renderEventRingBuffer_` for processing in order /// - note: This is the thread entry point for the event thread +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L void SequenceAndProcessEvents(std::stop_token stoken) noexcept; +#else + void SequenceAndProcessEvents() noexcept; +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ /// Processes an event from `decodeEventRingBuffer_` void ProcessDecodingEvent(const DecodingEventHeader& header) noexcept; diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm index d4de8e2c6..a7ba74f7d 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm @@ -324,8 +324,13 @@ bool PerformSeekIfRequired() noexcept // Launch the decoding and event processing threads try { +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L decodingThread_ = std::jthread(std::bind_front(&SFB::AudioPlayerNode::ProcessDecoders, this)); eventThread_ = std::jthread(std::bind_front(&SFB::AudioPlayerNode::SequenceAndProcessEvents, this)); +#else + decodingThread_ = std::thread(&SFB::AudioPlayerNode::ProcessDecoders, this); + eventThread_ = std::thread(&SFB::AudioPlayerNode::SequenceAndProcessEvents, this); +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ } catch(const std::exception& e) { os_log_error(log_, "Unable to create thread: %{public}s", e.what()); throw; @@ -336,6 +341,7 @@ bool PerformSeekIfRequired() noexcept { Stop(); +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L // Register a stop callback for the decoding thread std::stop_callback decodingThreadStopCallback(decodingThread_.get_stop_token(), [this] { dispatch_semaphore_signal(decodingSemaphore_); @@ -343,12 +349,19 @@ bool PerformSeekIfRequired() noexcept // Issue a stop request to the decoding thread and wait for it to exit decodingThread_.request_stop(); +#else + // Stop the decoding thread + flags_.fetch_or(static_cast(Flags::stopDecodingThread), std::memory_order_acq_rel); + dispatch_semaphore_signal(decodingSemaphore_); +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ + try { decodingThread_.join(); } catch(const std::exception& e) { os_log_error(log_, "Unable to join decoding thread: %{public}s", e.what()); } +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L // Register a stop callback for the event processing thread std::stop_callback eventThreadStopCallback(eventThread_.get_stop_token(), [this] { dispatch_semaphore_signal(eventSemaphore_); @@ -356,6 +369,12 @@ bool PerformSeekIfRequired() noexcept // Issue a stop request to the event processing thread and wait for it to exit eventThread_.request_stop(); +#else + // Stop the decoding thread + flags_.fetch_or(static_cast(Flags::stopEventThread), std::memory_order_acq_rel); + dispatch_semaphore_signal(eventSemaphore_); +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ + try { eventThread_.join(); } catch(const std::exception& e) { @@ -678,7 +697,11 @@ bool PerformSeekIfRequired() noexcept // MARK: - Decoding +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L void SFB::AudioPlayerNode::ProcessDecoders(std::stop_token stoken) noexcept +#else +void SFB::AudioPlayerNode::ProcessDecoders() noexcept +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ { pthread_setname_np("AudioPlayerNode.Decoding"); pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); @@ -722,8 +745,14 @@ bool PerformSeekIfRequired() noexcept } // Terminate the thread if requested after processing cancelations - if(stoken.stop_requested()) - break; + if( +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L + stoken.stop_requested() +#else + flags_.load(std::memory_order_acquire) & static_cast(Flags::stopDecodingThread) +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ + ) + break; // Process pending seeks if(decoderState && decoderState->IsSeekPending()) { @@ -1021,14 +1050,24 @@ bool PerformSeekIfRequired() noexcept // MARK: - Event Processing +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L void SFB::AudioPlayerNode::SequenceAndProcessEvents(std::stop_token stoken) noexcept +#else +void SFB::AudioPlayerNode::SequenceAndProcessEvents() noexcept +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ { pthread_setname_np("AudioPlayerNode.Events"); pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); os_log_debug(log_, "Event processing thread starting"); - while(!stoken.stop_requested()) { + while( +#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L + !stoken.stop_requested() +#else + !(flags_.load(std::memory_order_acquire) & static_cast(Flags::stopEventThread)) +#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ + ) { auto decodeEventHeader = decodeEventRingBuffer_.ReadValue(); auto renderEventHeader = renderEventRingBuffer_.ReadValue(); From 37a14158223ab3b8023068f856ba278ec65b45df Mon Sep 17 00:00:00 2001 From: "Stephen F. Booth" Date: Mon, 1 Dec 2025 08:49:36 -0600 Subject: [PATCH 2/6] Fix comment --- Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm index a7ba74f7d..9f55388fb 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.mm @@ -370,7 +370,7 @@ bool PerformSeekIfRequired() noexcept // Issue a stop request to the event processing thread and wait for it to exit eventThread_.request_stop(); #else - // Stop the decoding thread + // Stop the event processing thread flags_.fetch_or(static_cast(Flags::stopEventThread), std::memory_order_acq_rel); dispatch_semaphore_signal(eventSemaphore_); #endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ From 562284a9af813d688d4b63cddefd5e14745cff74 Mon Sep 17 00:00:00 2001 From: "Stephen F. Booth" Date: Mon, 1 Dec 2025 08:53:42 -0600 Subject: [PATCH 3/6] Check `defined(__has_include)` --- Sources/CSFBAudioEngine/Player/AudioPlayerNode.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h index 6498a0f74..3733774e3 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h @@ -10,9 +10,9 @@ #import #import #import -#if __has_include() +#if defined(__has_include) && __has_include() #import -#endif /* __has_include() */ +#endif /* defined(__has_include) && __has_include() */ #import #import From a7b8219ddbce4057b6312991e128d4276bcd0cb6 Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 22 Dec 2025 18:34:04 -0600 Subject: [PATCH 4/6] Remove flag --- Sources/CSFBAudioEngine/Player/AudioPlayerNode.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h index d28160208..a6a24e342 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h +++ b/Sources/CSFBAudioEngine/Player/AudioPlayerNode.h @@ -224,13 +224,11 @@ class AudioPlayerNode final { isMuted = 1u << 2, /// The decoding thread should unmute after the next decoder is dequeued and becomes active unmuteAfterDequeue = 1u << 3, - /// The audio ring buffer requires a non-threadsafe reset - ringBufferNeedsReset = 1u << 4, #if !(defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L) /// The decoding thread should exit - stopDecodingThread = 1u << 5, + stopDecodingThread = 1u << 4, /// The event thread should exit - stopEventThread = 1u << 6, + stopEventThread = 1u << 5, #endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ }; From 10c9b49fe8c4898bd2226e1095fe434ee709394a Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Fri, 9 Jan 2026 09:20:36 -0600 Subject: [PATCH 5/6] Correct merge error --- Sources/CSFBAudioEngine/Player/AudioPlayer.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index a11be8fc8..c589f7788 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -974,7 +974,6 @@ T fetch_update(std::atomic& atom, Func&& func, std::memory_order order = std: #else !(flags_.load(std::memory_order_acquire) & static_cast(Flags::stopDecodingThread)) #endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ - !stoken.stop_requested() ) { // The decoder state being processed DecoderState *decoderState = nullptr; From 78c16c6a9ca6ba86b7700b26b2e659c74ff0f9c4 Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Fri, 9 Jan 2026 09:29:25 -0600 Subject: [PATCH 6/6] Refactor exit checks into a lambda --- Sources/CSFBAudioEngine/Player/AudioPlayer.mm | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm index c589f7788..8902dd23d 100644 --- a/Sources/CSFBAudioEngine/Player/AudioPlayer.mm +++ b/Sources/CSFBAudioEngine/Player/AudioPlayer.mm @@ -968,13 +968,16 @@ T fetch_update(std::atomic& atom, Func&& func, std::memory_order order = std: // Whether there is a mismatch between the rendering format and the next decoder's processing format auto formatMismatch = false; - while( + // Returns true if the decoding thread should exit + const auto stop_requested = [&] { #if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L - !stoken.stop_requested() + return stoken.stop_requested(); #else - !(flags_.load(std::memory_order_acquire) & static_cast(Flags::stopDecodingThread)) + return (flags_.load(std::memory_order_acquire) & static_cast(Flags::stopDecodingThread)); #endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ - ) { + }; + + while(!stop_requested()) { // The decoder state being processed DecoderState *decoderState = nullptr; auto ringBufferStale = false; @@ -1380,13 +1383,16 @@ T fetch_update(std::atomic& atom, Func&& func, std::memory_order order = std: os_log_debug(log_, " event processing thread starting", this); - while( + // Returns true if the event processing thread should exit + const auto stop_requested = [&] { #if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L - !stoken.stop_requested() + return stoken.stop_requested(); #else - !(flags_.load(std::memory_order_acquire) & static_cast(Flags::stopEventThread)) + return (flags_.load(std::memory_order_acquire) & static_cast(Flags::stopEventThread)); #endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */ - ) { + }; + + while(!stop_requested()) { DecodingEventCommand decodingEventCommand; uint64_t decodingEventIdentificationNumber; auto gotDecodingEvent = decodingEvents_.ReadValues(decodingEventCommand, decodingEventIdentificationNumber);