Files
hayodea e689063a8c StimFrame: Store ringbuff index as member var
Now each StimFrame knows its index within its parent SpMcRingbuff
object.
2025-11-23 06:15:54 -04:00

158 lines
4.3 KiB
C++

#ifndef _SP_MC_RING_BUFFER_H
#define _SP_MC_RING_BUFFER_H
#include <vector>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <new>
#include <memory>
#include <user/stimulusFrame.h>
#include <user/frameAssemblyDesc.h>
#include <user/sequenceLock.h>
#include <user/senseApiDesc.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace stim_buff {
/**
* @brief Single-producer, multi-consumer ring buffer w/per-slot sequence locks
*
* A ring buffer that maintains data alignment constraints while providing
* lock-free read access through per-slot sequence locks. The locks are kept
* separate from the data to preserve alignment requirements for the input
* engine.
*/
class SpMcRingBuffer
{
public:
/** EXPLANATION:
* Constructor initializes the ring buffer with FrameAssemblyDesc.
* Allocates frames vector with properly constructed StimulusFrame instances,
* each initialized with a SlotDesc from the FrameAssemblyDesc.
*/
explicit SpMcRingBuffer(
const std::shared_ptr<FrameAssemblyDesc> &frameAssemblyDesc_,
const SmoCallbacks& callbacks,
cl_mem_flags flags)
:
nBuffers(frameAssemblyDesc_ ? frameAssemblyDesc_->slots.size() : 0),
frameAssemblyDesc(frameAssemblyDesc_),
slots(nBuffers), // Default-construct all frames
producerNextUsableIndex(0)
{
if (!frameAssemblyDesc)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: frameAssemblyDesc must not be null");
}
if (nBuffers == 0)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: frameAssemblyDesc must have at least one "
"slot");
}
// Re-invoke constructors w/placement new on default-constructed frames
for (size_t i = 0; i < nBuffers; ++i)
{
slots[i].~StimulusFrame(); // Destroy default-constructed object
new (&slots[i]) StimulusFrame(
frameAssemblyDesc->slots[i], callbacks, flags, i);
}
}
~SpMcRingBuffer() = default;
// Non-copyable, non-movable (slots are non-movable)
SpMcRingBuffer(const SpMcRingBuffer&) = delete;
SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
SpMcRingBuffer(SpMcRingBuffer&&) = delete;
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = delete;
public:
/**
* @brief Get a reference to the StimulusFrame at the specified slot
*
* @param slotIndex The index of the slot (0-based)
* @return Reference to StimulusFrame at the slot
* @throws std::out_of_range if slotIndex >= nBuffers
*/
StimulusFrame& getDataAtSlot(size_t slotIndex)
{
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
return slots[slotIndex];
}
SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
{
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
return slots[slotIndex].lock;
}
/**
* @brief Get the next index to produce into, atomically incrementing it
*
* Uses sequence lock to perform an emulated fetch_add with modulo nBuffers
* applied, ensuring the returned index is always < nBuffers.
*
* @return The index to produce into (always < nBuffers)
*/
size_t getIndexToProduceInto()
{
producerNextUsableIndexLock.writeAcquire();
size_t currentIndex = producerNextUsableIndex;
size_t nextIndex = (currentIndex + 1) % nBuffers;
producerNextUsableIndex = nextIndex;
producerNextUsableIndexLock.writeRelease();
return currentIndex;
}
/**
* @brief Abort production by setting the producer index to a specific value
*
* @param index The index to set (must be < nBuffers)
* @throws std::out_of_range if index >= nBuffers
*/
void abortProduction(size_t index)
{
if (index >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: index must be < nBuffers");
}
producerNextUsableIndexLock.writeAcquire();
producerNextUsableIndex = index;
producerNextUsableIndexLock.writeRelease();
}
public:
// Layout/invariants
size_t nBuffers;
private:
// FrameAssemblyDesc describing the memory layout
std::shared_ptr<FrameAssemblyDesc> frameAssemblyDesc;
// Frames vector: each frame contains a sequence lock and SlotDesc
std::vector<StimulusFrame> slots;
SequenceLock producerNextUsableIndexLock;
size_t producerNextUsableIndex;
};
} // namespace stim_buff
} // namespace smo
#endif // _SP_MC_RING_BUFFER_H