i hereby declare this stupid library as "deprecated" in favor of the less-stupid: https://github.com/colugomusic/ez
Header-only lock-free synchronization utilities (one writer, many readers). No queues
The base functionality of this library is provided by the classes:
stupid::object<T>stupid::ref<T>
stupid::object<Thing> thing{constructor, args, ...};In the writer thread
thing.write.update([](Thing thing)
{
thing.modify();
thing.the();
thing.object();
return thing;
});In the reader thread
// Get a reference to the most recent version of the object
stupid::ref<Thing> ref = thing.read.acquire();
ref->access();
ref->read_only();
ref->stuff();
// stupid::ref is a reference counted type. The referenced version of
// the object will remain valid as long as there are references to it, even
// if the writer thread commits new versions of the object.
- Multiple simultaneous writer threads are not supported
-
Only these methods allocate memory:
stupid::object::object(...)(the constructor)stupid::write_t::update()
-
Only these methods deallocate memory (in the form of garbage collection of old versions of the object):
stupid::object::~object()stupid::write_t::update()
-
When a
stupid::objectis destroyed, if you are still holding on to any associatedstupid::refs then the last one to be destroyed will also deallocate in the destructor, so if you don't want your reader thread to deallocate then make sure you destroy all yourstupid::refs before destroying the associatedstupid::object.
Some additional, higher-level classes are provided for more specific use cases:
stupid::sync_signalstupid::signal_synced_object<T>stupid::signal_synced_object_pair<T>
The audio callback in this example has the following stipulations:
- May not allocate or deallocate memory
- May not lock a mutex
- Should be able to get a reference to some immutable value by calling some function e.g.
get_value() - Repeated calls to
get_value()within the same invocation of the audio callback must return the same reference - The reference returned from
get_value()must remain valid for the duration of the current invocation of the audio callback
struct
{
stupid::sync_signal signal;
stupid::signal_synced_object<AudioData> data;
Sync() : data(signal) {}
} sync;UI thread
void update_audio_data()
{
sync.data.write.update([](AudioData data)
{
data.modify();
data.the();
data.data();
return data;
});
}Audio thread
void audio_callback(...)
{
// stupid::sync_signal::notify() is called once at the start
// of each audio buffer, and nowhere else.
// This increments its value by 1.
// The signal's value is checked whenever
// stupid::signal_synced_object::read_t::get_value() is called.
sync.signal.notify();
...
// stupid::signal_synced_object::read_t::get_value() is guaranteed to always
// return a reference to the same data unless:
// 1. there is new data available, AND
// 2. the current signal value is greater than the previous
// call to get_value().
// Therefore new data (if there is any) is only retrieved on
// the first call to get_value() per audio buffer.
const AudioData& data1 = sync.data.read.get_value();
data1.use();
data1.the();
data1.data();
/**** UI thread could write new data here, for example ****/
const auto& data2 = sync.data.read.get_value();
// Will always pass. If new data was written by the UI thread
// then it won't be picked up until the next audio buffer.
assert(&data1 == &data2);
}
It's a tiny wrapper around std::atomic_flag.
stupid::trigger::operator()primes the triggerstupid::trigger::operator boolreturns true if the trigger was primed, and resets it
struct
{
stupid::trigger start_playback;
stupid::trigger stop_playback;
} sync;UI thread
void process_ui_events()
{
if (start_button_pressed)
{
sync.start_playback(); // Tell the audio thread to start playback ASAP
}
if (stop_button_pressed)
{
sync.stop_playback(); // Tell the audio thread to stop playback ASAP
}
}Audio thread
void audio_process()
{
switch (current_state)
{
case Playing:
{
if (sync.stop_playback) stop();
break;
}
case Stopped:
{
if (sync.start_playback) start();
break;
}
}
}Can be used to synchronize access to some memory between exactly two threads.
Some documentation here: beach_ball.md