Files
salmanoff/include/user/spMcRingBuffer.h
T
hayodea 018c1f1e1d SpMcRingBuffer: Added this new class
This will be the foundation for all StimulusBuffers. We can most
likely add this generically to the StimulusBuffer base class rather
than adding it only to StimulusBuffer's derived classes.
2025-10-31 22:58:18 -04:00

138 lines
3.5 KiB
C++

#ifndef _SP_MC_RING_BUFFER_H
#define _SP_MC_RING_BUFFER_H
#include <vector>
#include <cstddef>
#include <stdexcept>
#include <algorithm>
#include <user/sequenceLock.h>
namespace smo {
/**
* @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:
class InputEngineConstraints
{
public:
InputEngineConstraints(
size_t slotStartAlignmentNBytes_,
size_t slotPadToNBytes_)
: slotStartAlignmentNBytes(slotStartAlignmentNBytes_),
slotPadToNBytes(slotPadToNBytes_)
{}
~InputEngineConstraints() = default;
// Input-engine layout/constraints
size_t slotStartAlignmentNBytes; // power-of-2 alignment (e.g., 4096)
size_t slotPadToNBytes; // minimum size per slot
};
public:
/** EXPLANATION:
* Constructor initializes the ring buffer with the given constraints and
* number of slots. Calculates stride and allocates data buffer and sequence
* locks array.
*/
explicit SpMcRingBuffer(
const InputEngineConstraints& constraints_,
size_t nSlots_)
: nSlots(nSlots_), strideNBytes(0), bufferNBytes(0),
constraints(constraints_)
{
if (nSlots == 0)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: nSlots must be > 0");
}
computeStrideAndBufferSize();
// Allocate data buffer: bufferNBytes (aligned up to alignment)
data.resize(bufferNBytes);
// Initialize sequence locks array: one lock per slot
sequenceLocks.resize(nSlots);
}
~SpMcRingBuffer() = default;
// Non-copyable, movable
SpMcRingBuffer(const SpMcRingBuffer&) = delete;
SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
SpMcRingBuffer(SpMcRingBuffer&&) = default;
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = default;
public:
/**
* @brief Get a reference to data at the specified slot
*
* @tparam T The type of data stored in the slot
* @param slotIndex The index of the slot (0-based)
* @return Reference to T at the slot
* @throws std::out_of_range if slotIndex >= nSlots
*/
template<typename T>
T& getDataAtSlot(size_t slotIndex)
{
if (slotIndex >= nSlots)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
}
size_t offset = slotIndex * strideNBytes;
return *reinterpret_cast<T*>(data.data() + offset);
}
SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
{
if (slotIndex >= nSlots)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
}
return sequenceLocks[slotIndex];
}
private:
void computeStrideAndBufferSize()
{
// Stride is the maximum of alignment and padding
strideNBytes = std::max(
constraints.slotStartAlignmentNBytes,
constraints.slotPadToNBytes);
// Buffer size is nSlots * strideNBytes, aligned up to alignment
size_t rawSize = nSlots * strideNBytes;
bufferNBytes = ((rawSize + constraints.slotStartAlignmentNBytes - 1)
/ constraints.slotStartAlignmentNBytes)
* constraints.slotStartAlignmentNBytes;
}
// Buffer data
std::vector<uint8_t> data;
// Sequence locks array: one lock per slot
std::vector<SequenceLock> sequenceLocks;
public:
// Layout/invariants
size_t nSlots;
size_t strideNBytes;
size_t bufferNBytes;
InputEngineConstraints constraints;
};
} // namespace smo
#endif // _SP_MC_RING_BUFFER_H