view 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 source

/*
 * 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