573 lines
15 KiB
C++
573 lines
15 KiB
C++
/*
|
|
* Carla Ring Buffer
|
|
* Copyright (C) 2013-2023 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 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 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 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 <typename T>
|
|
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<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, %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<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, %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 <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_COPYABLE(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_COPYABLE(CarlaSmallStackRingBuffer)
|
|
};
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
#endif // CARLA_RING_BUFFER_HPP_INCLUDED
|