#ifndef _SP_MC_RING_BUFFER_H #define _SP_MC_RING_BUFFER_H #include #include #include #include #include #include #include #include #include #include #define CL_TARGET_OPENCL_VERSION 120 #include 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_, 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; // Frames vector: each frame contains a sequence lock and SlotDesc std::vector slots; SequenceLock producerNextUsableIndexLock; size_t producerNextUsableIndex; }; } // namespace stim_buff } // namespace smo #endif // _SP_MC_RING_BUFFER_H