/*!********************************************************************
 
 Audacity: A Digital Audio Editor
 
 @file MessageBuffer.h
 
 Paul Licameli split from PlaybackSchedule.h
 
 **********************************************************************/

#ifndef __AUDACITY_MESSAGE_BUFFER__
#define __AUDACITY_MESSAGE_BUFFER__

#include <atomic>

//! Communicate data atomically from one writer thread 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 reassignable.
 */
template<typename Data>
class MessageBuffer {
   struct UpdateSlot {
      Data mData;
      std::atomic<bool> mBusy{ false };
   };
   NonInterfering<UpdateSlot> mSlots[2];

   std::atomic<unsigned char> mLastWrittenSlot{ 0 };

public:
   void Initialize();

   //! Move data out (if available), or else copy it out
   /*!
    @tparam Result is constructible from Data&& and forwards of other arguments
   */
   template<typename Result = Data, typename... ConstructorArgs>
   Result Read(ConstructorArgs &&...args);
   
   //! Reassign a slot by move or copy
   template<typename Arg = Data&&> void Write( Arg &&arg );
};

template<typename Data>
void MessageBuffer<Data>::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<typename Data>
template<typename Result, typename... ConstructorArgs>
Result MessageBuffer<Data>::Read(ConstructorArgs &&...args)
{
   // 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
   Result result(
      std::move( mSlots[idx].mData ), std::forward<ConstructorArgs>(args)... );

   mSlots[idx].mBusy.store( false, std::memory_order_release );

   return result;
}

template<typename Data>
template<typename Arg>
void MessageBuffer<Data>::Write( Arg &&arg )
{
   // 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::forward<Arg>(arg);
   mLastWrittenSlot.store( idx, std::memory_order_relaxed );

   mSlots[idx].mBusy.store( false, std::memory_order_release );
}

#endif
