// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Templates/Atomic.h" #include "Templates/SharedPointer.h" namespace Chaos { /** * A lock free paradigm for sharing data between threads. * * Called a triple buffer because at any point in time, there may be 3 * buffers available: 1 owned by the producing thread, 1 owned by the * consuming thread, and 1 waiting in an atomic variable. The third storage * location enables transfer of ownership such that if you have a copy of * the data, you own it without worry of contention. * * Producer thread: * \code * struct AnimXf * { * TArray Xf; * TArray Velocity; * }; * * struct MyProducer * { * MyConsumer Consumer; // Owns the triple buffer. * AnimXf* Buffer = nullptr; * * // This function is called repeatedly at some interval by the producing thread. * void Produce() * { * // Get a new buffer if we need one. * if(!Buffer) * Buffer = Consumer.AnimXfTripleBuffer.ExchangeProducerBuffer(); * // This class now has exclusive ownership of the memory pointed to by Buffer. * Buffer->Xf = ...; * Buffer->Velocity = ...; * // Push the new values to the consumer, and get a new buffer for next time. * Buffer = Consumer.AnimXfTripleBuffer.ExchangeProducerBuffer(); * } * }; * \endcode * * Consumer thread: * \code * struct MyConsumer * { * // In this example the consumer owns the triple buffer, but that's * // not a requirement. * TTripleBufferedData AnimXfTripleBuffer; * AnimXf* Buffer = nullptr; * * // This function is called repeatedly at some interval by the consuming thread. * void Consume() * { * // Get a new view of the data, which can be null or old. * Buffer = AnimXfTripleBuffer.ExchangeAnimXfConsumerBuffer(); * // This class now has exclusive ownership of the memory pointed to by Buffer. * if(Buffer) * { * ... = Buffer->Xf; * ... = Buffer->Velocity; * } * } * }; * \endcode */ template class TTripleBufferedData { public: TTripleBufferedData() : ProducerThreadBuffer(&Buffers[0]) , ConsumerThreadBuffer(&Buffers[1]) , Interchange(&Buffers[2]) , Counter(0) , LastId(0) {} /** * Get a new buffer for the producing thread to write to, while at * the same time making the previous buffer available to the consumer. * * This function will manufacture new instances of \c DataType, if it * needs to. Thus, the returned pointer will never be null. * * The \c DataType value is returned by shared pointer to simplify * ownership semantics, particularly with respect to cleanup. It is * important to not mistake that for exclusive ownership of the memory, * and retain the return value of this function too long. For instance: * \code * TTripleBufferedData IntBuffer; * ... * int* MyCopy = IntBuffer.ExchangeProducerBuffer(); * (*MyCopy) = 1; // Ok * IntBuffer.ExchangeProducerBuffer(); // Mem holding "1" now available to consumer thread. * (*MyCopy) = 2; // Race condition! * \endcode * * Instead, always reuse the same shared pointer, or dereference the * returned shared pointer if you're not worried about copying the data. * \code * TTripleBufferedData IntBuffer; * ... * int* MyCopy = IntBuffer.ExchangeProducerBuffer(); * (*MyCopy) = 1; // Ok * MyCopy = IntBuffer.ExchangeProducerBuffer(); // Mem holding "1" now available to consumer thread. * (*MyCopy) = 2; // Ok * \endcode */ DataType* ExchangeProducerBuffer() { ProducerThreadBuffer = Interchange.Exchange(ProducerThreadBuffer); if (!ProducerThreadBuffer->Value) { ProducerThreadBuffer->Value = TUniquePtr(new DataType()); } ProducerThreadBuffer->Id = ++Counter; // First value is 1, will overflow. return ProducerThreadBuffer->Value.Get(); } /** * Get an updated buffer for the consuming thread to read from. * * The returned pointer may be null if the producer thread hasn't done * an update since the last time this function was called. * * The same capacity for a race condition exists with this functions * return value as with \c ExchangeProducerBuffer(). * \code * TTripleBufferedData IntBuffer; * ... * int* MyCopy = IntBuffer.ExchangeConsumerBuffer(); * int X = *MyCopy; // Ok * IntBuffer.ExchangeConsumerBuffer(); // Mem holding X returned to producer thread. * int Y = *MyCopy; // Race condition! * \endcode * * Always refresh the same variable. * \code * TTripleBufferedData IntBuffer; * ... * int* MyCopy = IntBuffer.ExchangeConsumerBuffer(); * int X = *MyCopy; // Ok * MyCopy = IntBuffer.ExchangeConsumerBuffer(); // Mem holding X returned to producer thread. * int Y = *MyCopy; // Ok * \endcode */ DataType* ExchangeConsumerBuffer() { ConsumerThreadBuffer = Interchange.Exchange(ConsumerThreadBuffer); if (ConsumerThreadBuffer->Id <= LastId && LastId - ConsumerThreadBuffer->Id < TNumericLimits::Max()) // Detect overflows { return nullptr; } LastId = ConsumerThreadBuffer->Id; return ConsumerThreadBuffer->Value.Get(); } private: // Non-copyable and non-movable, due to TAtomic member. TTripleBufferedData(TTripleBufferedData&& Other) = delete; TTripleBufferedData(const TTripleBufferedData&) = delete; TTripleBufferedData& operator=(TTripleBufferedData&&) = delete; TTripleBufferedData& operator=(const TTripleBufferedData&) = delete; struct DataTypeWrapper { DataTypeWrapper() : Value(nullptr) , Id(0) {} DataTypeWrapper(const DataTypeWrapper&) = delete; DataTypeWrapper(DataTypeWrapper&& Other) : Value(MoveTemp(Other.Value)) , Id(MoveTemp(Other.Id)) {} void operator=(const DataTypeWrapper& Other) = delete; // Depending on the use case, it may be useful to store DataType // instances via shared pointer so that the thread that doesn't // own the TripleBufferedData instance, can retain ownership of // this memory after this class goes out of scope. TUniquePtr Value; uint64 Id; }; DataTypeWrapper Buffers[3] = { DataTypeWrapper(), DataTypeWrapper(), DataTypeWrapper() }; DataTypeWrapper* ProducerThreadBuffer; DataTypeWrapper* ConsumerThreadBuffer; TAtomic Interchange; uint64 Counter; // Only accessed by producer thread uint64 LastId; // Only accessed by consumer thread }; } // namespace Chaos