Mercurial > hg > pub > prymula > com
diff DPF-Prymula-audioplugins/dpf/distrho/extra/RingBuffer.hpp @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/extra/RingBuffer.hpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,788 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_RING_BUFFER_HPP_INCLUDED +#define DISTRHO_RING_BUFFER_HPP_INCLUDED + +#include "../DistrhoUtils.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- +// Buffer structs + +/** + Base structure for all RingBuffer containers. + This struct details the data model used in DPF's RingBuffer class. + + DPF RingBuffer uses a struct just like this one to store positions, buffer data, size, etc. + The RingBuffer itself takes ownership of this struct and uses it to store any needed data. + This allows to dynamically change the way its ring buffer is allocated, simply by changing the template type. + For example, `RingBufferControl<HeapBuffer>` will create a ring buffer with heap memory, which can be of any size. + In the same vein, `RingBufferControl<SmallStackBuffer>` will create a ring buffer with stack memory, + directly tied to the RingBufferControl it belongs to. + + The main idea behind this model is to allow RingBufferControl over memory created elsewhere, + for example shared memory area. + One can create/place the Buffer struct in shared memory, and point RingBufferControl to it, + thus avoiding the pitfalls of sharing access to a non trivially-copyable/POD C++ class. + + Unlike other ring buffers, an extra variable is used to track pending writes. + This is so we can write a few bytes at a time and later mark the whole operation as complete, + thus avoiding the issue of reading data too early from the other side. + For example, write the size of some data first, and then the actual data. + The reading side will only see data available once size + data is completely written and "committed". + */ +struct HeapBuffer { + /** + Size of the buffer, allocated in @a buf. + If the size is fixed (stack buffer), this variable can be static. + */ + uint32_t size; + + /** + Current writing position, headmost position of the buffer. + Increments when writing. + */ + uint32_t head; + + /** + Current reading position, last used position of the buffer. + Increments when reading. + head == tail means empty buffer. + */ + uint32_t tail; + + /** + 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. + */ + uint32_t wrtn; + + /** + Boolean used to check if a write operation failed. + This ensures we don't get incomplete writes. + */ + bool invalidateCommit; + + /** + Pointer to buffer data. + This can be either stack or heap data, depending on the usecase. + */ + uint8_t* buf; +}; + +/** + RingBufferControl compatible struct with a relatively small stack size (4k bytes). + @see HeapBuffer +*/ +struct SmallStackBuffer { + static const uint32_t size = 4096; + uint32_t head, tail, wrtn; + bool invalidateCommit; + uint8_t buf[size]; +}; + +/** + RingBufferControl compatible struct with a relatively big stack size (16k bytes). + @see HeapBuffer +*/ +struct BigStackBuffer { + static const uint32_t size = 16384; + uint32_t head, tail, wrtn; + bool invalidateCommit; + uint8_t buf[size]; +}; + +/** + RingBufferControl compatible struct with a huge stack size (64k bytes). + @see HeapBuffer +*/ +struct HugeStackBuffer { + static const uint32_t size = 65536; + uint32_t head, tail, wrtn; + bool invalidateCommit; + uint8_t buf[size]; +}; + +#ifdef DISTRHO_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 + +// ----------------------------------------------------------------------- +// RingBufferControl templated class + +/** + DPF built-in RingBuffer class. + RingBufferControl takes one buffer struct to take control over, and operates over it. + + This is meant for single-writer, single-reader type of control. + Writing and reading is wait and lock-free. + + Typically usage involves: + ``` + // definition + HeapRingBuffer myHeapBuffer; // or RingBufferControl<HeapBuffer> class for more control + + // construction, only needed for heap buffers + myHeapBuffer.createBuffer(8192); + + // writing data + myHeapBuffer.writeUInt(size); + myHeapBuffer.writeCustomData(someOtherData, size); + myHeapBuffer.commitWrite(); + + // reading data + if (myHeapBuffer.isDataAvailableForReading()) + { + uint32_t size; + if (myHeapBuffer.readUInt(size) && readCustomData(&anotherData, size)) + { + // do something with "anotherData" + } + } + ``` + + @see HeapBuffer + */ +template <class BufferStruct> +class RingBufferControl +{ +public: + /* + * Constructor for uninitialised ring buffer. + * A call to setRingBuffer is required to tied this control to a ring buffer struct; + * + */ + RingBufferControl() noexcept + : buffer(nullptr), + errorReading(false), + errorWriting(false) {} + + /* + * Destructor. + */ + virtual ~RingBufferControl() noexcept {} + + // ------------------------------------------------------------------- + // check operations + + /* + * Check if there is any data available for reading, regardless of size. + */ + bool isDataAvailableForReading() const noexcept; + + /* + * Check if ring buffer is empty (that is, there is nothing to read). + */ + bool isEmpty() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, false); + + return (buffer->buf == nullptr || buffer->head == buffer->tail); + } + + /* + * Get the size of the data available to read. + */ + uint32_t getReadableDataSize() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); + + const uint32_t wrap = buffer->head > buffer->tail ? 0 : buffer->size; + + return wrap + buffer->head - buffer->tail; + } + + /* + * Get the size of the data available to write. + */ + uint32_t getWritableDataSize() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); + + const uint32_t wrap = (buffer->tail > buffer->wrtn) ? 0 : buffer->size; + + return wrap + buffer->tail - buffer->wrtn; + } + + // ------------------------------------------------------------------- + // clear/reset operations + + /* + * Clear the entire ring buffer data, marking the buffer as empty. + * Requires a buffer struct tied to this class. + */ + void clearData() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr,); + + buffer->head = 0; + buffer->tail = 0; + buffer->wrtn = 0; + buffer->invalidateCommit = false; + + std::memset(buffer->buf, 0, buffer->size); + } + + // ------------------------------------------------------------------- + // read operations + + /* + * Read a single boolean value. + * Returns false if reading fails. + */ + bool readBool() noexcept + { + bool b = false; + return tryRead(&b, sizeof(bool)) ? b : false; + } + + /* + * Read a single 8-bit byte. + * Returns 0 if reading fails. + */ + uint8_t readByte() noexcept + { + uint8_t B = 0; + return tryRead(&B, sizeof(uint8_t)) ? B : 0; + } + + /* + * Read a short 16-bit integer. + * Returns 0 if reading fails. + */ + int16_t readShort() noexcept + { + int16_t s = 0; + return tryRead(&s, sizeof(int16_t)) ? s : 0; + } + + /* + * Read a short unsigned 16-bit integer. + * Returns 0 if reading fails. + */ + uint16_t readUShort() noexcept + { + uint16_t us = 0; + return tryRead(&us, sizeof(uint16_t)) ? us : 0; + } + + /* + * Read a regular 32-bit integer. + * Returns 0 if reading fails. + */ + int32_t readInt() noexcept + { + int32_t i = 0; + return tryRead(&i, sizeof(int32_t)) ? i : 0; + } + + /* + * Read an unsigned 32-bit integer. + * Returns 0 if reading fails. + */ + uint32_t readUInt() noexcept + { + uint32_t ui = 0; + return tryRead(&ui, sizeof(int32_t)) ? ui : 0; + } + + /* + * Read a long 64-bit integer. + * Returns 0 if reading fails. + */ + int64_t readLong() noexcept + { + int64_t l = 0; + return tryRead(&l, sizeof(int64_t)) ? l : 0; + } + + /* + * Read a long unsigned 64-bit integer. + * Returns 0 if reading fails. + */ + uint64_t readULong() noexcept + { + uint64_t ul = 0; + return tryRead(&ul, sizeof(int64_t)) ? ul : 0; + } + + /* + * Read a single-precision floating point number. + * Returns 0 if reading fails. + */ + float readFloat() noexcept + { + float f = 0.0f; + return tryRead(&f, sizeof(float)) ? f : 0.0f; + } + + /* + * Read a double-precision floating point number. + * Returns 0 if reading fails. + */ + double readDouble() noexcept + { + double d = 0.0; + return tryRead(&d, sizeof(double)) ? d : 0.0; + } + + /*! + * Read an arbitrary amount of data, specified by @a size. + * data pointer must be non-null, and size > 0. + * + * Returns true if reading succeeds. + * In case of failure, @a data pointer is automatically cleared by @a size bytes. + */ + bool readCustomData(void* const data, const uint32_t size) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(size > 0, false); + + if (tryRead(data, size)) + return true; + + std::memset(data, 0, size); + return false; + } + + /*! + * Read a custom data type specified by the template typename used, + * with size being automatically deduced by the compiler (through the use of sizeof). + * + * Returns true if reading succeeds. + * In case of failure, @a type value is automatically cleared by its deduced size. + */ + template <typename T> + bool readCustomType(T& type) noexcept + { + if (tryRead(&type, sizeof(T))) + return true; + + std::memset(&type, 0, sizeof(T)); + return false; + } + + // ------------------------------------------------------------------- + // write operations + + /* + * Write a single boolean value. + */ + bool writeBool(const bool value) noexcept + { + return tryWrite(&value, sizeof(bool)); + } + + /* + * Write a single 8-bit byte. + */ + bool writeByte(const uint8_t value) noexcept + { + return tryWrite(&value, sizeof(uint8_t)); + } + + /* + * Write a short 16-bit integer. + */ + bool writeShort(const int16_t value) noexcept + { + return tryWrite(&value, sizeof(int16_t)); + } + + /* + * Write a short unsigned 16-bit integer. + */ + bool writeUShort(const uint16_t value) noexcept + { + return tryWrite(&value, sizeof(uint16_t)); + } + + /* + * Write a regular 32-bit integer. + */ + bool writeInt(const int32_t value) noexcept + { + return tryWrite(&value, sizeof(int32_t)); + } + + /* + * Write an unsigned 32-bit integer. + */ + bool writeUInt(const uint32_t value) noexcept + { + return tryWrite(&value, sizeof(uint32_t)); + } + + /* + * Write a long 64-bit integer. + */ + bool writeLong(const int64_t value) noexcept + { + return tryWrite(&value, sizeof(int64_t)); + } + + /* + * Write a long unsigned 64-bit integer. + */ + bool writeULong(const uint64_t value) noexcept + { + return tryWrite(&value, sizeof(uint64_t)); + } + + /* + * Write a single-precision floating point number. + */ + bool writeFloat(const float value) noexcept + { + return tryWrite(&value, sizeof(float)); + } + + /* + * Write a double-precision floating point number. + */ + bool writeDouble(const double value) noexcept + { + return tryWrite(&value, sizeof(double)); + } + + /*! + * Write an arbitrary amount of data, specified by @a size. + * data pointer must be non-null, and size > 0. + */ + bool writeCustomData(const void* const data, const uint32_t size) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(size > 0, false); + + return tryWrite(data, size); + } + + /*! + * Write a custom data type specified by the template typename used, + * with size being automatically deduced by the compiler (through the use of sizeof). + */ + template <typename T> + bool writeCustomType(const T& type) noexcept + { + return tryWrite(&type, sizeof(T)); + } + + // ------------------------------------------------------------------- + + /*! + * Commit all previous write operations to the ringbuffer. + * If a write operation has previously failed, this will reset/invalidate the previous write attempts. + */ + bool commitWrite() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, false); + + if (buffer->invalidateCommit) + { + buffer->wrtn = buffer->head; + buffer->invalidateCommit = false; + return false; + } + + // nothing to commit? + DISTRHO_SAFE_ASSERT_RETURN(buffer->head != buffer->wrtn, false); + + // all ok + buffer->head = buffer->wrtn; + errorWriting = false; + return true; + } + + // ------------------------------------------------------------------- + + /* + * Tie this ring buffer control to a ring buffer struct, optionally clearing its data. + */ + void setRingBuffer(BufferStruct* const ringBuf, const bool clearRingBufferData) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != ringBuf,); + + buffer = ringBuf; + + if (clearRingBufferData && ringBuf != nullptr) + clearData(); + } + + // ------------------------------------------------------------------- + +protected: + /** @internal try reading from the buffer, can fail. */ + bool tryRead(void* const buf, const uint32_t size) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, false); + #if defined(__clang__) + # pragma clang diagnostic push + # pragma clang diagnostic ignored "-Wtautological-pointer-compare" + #endif + DISTRHO_SAFE_ASSERT_RETURN(buffer->buf != nullptr, false); + #if defined(__clang__) + # pragma clang diagnostic pop + #endif + DISTRHO_SAFE_ASSERT_RETURN(buf != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(size > 0, false); + DISTRHO_SAFE_ASSERT_RETURN(size < buffer->size, false); + + // empty + if (buffer->head == buffer->tail) + return false; + + uint8_t* const bytebuf(static_cast<uint8_t*>(buf)); + + const uint32_t head(buffer->head); + const uint32_t tail(buffer->tail); + const uint32_t wrap((head > tail) ? 0 : buffer->size); + + if (size > wrap + head - tail) + { + if (! errorReading) + { + errorReading = true; + d_stderr2("RingBuffer::tryRead(%p, %lu): failed, not enough space", buf, (ulong)size); + } + return false; + } + + uint32_t readto(tail + size); + + if (readto > buffer->size) + { + readto -= buffer->size; + + if (size == 1) + { + std::memcpy(bytebuf, buffer->buf + tail, 1); + } + else + { + const uint32_t firstpart(buffer->size - tail); + std::memcpy(bytebuf, buffer->buf + tail, firstpart); + std::memcpy(bytebuf + firstpart, buffer->buf, readto); + } + } + else + { + std::memcpy(bytebuf, buffer->buf + tail, size); + + if (readto == buffer->size) + readto = 0; + } + + buffer->tail = readto; + errorReading = false; + return true; + } + + /** @internal try writing to the buffer, can fail. */ + bool tryWrite(const void* const buf, const uint32_t size) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(buf != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(size > 0, false); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(size < buffer->size, size, buffer->size, false); + + const uint8_t* const bytebuf(static_cast<const uint8_t*>(buf)); + + const uint32_t tail(buffer->tail); + const uint32_t wrtn(buffer->wrtn); + const uint32_t wrap((tail > wrtn) ? 0 : buffer->size); + + if (size >= wrap + tail - wrtn) + { + if (! errorWriting) + { + errorWriting = true; + d_stderr2("RingBuffer::tryWrite(%p, %lu): failed, not enough space", buf, (ulong)size); + } + buffer->invalidateCommit = true; + return false; + } + + uint32_t writeto(wrtn + size); + + if (writeto > buffer->size) + { + writeto -= buffer->size; + + if (size == 1) + { + std::memcpy(buffer->buf, bytebuf, 1); + } + else + { + const uint32_t firstpart(buffer->size - wrtn); + std::memcpy(buffer->buf + wrtn, bytebuf, firstpart); + std::memcpy(buffer->buf, bytebuf + firstpart, writeto); + } + } + else + { + std::memcpy(buffer->buf + wrtn, bytebuf, size); + + if (writeto == buffer->size) + writeto = 0; + } + + buffer->wrtn = writeto; + return true; + } + +private: + /** Buffer struct pointer. */ + BufferStruct* buffer; + + /** Whether read errors have been printed to terminal. */ + bool errorReading; + + /** Whether write errors have been printed to terminal. */ + bool errorWriting; + + DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION + DISTRHO_DECLARE_NON_COPYABLE(RingBufferControl) +}; + +template <class BufferStruct> +inline bool RingBufferControl<BufferStruct>::isDataAvailableForReading() const noexcept +{ + return (buffer != nullptr && buffer->head != buffer->tail); +} + +template <> +inline bool RingBufferControl<HeapBuffer>::isDataAvailableForReading() const noexcept +{ + return (buffer != nullptr && buffer->buf != nullptr && buffer->head != buffer->tail); +} + +// ----------------------------------------------------------------------- +// RingBuffer using heap space + +/** + RingBufferControl with a heap buffer. + This is a convenience class that provides a method for creating and destroying the heap data. + Requires the use of createBuffer(uint32_t) to make the ring buffer usable. +*/ +class HeapRingBuffer : public RingBufferControl<HeapBuffer> +{ +public: + /** Constructor. */ + HeapRingBuffer() noexcept + : heapBuffer(HeapBuffer_INIT) + { +#ifndef DISTRHO_PROPER_CPP11_SUPPORT + std::memset(&heapBuffer, 0, sizeof(heapBuffer)); +#endif + } + + /** Destructor. */ + ~HeapRingBuffer() noexcept override + { + if (heapBuffer.buf == nullptr) + return; + + delete[] heapBuffer.buf; + heapBuffer.buf = nullptr; + } + + /** Create a buffer of the specified size. */ + bool createBuffer(const uint32_t size) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(heapBuffer.buf == nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(size > 0, false); + + const uint32_t p2size = d_nextPowerOf2(size); + + try { + heapBuffer.buf = new uint8_t[p2size]; + } DISTRHO_SAFE_EXCEPTION_RETURN("HeapRingBuffer::createBuffer", false); + + heapBuffer.size = p2size; + setRingBuffer(&heapBuffer, true); + return true; + } + + /** Delete the previously allocated buffer. */ + void deleteBuffer() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(heapBuffer.buf != nullptr,); + + setRingBuffer(nullptr, false); + + delete[] heapBuffer.buf; + heapBuffer.buf = nullptr; + heapBuffer.size = 0; + } + + void copyFromAndClearOther(HeapRingBuffer& other) + { + DISTRHO_SAFE_ASSERT_RETURN(other.heapBuffer.size == heapBuffer.size,); + + std::memcpy(&heapBuffer, &other.heapBuffer, sizeof(HeapBuffer) - sizeof(uint8_t*)); + std::memcpy(heapBuffer.buf, other.heapBuffer.buf, sizeof(uint8_t) * heapBuffer.size); + other.clearData(); + } + +private: + /** The heap buffer used for this class. */ + HeapBuffer heapBuffer; + + DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION + DISTRHO_DECLARE_NON_COPYABLE(HeapRingBuffer) +}; + +// ----------------------------------------------------------------------- +// RingBuffer using small stack space + +/** + RingBufferControl with an included small stack buffer. + No setup is necessary, this class is usable as-is. +*/ +class SmallStackRingBuffer : public RingBufferControl<SmallStackBuffer> +{ +public: + /** Constructor. */ + SmallStackRingBuffer() noexcept + : stackBuffer(StackBuffer_INIT) + { +#ifndef DISTRHO_PROPER_CPP11_SUPPORT + std::memset(&stackBuffer, 0, sizeof(stackBuffer)); +#endif + setRingBuffer(&stackBuffer, true); + } + +private: + /** The small stack buffer used for this class. */ + SmallStackBuffer stackBuffer; + + DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION + DISTRHO_DECLARE_NON_COPYABLE(SmallStackRingBuffer) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_RING_BUFFER_HPP_INCLUDED