123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- #pragma once
- #include <stdint.h>
- /**
- * Template for triple buffers.
- *
- * This template implements a lock-free triple buffer that can be used to exchange data
- * between two threads that are producing and consuming at different rates. Instead of
- * atomically exchanging pointers to the buffers, we atomically update a Flags register
- * that holds the indices into a 3-element buffer array.
- *
- * The three buffers are named as follows:
- * - Read buffer: This is where Read() will read the latest value from
- * - Write buffer: This is where Write() will write a new value to
- * - Temp buffer: This is the second back-buffer currently not used for reading or writing
- *
- * Please note that reading and writing to the buffer does not automatically swap the
- * back-buffers. Instead, two separate methods, SwapReadBuffers() and SwapWriteBuffers()
- * are provided. For convenience, we also provide SwapAndRead() and WriteAndSwap() to
- * update and swap the buffers using a single method call.
- *
- * A dirty flag indicates whether a new value has been written and swapped into the second
- * back-buffer and is available for reading. It can be checked using the IsDirtyMethod().
- * As an optimization, SwapReadBuffers() and SwapAndRead() will not perform a back-buffer
- * swap if no new data is available.
- *
- * This class is thread-safe in single-producer, single-consumer scenarios.
- *
- * Based on ideas and C code in "Triple Buffering as a Concurrency Mechanism" (Reddit.com)
- *
- * @param BufferType The type of buffered items.
- */
- template<typename BufferType>
- class TTripleBuffer
- {
- /** Enumerates human readable bit values for accessing the Flags field. */
- enum EBufferFlag
- {
- /** Indicates whether a new buffer is available for reading. */
- Dirty = 0x40,
- /** Initial flags value (0dttwwrr; dirty = false, temp index = 0, write index = 1, read index = 2) */
- Initial = 0x06,
- /** Bit mask for accessing the read buffer index (bit 0-1). */
- ReaderMask = 0x03,
- /** Bit mask for the index of the unused/clean/empty buffer (bit 4-5). */
- TempMask = 0x30,
- /** Bit shift for accessing the temp buffer index. */
- TempShift = 4,
- /** Bit mask for accessing the write buffer index (bit 2-3). */
- WriterMask = 0x0c,
- /** Bit shift for accessing the write buffer index. */
- WriterShift = 2,
- };
- public:
- /** Default constructor. */
- TTripleBuffer()
- {
- Initialize();
- Buffers[0] = Buffers[1] = Buffers[2] = BufferType();
- }
- /**
- * Create and initialize a new instance with a given buffer value.
- *
- * @param InValue The initial value of all three buffers.
- */
- explicit TTripleBuffer(const BufferType& InValue)
- {
- Initialize();
- Buffers[0] = Buffers[1] = Buffers[2] = InValue;
- }
- /**
- * Create and initialize a new instance using provided buffers.
- *
- * The elements of the provided items array are expected to have
- * the following initial contents:
- * 0 = Temp
- * 1 = Write
- * 2 = Read
- *
- * @param InBuffers The buffer memory to use.
- */
- /*TTripleBuffer(BufferType(&InBuffers)[3])
- {
- Buffers = &InBuffers[0];
- Flags = EBufferFlag::Initial | EBufferFlag::Dirty;
- OwnsMemory = false;
- }*/
- /** Destructor. */
- ~TTripleBuffer()
- {
- delete[] Buffers;
- }
- public:
- /**
- * Check whether a new value is available for reading.
- *
- * @return true if a new value is available, false otherwise.
- */
- bool IsDirty() const
- {
- return ((Flags & EBufferFlag::Dirty) != 0);
- }
- /**
- * Read a value from the current read buffer.
- *
- * @return Reference to the read buffer's value.
- * @see SwapRead, Write
- */
- BufferType& Read()
- {
- return Buffers[Flags & EBufferFlag::ReaderMask];
- }
- /**
- * Swap the latest read buffer, if available.
- *
- * Will not perform a back-buffer swap if no new data is available (dirty flag = false).
- *
- * @see SwapWrite, Read
- */
- void SwapReadBuffers()
- {
- if(!IsDirty())
- {
- return;
- }
- Flags = SwapReadWithTempFlags(Flags);
- }
- public:
- /**
- * Get the current write buffer.
- *
- * @return Reference to write buffer.
- */
- BufferType& GetWriteBuffer()
- {
- return Buffers[(Flags & EBufferFlag::WriterMask) >> EBufferFlag::WriterShift];
- }
- /**
- * Swap a new write buffer (makes current write buffer available for reading).
- *
- * @see SwapRead, Write
- */
- void SwapWriteBuffers()
- {
- if(IsDirty())
- {
- return;
- }
- Flags = SwapWriteWithTempFlags(Flags);
- }
- /**
- * Write a value to the current write buffer.
- *
- * @param Value The value to write.
- * @see SwapWrite, Read
- */
- void Write(const BufferType Value)
- {
- Buffers[(Flags & EBufferFlag::WriterMask) >> EBufferFlag::WriterShift] = Value;
- }
- public:
- /** Reset the buffer. */
- void Reset()
- {
- Flags = EBufferFlag::Initial;
- }
- protected:
- /** Initialize the triple buffer. */
- void Initialize()
- {
- Buffers = new BufferType[3];
- Flags = EBufferFlag::Initial;
- }
- private:
- /** Swaps the read and temp buffer indices in the Flags field. */
- static int32_t SwapReadWithTempFlags(int32_t Flags)
- {
- return ((Flags & EBufferFlag::ReaderMask) << 4) | ((Flags & EBufferFlag::TempMask) >> 4) | (Flags & EBufferFlag::WriterMask);
- }
- /** Swaps the write and temp buffer indices in the Flags field, and sets the dirty bit. */
- static int32_t SwapWriteWithTempFlags(int32_t Flags)
- {
- return ((Flags & EBufferFlag::TempMask) >> 2) | ((Flags & EBufferFlag::WriterMask) << 2) | (Flags & EBufferFlag::ReaderMask) | EBufferFlag::Dirty;
- }
- private:
- /** Hidden copy constructor (triple buffers cannot be copied). */
- TTripleBuffer(const TTripleBuffer&);
- /** Hidden copy assignment (triple buffers cannot be copied). */
- TTripleBuffer& operator=(const TTripleBuffer&);
- private:
- /** The three buffers. */
- BufferType* Buffers;
- /** Buffer access flags. */
- __declspec(align(16)) int32_t volatile Flags ;
- };
|