/* * Carla Ring Buffer * Copyright (C) 2013-2023 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #ifndef CARLA_RING_BUFFER_HPP_INCLUDED #define CARLA_RING_BUFFER_HPP_INCLUDED #include "CarlaMathUtils.hpp" // ----------------------------------------------------------------------- // Buffer structs /* head: current writing position, headmost position of the buffer. increments when writing. tail: current reading position, last used position of the buffer. increments when reading. head == tail means empty buffer wrtn: temporary position of head until a commitWrite() is called. if buffer writing fails, wrtn will be back to head position thus ignoring the last operation(s). if buffer writing succeeds, head will be set to this variable. invalidateCommit: boolean used to check if a write operation failed. this ensures we don't get incomplete writes. */ struct HeapBuffer { uint32_t size; uint32_t head, tail, wrtn; bool invalidateCommit; uint8_t* buf; void copyDataFrom(const HeapBuffer& rb) noexcept { CARLA_SAFE_ASSERT_RETURN(size == rb.size,); head = rb.head; tail = rb.tail; wrtn = rb.wrtn; invalidateCommit = rb.invalidateCommit; std::memcpy(buf, rb.buf, size); } }; struct SmallStackBuffer { static constexpr const uint32_t size = 4096; uint32_t head, tail, wrtn; bool invalidateCommit; uint8_t buf[size]; }; struct BigStackBuffer { static constexpr const uint32_t size = 16384; uint32_t head, tail, wrtn; bool invalidateCommit; uint8_t buf[size]; }; struct HugeStackBuffer { static constexpr const uint32_t size = 65536; uint32_t head, tail, wrtn; bool invalidateCommit; uint8_t buf[size]; }; #ifdef CARLA_PROPER_CPP11_SUPPORT # define HeapBuffer_INIT {0, 0, 0, 0, false, nullptr} # define StackBuffer_INIT {0, 0, 0, false, {0}} #else # define HeapBuffer_INIT # define StackBuffer_INIT #endif // ----------------------------------------------------------------------- // CarlaRingBufferControl templated class template class CarlaRingBufferControl { public: CarlaRingBufferControl() noexcept : fBuffer(nullptr), fErrorReading(false), fErrorWriting(false) {} virtual ~CarlaRingBufferControl() noexcept {} // ------------------------------------------------------------------- void clearData() noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); fBuffer->head = 0; fBuffer->tail = 0; fBuffer->wrtn = 0; fBuffer->invalidateCommit = false; carla_zeroBytes(fBuffer->buf, fBuffer->size); } // ------------------------------------------------------------------- bool commitWrite() noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); if (fBuffer->invalidateCommit) { fBuffer->wrtn = fBuffer->head; fBuffer->invalidateCommit = false; return false; } // nothing to commit? CARLA_SAFE_ASSERT_RETURN(fBuffer->head != fBuffer->wrtn, false); // all ok fBuffer->head = fBuffer->wrtn; fErrorWriting = false; return true; } bool isDataAvailableForReading() const noexcept; bool isEmpty() const noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); return (fBuffer->buf == nullptr || fBuffer->head == fBuffer->tail); } uint32_t getAvailableDataSize() const noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, 0); const uint32_t wrap((fBuffer->tail > fBuffer->wrtn) ? 0 : fBuffer->size); return wrap + fBuffer->tail - fBuffer->wrtn; } // ------------------------------------------------------------------- bool readBool() noexcept { bool b = false; return tryRead(&b, sizeof(bool)) ? b : false; } uint8_t readByte() noexcept { uint8_t B = 0; return tryRead(&B, sizeof(uint8_t)) ? B : 0; } int16_t readShort() noexcept { int16_t s = 0; return tryRead(&s, sizeof(int16_t)) ? s : 0; } uint16_t readUShort() noexcept { uint16_t us = 0; return tryRead(&us, sizeof(uint16_t)) ? us : 0; } int32_t readInt() noexcept { int32_t i = 0; return tryRead(&i, sizeof(int32_t)) ? i : 0; } uint32_t readUInt() noexcept { uint32_t ui = 0; return tryRead(&ui, sizeof(int32_t)) ? ui : 0; } int64_t readLong() noexcept { int64_t l = 0; return tryRead(&l, sizeof(int64_t)) ? l : 0; } uint64_t readULong() noexcept { uint64_t ul = 0; return tryRead(&ul, sizeof(int64_t)) ? ul : 0; } float readFloat() noexcept { float f = 0.0f; return tryRead(&f, sizeof(float)) ? f : 0.0f; } double readDouble() noexcept { double d = 0.0; return tryRead(&d, sizeof(double)) ? d : 0.0; } void readCustomData(void* const data, const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(data != nullptr,); CARLA_SAFE_ASSERT_RETURN(size > 0,); if (! tryRead(data, size)) std::memset(data, 0, size); } template void readCustomType(T& type) noexcept { if (! tryRead(&type, sizeof(T))) std::memset(&type, 0, sizeof(T)); } // ------------------------------------------------------------------- void skipRead(const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); CARLA_SAFE_ASSERT_RETURN(size > 0,); CARLA_SAFE_ASSERT_RETURN(size < fBuffer->size,); // empty if (fBuffer->head == fBuffer->tail) return; const uint32_t head = fBuffer->head; const uint32_t tail = fBuffer->tail; const uint32_t wrap = head > tail ? 0 : fBuffer->size; if (size > wrap + head - tail) { if (! fErrorReading) { fErrorReading = true; carla_stderr2("CarlaRingBuffer::skipRead(%u): failed, not enough space", size); } return; } uint32_t readto = tail + size; if (readto >= fBuffer->size) readto -= fBuffer->size; fBuffer->tail = readto; fErrorReading = false; return; } // ------------------------------------------------------------------- bool writeBool(const bool value) noexcept { return tryWrite(&value, sizeof(bool)); } bool writeByte(const uint8_t value) noexcept { return tryWrite(&value, sizeof(uint8_t)); } bool writeShort(const int16_t value) noexcept { return tryWrite(&value, sizeof(int16_t)); } bool writeUShort(const uint16_t value) noexcept { return tryWrite(&value, sizeof(uint16_t)); } bool writeInt(const int32_t value) noexcept { return tryWrite(&value, sizeof(int32_t)); } bool writeUInt(const uint32_t value) noexcept { return tryWrite(&value, sizeof(uint32_t)); } bool writeLong(const int64_t value) noexcept { return tryWrite(&value, sizeof(int64_t)); } bool writeULong(const uint64_t value) noexcept { return tryWrite(&value, sizeof(uint64_t)); } bool writeFloat(const float value) noexcept { return tryWrite(&value, sizeof(float)); } bool writeDouble(const double value) noexcept { return tryWrite(&value, sizeof(double)); } bool writeCustomData(const void* const data, const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(data != nullptr, false); CARLA_SAFE_ASSERT_RETURN(size > 0, false); return tryWrite(data, size); } template bool writeCustomType(const T& type) noexcept { return tryWrite(&type, sizeof(T)); } // ------------------------------------------------------------------- protected: void setRingBuffer(BufferStruct* const ringBuf, const bool resetBuffer) noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != ringBuf,); fBuffer = ringBuf; if (resetBuffer && ringBuf != nullptr) clearData(); } // ------------------------------------------------------------------- bool tryRead(void* const buf, const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-pointer-compare" #endif CARLA_SAFE_ASSERT_RETURN(fBuffer->buf != nullptr, false); #if defined(__clang__) #pragma clang diagnostic pop #endif CARLA_SAFE_ASSERT_RETURN(buf != nullptr, false); CARLA_SAFE_ASSERT_RETURN(size > 0, false); CARLA_SAFE_ASSERT_RETURN(size < fBuffer->size, false); // empty if (fBuffer->head == fBuffer->tail) return false; uint8_t* const bytebuf = static_cast(buf); const uint32_t head = fBuffer->head; const uint32_t tail = fBuffer->tail; const uint32_t wrap = head > tail ? 0 : fBuffer->size; if (size > wrap + head - tail) { if (! fErrorReading) { fErrorReading = true; carla_stderr2("CarlaRingBuffer::tryRead(%p, %u): failed, not enough space", buf, size); } return false; } uint32_t readto = tail + size; if (readto > fBuffer->size) { readto -= fBuffer->size; if (size == 1) { std::memcpy(bytebuf, fBuffer->buf + tail, 1); } else { const uint32_t firstpart = fBuffer->size - tail; std::memcpy(bytebuf, fBuffer->buf + tail, firstpart); std::memcpy(bytebuf + firstpart, fBuffer->buf, readto); } } else { std::memcpy(bytebuf, fBuffer->buf + tail, size); if (readto == fBuffer->size) readto = 0; } fBuffer->tail = readto; fErrorReading = false; return true; } bool tryWrite(const void* const buf, const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); CARLA_SAFE_ASSERT_RETURN(buf != nullptr, false); CARLA_SAFE_ASSERT_RETURN(size > 0, false); CARLA_SAFE_ASSERT_UINT2_RETURN(size < fBuffer->size, size, fBuffer->size, false); const uint8_t* const bytebuf = static_cast(buf); const uint32_t tail = fBuffer->tail; const uint32_t wrtn = fBuffer->wrtn; const uint32_t wrap = tail > wrtn ? 0 : fBuffer->size; if (size >= wrap + tail - wrtn) { if (! fErrorWriting) { fErrorWriting = true; carla_stderr2("CarlaRingBuffer::tryWrite(%p, %u): failed, not enough space", buf, size); } fBuffer->invalidateCommit = true; return false; } uint32_t writeto = wrtn + size; if (writeto > fBuffer->size) { writeto -= fBuffer->size; if (size == 1) { std::memcpy(fBuffer->buf, bytebuf, 1); } else { const uint32_t firstpart = fBuffer->size - wrtn; std::memcpy(fBuffer->buf + wrtn, bytebuf, firstpart); std::memcpy(fBuffer->buf, bytebuf + firstpart, writeto); } } else { std::memcpy(fBuffer->buf + wrtn, bytebuf, size); if (writeto == fBuffer->size) writeto = 0; } fBuffer->wrtn = writeto; return true; } private: BufferStruct* fBuffer; // wherever read/write errors have been printed to terminal bool fErrorReading; bool fErrorWriting; CARLA_PREVENT_VIRTUAL_HEAP_ALLOCATION CARLA_DECLARE_NON_COPYABLE(CarlaRingBufferControl) }; template inline bool CarlaRingBufferControl::isDataAvailableForReading() const noexcept { return (fBuffer != nullptr && fBuffer->head != fBuffer->tail); } template <> inline bool CarlaRingBufferControl::isDataAvailableForReading() const noexcept { return (fBuffer != nullptr && fBuffer->buf != nullptr && fBuffer->head != fBuffer->tail); } // ----------------------------------------------------------------------- // CarlaRingBuffer using heap space class CarlaHeapRingBuffer : public CarlaRingBufferControl { public: CarlaHeapRingBuffer() noexcept : fHeapBuffer(HeapBuffer_INIT) { carla_zeroStruct(fHeapBuffer); } ~CarlaHeapRingBuffer() noexcept override { if (fHeapBuffer.buf == nullptr) return; delete[] fHeapBuffer.buf; fHeapBuffer.buf = nullptr; } void createBuffer(const uint32_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(fHeapBuffer.buf == nullptr,); CARLA_SAFE_ASSERT_RETURN(size > 0,); const uint32_t p2size = carla_nextPowerOf2(size); try { fHeapBuffer.buf = new uint8_t[p2size]; } CARLA_SAFE_EXCEPTION_RETURN("CarlaHeapRingBuffer::createBuffer",); fHeapBuffer.size = p2size; setRingBuffer(&fHeapBuffer, true); } void deleteBuffer() noexcept { CARLA_SAFE_ASSERT_RETURN(fHeapBuffer.buf != nullptr,); setRingBuffer(nullptr, false); delete[] fHeapBuffer.buf; fHeapBuffer.buf = nullptr; fHeapBuffer.size = 0; } private: HeapBuffer fHeapBuffer; CARLA_PREVENT_VIRTUAL_HEAP_ALLOCATION CARLA_DECLARE_NON_COPYABLE(CarlaHeapRingBuffer) }; // ----------------------------------------------------------------------- // CarlaRingBuffer using small stack space class CarlaSmallStackRingBuffer : public CarlaRingBufferControl { public: CarlaSmallStackRingBuffer() noexcept : fStackBuffer(StackBuffer_INIT) { setRingBuffer(&fStackBuffer, true); } private: SmallStackBuffer fStackBuffer; CARLA_PREVENT_VIRTUAL_HEAP_ALLOCATION CARLA_DECLARE_NON_COPYABLE(CarlaSmallStackRingBuffer) }; // ----------------------------------------------------------------------- #endif // CARLA_RING_BUFFER_HPP_INCLUDED