From 454379357988ba3712813195d4a44b509a8337a0 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 5 Jul 2014 02:01:36 +0100 Subject: [PATCH] Rework ringbuffer class, add tests --- .gitignore | 1 + source/tests/CarlaRingBuffer.cpp | 191 +++++++++++++++ source/tests/Makefile | 6 +- source/utils/CarlaRingBuffer.hpp | 409 ++++++++++++++++++++----------- 4 files changed, 460 insertions(+), 147 deletions(-) create mode 100644 source/tests/CarlaRingBuffer.cpp diff --git a/.gitignore b/.gitignore index 2a3fa246d..ad9e3439b 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ data/windows/CarlaControl source/bridges/jackplugin/libjack.so.0 source/frontend/Makefile source/tests/ansi-pedantic-test_* +source/tests/CarlaRingBuffer source/tests/CarlaString source/tests/CarlaUtils source/tests/Exceptions diff --git a/source/tests/CarlaRingBuffer.cpp b/source/tests/CarlaRingBuffer.cpp new file mode 100644 index 000000000..2b3b87ae1 --- /dev/null +++ b/source/tests/CarlaRingBuffer.cpp @@ -0,0 +1,191 @@ +/* + * CarlaRingBuffer Tests + * Copyright (C) 2014 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. + */ + +#include "CarlaRingBuffer.hpp" + +// ----------------------------------------------------------------------- +// simple types + +template +static void test_CarlaRingBuffer1(CarlaRingBuffer& b) noexcept +{ + // start empty + assert(b.isEmpty()); + + // write all types + assert(b.writeBool(true)); + assert(b.writeByte(123)); + assert(b.writeShort(1234)); + assert(b.writeInt(99999123)); + assert(b.writeLong(0xffffffff)); + assert(b.writeFloat(0.0123f)); + assert(b.writeDouble(0.0123)); + + // should be considered empty until a commitWrite + assert(b.isEmpty()); + + // commit + assert(b.commitWrite()); + + // should have data now + assert(! b.isEmpty()); + + // read all types back + assert(b.readBool() == true); + assert(b.readByte() == 123); + assert(b.readShort() == 1234); + assert(b.readInt() == 99999123); + assert(b.readLong() == 0xffffffff); + assert(b.readFloat() == 0.0123f); + + // still has data + assert(! b.isEmpty()); + + assert(b.readDouble() == 0.0123); + + // now empty + assert(b.isEmpty()); +} + +// ----------------------------------------------------------------------- +// custom type + +struct BufferTestStruct { + BufferTestStruct() + : b(false), i(255), l(9999) {} + + bool b; + int32_t i; + char _pad[999]; + int64_t l; + + bool operator==(const BufferTestStruct& s) const noexcept + { + return (b == s.b && i == s.i && l == s.l); + } +}; + +template +static void test_CarlaRingBuffer2(CarlaRingBuffer& b) noexcept +{ + // start empty + assert(b.isEmpty()); + + // write unmodified + BufferTestStruct t1, t2; + assert(t1 == t2); + assert(b.writeCustomType(t1)); + assert(b.commitWrite()); + + // test read + b.readCustomType(t1); + assert(t1 == t2); + + // modify t1 + assert(b.writeCustomType(t1)); + assert(b.commitWrite()); + carla_zeroStruct(t1); + + // test read + b.readCustomType(t1); + assert(t1 == t2); + + // now empty + assert(b.isEmpty()); +} + +// ----------------------------------------------------------------------- +// custom data + +template +static void test_CarlaRingBuffer3(CarlaRingBuffer& b) noexcept +{ + static const char* const kLicense = "" + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License as\n" + "published by the Free Software Foundation; either version 2 of\n" + "the License, or any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "For a full copy of the GNU General Public License see the doc/GPL.txt file.\n" + "\n"; + + static const std::size_t kLicenseSize = std::strlen(kLicense); + + // start empty + assert(b.isEmpty()); + + // write big chunk + assert(b.writeCustomData(kLicense, kLicenseSize)); + + // still empty + assert(b.isEmpty()); + + // commit + assert(b.commitWrite()); + + // not empty + assert(! b.isEmpty()); + + // read data + char license[kLicenseSize+1]; + carla_zeroChar(license, kLicenseSize+1); + b.readCustomData(license, kLicenseSize); + + // now empty again + assert(b.isEmpty()); + + // test data integrity + assert(std::strcmp(license, kLicense) == 0); +} + +// ----------------------------------------------------------------------- + +int main() +{ + CarlaHeapRingBuffer heap; + CarlaStackRingBuffer stack; + + // small test first + heap.createBuffer(4096); + heap.deleteBuffer(); + heap.createBuffer(1); + heap.deleteBuffer(); + + // ok + heap.createBuffer(1024); + + test_CarlaRingBuffer1(heap); + test_CarlaRingBuffer1(stack); + test_CarlaRingBuffer2(heap); + test_CarlaRingBuffer2(stack); + + // test the big chunk a couple of times to ensure circular writing + for (int i=0; i<20; ++i) + { + test_CarlaRingBuffer3(heap); + test_CarlaRingBuffer3(stack); + } + + return 0; +} + +// ----------------------------------------------------------------------- diff --git a/source/tests/Makefile b/source/tests/Makefile index 92d4a38f7..f6d7a17fe 100644 --- a/source/tests/Makefile +++ b/source/tests/Makefile @@ -40,7 +40,7 @@ endif # -------------------------------------------------------------- TARGETS = ansi-pedantic-test_c ansi-pedantic-test_c99 -TARGETS += CarlaString CarlaUtils Exceptions Print RDF +TARGETS += CarlaRingBuffer CarlaString CarlaUtils Exceptions Print RDF # ansi-pedantic-test_cxx ansi-pedantic-test_cxx11 # TARGETS += EngineEvents PipeServer RtLinkedList RtLinkedListGnu @@ -63,6 +63,10 @@ ansi-pedantic-test_cxx11: ansi-pedantic-test.cpp ../backend/Carla*.h # -------------------------------------------------------------- +CarlaRingBuffer: CarlaRingBuffer.cpp ../utils/CarlaRingBuffer.hpp + $(CXX) $< $(PEDANTIC_CXX_FLAGS) -o $@ + valgrind --leak-check=full ./$@ + CarlaString: CarlaString.cpp ../utils/CarlaString.hpp $(CXX) $< $(PEDANTIC_CXX_FLAGS) -o $@ valgrind --leak-check=full ./$@ diff --git a/source/utils/CarlaRingBuffer.hpp b/source/utils/CarlaRingBuffer.hpp index d85de84b9..b3f254f27 100644 --- a/source/utils/CarlaRingBuffer.hpp +++ b/source/utils/CarlaRingBuffer.hpp @@ -18,54 +18,68 @@ #ifndef CARLA_RING_BUFFER_HPP_INCLUDED #define CARLA_RING_BUFFER_HPP_INCLUDED -#include "CarlaUtils.hpp" - -#if 0 //ndef CARLA_OS_WIN -# include -# ifdef CARLA_OS_MAC -# include -# endif -#endif +#include "CarlaMathUtils.hpp" // ----------------------------------------------------------------------- -// RingBuffer structs +// 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; - int32_t head, tail, written; - bool invalidateCommit; - char* buf; + uint32_t size; + std::size_t head, tail, wrtn; + bool invalidateCommit; + uint8_t* buf; - HeapBuffer& operator=(const HeapBuffer& rb) noexcept + void copyDataFrom(const HeapBuffer& rb) noexcept { - CARLA_SAFE_ASSERT_RETURN(size == rb.size, *this); + CARLA_SAFE_ASSERT_RETURN(size == rb.size,); size = rb.size; head = rb.head; tail = rb.tail; - written = rb.written; + wrtn = rb.wrtn; invalidateCommit = rb.invalidateCommit; std::memcpy(buf, rb.buf, size); - - return *this; } }; struct StackBuffer { static const uint32_t size = 4096; - int32_t head, tail, written; - bool invalidateCommit; - char buf[size]; + std::size_t head, tail, wrtn; + bool invalidateCommit; + uint8_t buf[size]; }; // ----------------------------------------------------------------------- -// RingBufferControl templated class +// CarlaRingBuffer templated class template -class RingBufferControl +class CarlaRingBuffer { public: - RingBufferControl(BufferStruct* const ringBuf) noexcept + CarlaRingBuffer() noexcept + : fBuffer(nullptr) {} + + CarlaRingBuffer(BufferStruct* const ringBuf) noexcept : fBuffer(ringBuf) { if (ringBuf != nullptr) @@ -78,22 +92,10 @@ public: fBuffer->head = 0; fBuffer->tail = 0; - fBuffer->written = 0; + fBuffer->wrtn = 0; fBuffer->invalidateCommit = false; - if (fBuffer->size > 0) - carla_zeroChar(fBuffer->buf, fBuffer->size); - } - - void setRingBuffer(BufferStruct* const ringBuf, const bool reset) noexcept - { - CARLA_SAFE_ASSERT_RETURN(ringBuf != nullptr,); - CARLA_SAFE_ASSERT_RETURN(ringBuf != fBuffer,); - - fBuffer = ringBuf; - - if (reset) - clear(); + carla_zeroBytes(fBuffer->buf, fBuffer->size); } // ------------------------------------------------------------------- @@ -104,214 +106,329 @@ public: if (fBuffer->invalidateCommit) { - memoryBarrier(); - fBuffer->written = fBuffer->head; + fBuffer->wrtn = fBuffer->head; fBuffer->invalidateCommit = false; return false; } - else - { - fBuffer->head = fBuffer->written; - return true; - } + + // nothing to commit? + CARLA_SAFE_ASSERT_RETURN(fBuffer->head != fBuffer->wrtn, false); + + // all ok + fBuffer->head = fBuffer->wrtn; + return true; } - bool isDataAvailable() const noexcept + bool isDataAvailableForReading() const noexcept { CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); return (fBuffer->head != fBuffer->tail); } -#if 0 - void lockMemory() const noexcept + bool isEmpty() const noexcept { - CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr, false); -#ifdef CARLA_OS_WIN - try { - ::VirtualLock(fBuffer, sizeof(BufferStruct)); - ::VirtualLock(fBuffer->buf, fBuffer->size); - } CARLA_SAFE_EXCEPTION("RingBufferControl::lockMemory"); -#else - ::mlock(fBuffer, sizeof(BufferStruct)); - ::mlock(fBuffer->buf, fBuffer->size); -#endif + return (fBuffer->head == fBuffer->tail); } -#endif // ------------------------------------------------------------------- - char readChar() noexcept + bool readBool() noexcept { - char c = '\0'; - tryRead(&c, sizeof(char)); - return c; + bool b = false; + return tryRead(&b, sizeof(bool)) ? b : false; + } + + int8_t readByte() noexcept + { + int8_t b = 0; + return tryRead(&b, sizeof(int8_t)) ? b : 0; + } + + uint8_t readUByte() noexcept + { + int8_t ub = -1; + return (tryRead(&ub, sizeof(int8_t)) && ub >= 0 && ub <= INT_LEAST8_MAX) ? static_cast(ub) : 0; + } + + int16_t readShort() noexcept + { + int16_t s = 0; + return tryRead(&s, sizeof(int16_t)) ? s : 0; + } + + uint16_t readUShort() noexcept + { + int16_t us = -1; + return (tryRead(&us, sizeof(int16_t)) && us >= 0 && us <= INT_LEAST16_MAX) ? static_cast(us) : 0; } int32_t readInt() noexcept { int32_t i = 0; - tryRead(&i, sizeof(int32_t)); - return i; + return tryRead(&i, sizeof(int32_t)) ? i : 0; } uint32_t readUInt() noexcept { - int32_t i = -1; - tryRead(&i, sizeof(int32_t)); - return (i >= 0) ? static_cast(i) : 0; + int32_t ui = -1; + return (tryRead(&ui, sizeof(int32_t)) && ui >= 0 && ui <= INT_LEAST32_MAX) ? static_cast(ui) : 0; } int64_t readLong() noexcept { int64_t l = 0; - tryRead(&l, sizeof(int64_t)); - return l; + return tryRead(&l, sizeof(int64_t)) ? l : 0; + } + + uint64_t readULong() noexcept + { + int64_t ul = -1; + return (tryRead(&ul, sizeof(int64_t)) && ul >= 0 && ul <= INT_LEAST64_MAX) ? static_cast(ul) : 0; } float readFloat() noexcept { float f = 0.0f; - tryRead(&f, sizeof(float)); - return f; + 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 std::size_t size) noexcept + { + if (! tryRead(data, size)) + carla_zeroBytes(data, size); + } + + template + void readCustomType(T& type) noexcept + { + if (! tryRead(&type, sizeof(T))) + carla_zeroStruct(type); } // ------------------------------------------------------------------- - void writeChar(const char value) noexcept + bool writeBool(const bool value) noexcept + { + return tryWrite(&value, sizeof(bool)); + } + + bool writeByte(const int8_t value) noexcept + { + return tryWrite(&value, sizeof(int8_t)); + } + + bool writeShort(const int16_t value) noexcept + { + return tryWrite(&value, sizeof(int16_t)); + } + + bool writeInt(const int32_t value) noexcept + { + return tryWrite(&value, sizeof(int32_t)); + } + + bool writeLong(const int64_t value) noexcept + { + return tryWrite(&value, sizeof(int64_t)); + } + + bool writeFloat(const float value) noexcept { - tryWrite(&value, sizeof(char)); + return tryWrite(&value, sizeof(float)); } - void writeInt(const int32_t value) noexcept + bool writeDouble(const double value) noexcept { - tryWrite(&value, sizeof(int32_t)); + return tryWrite(&value, sizeof(double)); } - void writeLong(const int64_t value) noexcept + bool writeCustomData(const void* const value, const std::size_t size) noexcept { - tryWrite(&value, sizeof(int64_t)); + return tryWrite(value, size); } - void writeFloat(const float value) noexcept + template + bool writeCustomType(const T& value) noexcept { - tryWrite(&value, sizeof(float)); + return tryWrite(&value, sizeof(T)); } // ------------------------------------------------------------------- protected: - void tryRead(void* const buf, const size_t size) noexcept + void setRingBuffer(BufferStruct* const ringBuf, const bool reset) noexcept { - CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); - CARLA_SAFE_ASSERT_RETURN(buf != nullptr,); - CARLA_SAFE_ASSERT_RETURN(size != 0,); - CARLA_SAFE_ASSERT_RETURN(size < fBuffer->size,); + CARLA_SAFE_ASSERT_RETURN(ringBuf != fBuffer,); + + fBuffer = ringBuf; + + if (reset && ringBuf != nullptr) + clear(); + } + + // ------------------------------------------------------------------- - // this should not happen - CARLA_ASSERT(fBuffer->head >= 0); - CARLA_ASSERT(fBuffer->tail >= 0); - CARLA_ASSERT(fBuffer->written >= 0); +private: + BufferStruct* fBuffer; + + bool tryRead(void* const buf, const std::size_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; + return false; - char* const charbuf(static_cast(buf)); + uint8_t* const bytebuf(static_cast(buf)); - const size_t head(static_cast(fBuffer->head)); - const size_t tail(static_cast(fBuffer->tail)); - const size_t wrap((head < tail) ? fBuffer->size : 0); + const std::size_t head(fBuffer->head); + const std::size_t tail(fBuffer->tail); + const std::size_t wrap((head > tail) ? 0 : fBuffer->size); - if (head - tail + wrap < size) + if (size > wrap + head - tail) { - carla_stderr2("RingBufferControl::tryRead() - failed"); - return; + carla_stderr2("CarlaRingBuffer::tryRead(%p, " P_SIZE "): failed, not enough space", buf, size); + return false; } - size_t readto = tail + size; + std::size_t readto(tail + size); - if (readto >= fBuffer->size) + if (readto > fBuffer->size) { readto -= fBuffer->size; - const size_t firstpart(fBuffer->size - tail); - std::memcpy(charbuf, fBuffer->buf + tail, firstpart); - std::memcpy(charbuf + firstpart, fBuffer->buf, readto); + const std::size_t firstpart(fBuffer->size - tail); + std::memcpy(bytebuf, fBuffer->buf + tail, firstpart); + std::memcpy(bytebuf + firstpart, fBuffer->buf, readto); } else { - std::memcpy(charbuf, fBuffer->buf + tail, size); + std::memcpy(bytebuf, fBuffer->buf + tail, size); + + if (readto == fBuffer->size) + readto = 0; } - memoryBarrier(); - fBuffer->tail = static_cast(readto); + fBuffer->tail = readto; + return true; } - void tryWrite(const void* const buf, const size_t size) noexcept + bool tryWrite(const void* const buf, const std::size_t size) noexcept { - CARLA_SAFE_ASSERT_RETURN(fBuffer != nullptr,); - CARLA_SAFE_ASSERT_RETURN(buf != nullptr,); - CARLA_SAFE_ASSERT_RETURN(size != 0,); - CARLA_SAFE_ASSERT_RETURN(size < fBuffer->size,); - - // this should not happen - CARLA_ASSERT(fBuffer->head >= 0); - CARLA_ASSERT(fBuffer->tail >= 0); - CARLA_ASSERT(fBuffer->written >= 0); + 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 char* const charbuf(static_cast(buf)); + const uint8_t* const bytebuf(static_cast(buf)); - const size_t tail(static_cast(fBuffer->tail)); - const size_t wrtn(static_cast(fBuffer->written)); - const size_t wrap((tail <= wrtn) ? fBuffer->size : 0); + const std::size_t tail(fBuffer->tail); + const std::size_t wrtn(fBuffer->wrtn); + const std::size_t wrap((tail > wrtn) ? 0 : fBuffer->size); - if (tail - wrtn + wrap <= size) + if (size >= wrap + tail - wrtn) { - carla_stderr2("RingBufferControl::tryWrite() - buffer full!"); + carla_stderr2("CarlaRingBuffer::tryWrite(%p, " P_SIZE "): failed, not enough space", buf, size); fBuffer->invalidateCommit = true; - return; + return false; } - size_t writeto = wrtn + size; + std::size_t writeto(wrtn + size); - if (writeto >= fBuffer->size) + if (writeto > fBuffer->size) { writeto -= fBuffer->size; - const size_t firstpart(fBuffer->size - wrtn); - std::memcpy(fBuffer->buf + wrtn, charbuf, firstpart); - std::memcpy(fBuffer->buf, charbuf + firstpart, writeto); + const std::size_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, charbuf, size); + std::memcpy(fBuffer->buf + wrtn, bytebuf, size); + + if (writeto == fBuffer->size) + writeto = 0; } - memoryBarrier(); - fBuffer->written = static_cast(writeto); + fBuffer->wrtn = writeto; + return true; } -private: - BufferStruct* fBuffer; + CARLA_PREVENT_HEAP_ALLOCATION + CARLA_DECLARE_NON_COPY_CLASS(CarlaRingBuffer) +}; + +// ----------------------------------------------------------------------- +// CarlaRingBuffer using heap space + +class CarlaHeapRingBuffer : public CarlaRingBuffer +{ +public: + CarlaHeapRingBuffer() noexcept + : CarlaRingBuffer() {} + + ~CarlaHeapRingBuffer() noexcept + { + if (fHeapBuffer.buf == nullptr) + return; + + delete[] fHeapBuffer.buf; + fHeapBuffer.buf = nullptr; + } + + void createBuffer(const uint32_t size) + { + CARLA_SAFE_ASSERT_RETURN(size > 0,); + + fHeapBuffer.size = carla_nextPowerOf2(size); + fHeapBuffer.buf = new uint8_t[fHeapBuffer.size]; + + setRingBuffer(&fHeapBuffer, true); + } - static void memoryBarrier() noexcept + void deleteBuffer() noexcept { - // this breaks win32<=>linux plugin bridges -#if 0 - try { -#if defined(CARLA_OS_MAC) - ::OSMemoryBarrier(); -#elif defined(CARLA_OS_WIN) - ::MemoryBarrier(); -#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 401 - ::__sync_synchronize(); -#endif - } CARLA_SAFE_EXCEPTION("RingBufferControl::memoryBarrier"); -#endif + CARLA_SAFE_ASSERT_RETURN(fHeapBuffer.buf != nullptr,); + + setRingBuffer(nullptr, false); + + delete[] fHeapBuffer.buf; + fHeapBuffer.buf = nullptr; + fHeapBuffer.size = 0; } +private: + HeapBuffer fHeapBuffer; + + CARLA_PREVENT_HEAP_ALLOCATION + CARLA_DECLARE_NON_COPY_CLASS(CarlaHeapRingBuffer) +}; + +// ----------------------------------------------------------------------- +// CarlaRingBuffer using stack space + +class CarlaStackRingBuffer : public CarlaRingBuffer +{ +public: + CarlaStackRingBuffer() noexcept + : CarlaRingBuffer(&fStackBuffer) {} + +private: + StackBuffer fStackBuffer; + CARLA_PREVENT_HEAP_ALLOCATION - CARLA_DECLARE_NON_COPY_CLASS(RingBufferControl) + CARLA_DECLARE_NON_COPY_CLASS(CarlaStackRingBuffer) }; // -----------------------------------------------------------------------