|  | /*
 * Carla Ring Buffer
 * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
 *
 * 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 const uint32_t size = 4096;
    uint32_t head, tail, wrtn;
    bool     invalidateCommit;
    uint8_t  buf[size];
};
struct BigStackBuffer {
    static const uint32_t size = 16384;
    uint32_t head, tail, wrtn;
    bool     invalidateCommit;
    uint8_t  buf[size];
};
struct HugeStackBuffer {
    static 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 BufferStruct>
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 <typename T>
    void readCustomType(T& type) noexcept
    {
        if (! tryRead(&type, sizeof(T)))
            std::memset(&type, 0, sizeof(T));
    }
    // -------------------------------------------------------------------
    void writeBool(const bool value) noexcept
    {
        tryWrite(&value, sizeof(bool));
    }
    void writeByte(const uint8_t value) noexcept
    {
        tryWrite(&value, sizeof(uint8_t));
    }
    void writeShort(const int16_t value) noexcept
    {
        tryWrite(&value, sizeof(int16_t));
    }
    void writeUShort(const uint16_t value) noexcept
    {
        tryWrite(&value, sizeof(uint16_t));
    }
    void writeInt(const int32_t value) noexcept
    {
        tryWrite(&value, sizeof(int32_t));
    }
    void writeUInt(const uint32_t value) noexcept
    {
        tryWrite(&value, sizeof(uint32_t));
    }
    void writeLong(const int64_t value) noexcept
    {
        tryWrite(&value, sizeof(int64_t));
    }
    void writeULong(const uint64_t value) noexcept
    {
        tryWrite(&value, sizeof(uint64_t));
    }
    void writeFloat(const float value) noexcept
    {
        tryWrite(&value, sizeof(float));
    }
    void writeDouble(const double value) noexcept
    {
        tryWrite(&value, sizeof(double));
    }
    void writeCustomData(const void* const data, const uint32_t size) noexcept
    {
        CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
        CARLA_SAFE_ASSERT_RETURN(size > 0,);
        tryWrite(data, size);
    }
    template <typename T>
    void writeCustomType(const T& type) noexcept
    {
        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);
        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<uint8_t*>(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, " P_SIZE "): failed, not enough space", buf, size);
            }
            return false;
        }
        uint32_t readto(tail + size);
        if (readto > fBuffer->size)
        {
            readto -= fBuffer->size;
            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_RETURN(size < fBuffer->size, false);
        const uint8_t* const bytebuf(static_cast<const uint8_t*>(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, " P_SIZE "): failed, not enough space", buf, size);
            }
            fBuffer->invalidateCommit = true;
            return false;
        }
        uint32_t writeto(wrtn + size);
        if (writeto > fBuffer->size)
        {
            writeto -= fBuffer->size;
            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_COPY_CLASS(CarlaRingBufferControl)
};
template <class BufferStruct>
inline bool CarlaRingBufferControl<BufferStruct>::isDataAvailableForReading() const noexcept
{
	return (fBuffer != nullptr && fBuffer->head != fBuffer->tail);
}
template <>
inline bool CarlaRingBufferControl<HeapBuffer>::isDataAvailableForReading() const noexcept
{
	return (fBuffer != nullptr && fBuffer->buf != nullptr && fBuffer->head != fBuffer->tail);
}
// -----------------------------------------------------------------------
// CarlaRingBuffer using heap space
class CarlaHeapRingBuffer : public CarlaRingBufferControl<HeapBuffer>
{
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_COPY_CLASS(CarlaHeapRingBuffer)
};
// -----------------------------------------------------------------------
// CarlaRingBuffer using small stack space
class CarlaSmallStackRingBuffer : public CarlaRingBufferControl<SmallStackBuffer>
{
public:
    CarlaSmallStackRingBuffer() noexcept
        : fStackBuffer(StackBuffer_INIT)
    {
        setRingBuffer(&fStackBuffer, true);
    }
private:
    SmallStackBuffer fStackBuffer;
    CARLA_PREVENT_VIRTUAL_HEAP_ALLOCATION
    CARLA_DECLARE_NON_COPY_CLASS(CarlaSmallStackRingBuffer)
};
// -----------------------------------------------------------------------
#endif // CARLA_RING_BUFFER_HPP_INCLUDED
 |