ObzLib lock-free queue
obz::spsc_ring_queue
A fixed-capacity queue for exactly one producer and exactly one consumer.
A Private Lane Between Two Threads
Think of this queue as a small conveyor belt between two rooms. The game thread puts commands onto one end. The audio thread takes commands from the other end. Nobody else touches either side.
That shape is common in games. One thread decides what should happen, and another timing-sensitive thread needs to receive those requests without taking a lock.
struct play_sound {
sound_id id;
};
struct set_music_intensity {
float value;
};
using audio_command = std::variant<play_sound, set_music_intensity>;
obz::spsc_ring_queue<audio_command, 256> audio_commands;
One Producer, One Consumer
The important rule is in the name: single producer, single consumer. If the game thread is the only producer and the audio thread is the only consumer, the queue can stay much simpler than a general thread-safe queue.
// Game thread
audio_commands.try_push(play_sound{sound_id::laser});
audio_commands.try_push(set_music_intensity{0.7f});
// Audio thread
audio_command command;
while (audio_commands.try_pop(command)) {
apply_audio_command(command);
}
The game thread does not wait for the audio thread to be ready. The audio thread drains whatever is available when it reaches the queue.
Why The Queue Can Be Small
The capacity is part of the type, so storage lives inside the queue object. There is no allocation while the game is running, and the threading contract is visible at the call site.
template <typename T, std::size_t Capacity>
class spsc_ring_queue {
public:
static_assert(Capacity > 0, "Capacity must be > 0");
That is the clean-code value: the queue is not trying to be every queue. It is a precise tool for one handoff lane between two threads.