diff --git a/src/AudioIO.h b/src/AudioIO.h index 47497b138..fa10ece14 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -198,6 +198,109 @@ int audacityAudioCallback( const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData ); +// Communicate data from one writer to one reader. +// This is not a queue: it is not necessary for each write to be read. +// Rather loss of a message is allowed: writer may overwrite. +// Data must be default-constructible and either copyable or movable. +template +class MessageBuffer { + struct alignas( 64 + //std::hardware_destructive_interference_size // C++17 + ) UpdateSlot { + std::atomic mBusy{ false }; + Data mData; + } mSlots[2]; + + std::atomic mLastWrittenSlot{ 0 }; + +public: + void Initialize(); + + // Move data out (if available), or else copy it out + Data Read(); + + // Copy data in + void Write( const Data &data ); + // Move data in + void Write( Data &&data ); +}; + +template +void MessageBuffer::Initialize() +{ + for (auto &slot : mSlots) + // Lock both slots first, maybe spinning a little + while ( slot.mBusy.exchange( true, std::memory_order_acquire ) ) + {} + + mSlots[0].mData = {}; + mSlots[1].mData = {}; + mLastWrittenSlot.store( 0, std::memory_order_relaxed ); + + for (auto &slot : mSlots) + slot.mBusy.exchange( false, std::memory_order_release ); +} + +template +Data MessageBuffer::Read() +{ + // Whichever slot was last written, prefer to read that. + auto idx = mLastWrittenSlot.load( std::memory_order_relaxed ); + idx = 1 - idx; + bool wasBusy = false; + do { + // This loop is unlikely to execute twice, but it might because the + // producer thread is writing a slot. + idx = 1 - idx; + wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire ); + } while ( wasBusy ); + + // Copy the slot + auto result = std::move( mSlots[idx].mData ); + + mSlots[idx].mBusy.store( false, std::memory_order_release ); + + return result; +} + +template +void MessageBuffer::Write( const Data &data ) +{ + // Whichever slot was last written, prefer to write the other. + auto idx = mLastWrittenSlot.load( std::memory_order_relaxed ); + bool wasBusy = false; + do { + // This loop is unlikely to execute twice, but it might because the + // consumer thread is reading a slot. + idx = 1 - idx; + wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire ); + } while ( wasBusy ); + + mSlots[idx].mData = data; + mLastWrittenSlot.store( idx, std::memory_order_relaxed ); + + mSlots[idx].mBusy.store( false, std::memory_order_release ); +} + +template +void MessageBuffer::Write( Data &&data ) +{ + // Whichever slot was last written, prefer to write the other. + auto idx = mLastWrittenSlot.load( std::memory_order_relaxed ); + bool wasBusy = false; + do { + // This loop is unlikely to execute twice, but it might because the + // consumer thread is reading a slot. + idx = 1 - idx; + wasBusy = mSlots[idx].mBusy.exchange( true, std::memory_order_acquire ); + } while ( wasBusy ); + + mSlots[idx].mData = std::move( data ); + mLastWrittenSlot.store( idx, std::memory_order_relaxed ); + + mSlots[idx].mBusy.store( false, std::memory_order_release ); +} + class AUDACITY_DLL_API AudioIO final { public: