Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9bdc01a
Make `jthread` support conditional on C++ `201911L`
sbooth Dec 1, 2025
37a1415
Fix comment
sbooth Dec 1, 2025
562284a
Check `defined(__has_include)`
sbooth Dec 1, 2025
1e3230d
Merge branch 'main' into thread
sbooth Dec 8, 2025
860b45c
Merge branch 'main' into thread
sbooth Dec 10, 2025
804ab49
Merge branch 'main' into thread
sbooth Dec 17, 2025
aff4183
Merge branch 'main' into thread
sbooth Dec 23, 2025
a7b8219
Remove flag
sbooth Dec 23, 2025
c0d92ad
Merge branch 'main' into thread
sbooth Dec 29, 2025
76b836f
Merge branch 'main' into thread
sbooth Jan 9, 2026
10c9b49
Correct merge error
sbooth Jan 9, 2026
78c16c6
Refactor exit checks into a lambda
sbooth Jan 9, 2026
4ff622e
Merge branch 'main' into thread
sbooth Jan 16, 2026
2d22795
Merge branch 'main' into thread
sbooth Jan 18, 2026
db00e61
Add closing condition comment
sbooth Jan 18, 2026
632a7b2
Merge branch 'main' into thread
sbooth Jan 22, 2026
daca92c
Remove erroneous commit
sbooth Jan 22, 2026
b1ccf5c
Reformat
sbooth Jan 22, 2026
ada089e
Merge branch 'main' into thread
sbooth Jan 23, 2026
8e54b6e
Merge branch 'main' into thread
sbooth Feb 3, 2026
668089c
Merge branch 'main' into thread
sbooth Feb 6, 2026
4bb98c6
Merge branch 'main' into thread
sbooth Feb 7, 2026
776376b
Merge branch 'main' into thread
sbooth Feb 11, 2026
b379250
Merge branch 'main' into thread
sbooth Feb 13, 2026
62d7fb1
Merge branch 'main' into thread
sbooth Feb 13, 2026
babe23a
Merge branch 'main' into thread
sbooth Feb 13, 2026
d082762
Merge branch 'main' into thread
sbooth Feb 13, 2026
56313d1
Merge branch 'main' into thread
sbooth Feb 28, 2026
25ea081
Merge branch 'main' into thread
sbooth Mar 22, 2026
6e9a9d3
Merge branch 'main' into thread
sbooth May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Sources/CSFBAudioEngine/Player/AudioPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
#import <deque>
#import <memory>
#import <mutex>
#if defined(__has_include) && __has_include(<stop_token>)
#import <stop_token>
#endif /* defined(__has_include) && __has_include(<stop_token>) */
#import <thread>
#import <vector>

Expand Down Expand Up @@ -67,12 +69,20 @@ class AudioPlayer final {
mutable mtx::UnfairMutex queuedDecodersMutex_;

/// 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
dsema::Semaphore decodingSemaphore_{0};

/// 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
dsema::Semaphore eventSemaphore_{0};

Expand Down Expand Up @@ -209,6 +219,12 @@ class AudioPlayer final {
isMuted = 1u << 2,
/// The ring buffer needs to be drained during the next render cycle
drainRequired = 1u << 3,
#if !(defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L)
/// The decoding thread should exit
stopDecodingThread = 1u << 4,
/// The event thread should exit
stopEventThread = 1u << 5,
#endif /* !(defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L) */
};

// Enable bitmask operations for `Flags`
Expand Down Expand Up @@ -242,7 +258,11 @@ class AudioPlayer final {

/// 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 `decodingEvents_` and signals `eventSemaphore_`
void submitDecodingErrorEvent(NSError *error) noexcept;
Expand Down Expand Up @@ -277,7 +297,11 @@ class AudioPlayer final {

/// Reads and sequences event headers from `decodingEvents_` and `renderingEvents_` for processing in order
/// - note: This is the thread entry point for the event processing 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 */

/// Reads and processes an event payload from `decodingEvents_`
bool processDecodingEvent(DecodingEventCommand command) noexcept;
Expand Down
55 changes: 51 additions & 4 deletions Sources/CSFBAudioEngine/Player/AudioPlayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,13 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re

// Launch the decoding and event processing threads
try {
#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
decodingThread_ = std::jthread(std::bind_front(&sfb::AudioPlayer::processDecoders, this));
eventThread_ = std::jthread(std::bind_front(&sfb::AudioPlayer::sequenceAndProcessEvents, this));
#else
decodingThread_ = std::thread(&sfb::AudioPlayer::processDecoders, this);
eventThread_ = std::thread(&sfb::AudioPlayer::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;
Expand Down Expand Up @@ -557,23 +562,37 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re
clearDecoderQueue();
cancelActiveDecoders();

#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] { decodingSemaphore_.signal(); });

// 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<unsigned int>(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] { eventSemaphore_.signal(); });

// Issue a stop request to the event processing thread and wait for it to exit
eventThread_.request_stop();
#else
// Stop the event processing thread
flags_.fetch_or(static_cast<unsigned int>(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) {
Expand Down Expand Up @@ -1151,7 +1170,12 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re

// MARK: - Decoding

void sfb::AudioPlayer::processDecoders(std::stop_token stoken) noexcept {
#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
void sfb::AudioPlayer::processDecoders(std::stop_token stoken) noexcept
#else
void sfb::AudioPlayer::processDecoders() noexcept
#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */
{
pthread_setname_np("AudioPlayer.Decoding");
pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0);

Expand All @@ -1162,7 +1186,16 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re
// Whether there is a mismatch between the rendering format and the next decoder's processing format
auto formatMismatch = false;

while (!stoken.stop_requested()) {
// Returns true if the decoding thread should exit
const auto stop_requested = [&] {
#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
return stoken.stop_requested();
#else
return (flags_.load(std::memory_order_acquire) & static_cast<unsigned int>(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;
Expand Down Expand Up @@ -1629,13 +1662,27 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re

// MARK: - Event Processing

void sfb::AudioPlayer::sequenceAndProcessEvents(std::stop_token stoken) noexcept {
#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
void sfb::AudioPlayer::sequenceAndProcessEvents(std::stop_token stoken) noexcept
#else
void sfb::AudioPlayer::sequenceAndProcessEvents() noexcept
#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */
{
pthread_setname_np("AudioPlayer.Events");
pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0);

os_log_debug(log_, "<AudioPlayer: %p> event processing thread starting", this);

while (!stoken.stop_requested()) {
// Returns true if the event processing thread should exit
const auto stop_requested = [&] {
#if defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L
return stoken.stop_requested();
#else
return (flags_.load(std::memory_order_acquire) & static_cast<unsigned int>(Flags::stopEventThread));
#endif /* defined(__cpp_lib_jthread) && __cpp_lib_jthread >= 201911L */
};

while (!stop_requested()) {
DecodingEventCommand decodingEventCommand;
uint64_t decodingEventIdentificationNumber;
auto gotDecodingEvent = decodingEvents_.readAll(decodingEventCommand, decodingEventIdentificationNumber);
Expand Down