Changeset - 41cf55d3a08c
[Not reviewed]
stream
0 3 2
Hasan Yavuz ÖZDERYA - 8 years ago 2017-12-10 05:50:33
hy@ozderya.net
added ring buffer and some tests
5 files changed with 357 insertions and 1 deletions:
0 comments (0 inline, 0 general)
src/framebuffer2.h
Show inline comments
 
@@ -29,21 +29,26 @@ struct Range
 
};
 

	
 
/// Abstract base class for all frame buffers.
 
class FrameBuffer
 
{
 
public:
 
    /// Returns size of the buffer.
 
    virtual unsigned size() const = 0;
 
    /// Returns a sample from given index.
 
    virtual double sample(unsigned i) const = 0;
 
    /// Returns minimum and maximum of the buffer values.
 
    virtual Range limits() const = 0;
 
};
 

	
 
/// Common base class for index and writable frame buffers
 
class ResizableBuffer : public FrameBuffer
 
{
 
    /// Resize buffer
 
    /// Resize the buffer.
 
    ///
 
    /// @important Resizing to same value is an error.
 
    virtual void resize(unsigned n) = 0;
 
};
 

	
 
/// Abstract base class for writable frame buffers
 
class WFrameBuffer : public ResizableBuffer
 
{
src/ringbuffer.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot 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 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot 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.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QtGlobal>
 

	
 
#include "ringbuffer.h"
 

	
 
RingBuffer::RingBuffer(unsigned n)
 
{
 
    _size = n;
 
    data = new double[_size]();
 
    headIndex = 0;
 

	
 
    limInvalid = false;
 
    limCache = {0, 0};
 
}
 

	
 
RingBuffer::~RingBuffer()
 
{
 
    delete[] data;
 
}
 

	
 
unsigned RingBuffer::size() const
 
{
 
    return _size;
 
}
 

	
 
double RingBuffer::sample(unsigned i) const
 
{
 
    unsigned index = headIndex + i;
 
    if (index >= _size) index -= _size;
 
    return data[index];
 
}
 

	
 
Range RingBuffer::limits() const
 
{
 
    if (limInvalid) updateLimits();
 
    return limCache;
 
}
 

	
 
void RingBuffer::resize(unsigned n)
 
{
 
    Q_ASSERT(n != _size);
 

	
 
    int offset = (int) n - (int) _size;
 
    if (offset == 0) return;
 

	
 
    double* newData = new double[n];
 

	
 
    // move data to new array
 
    int fill_start = offset > 0 ? offset : 0;
 

	
 
    for (int i = fill_start; i < int(n); i++)
 
    {
 
        newData[i] = sample(i - offset);
 
    }
 

	
 
    // fill the beginning of the new data
 
    if (fill_start > 0)
 
    {
 
        for (int i = 0; i < fill_start; i++)
 
        {
 
            newData[i] = 0;
 
        }
 
    }
 

	
 
    // data is ready, clean up and re-point
 
    delete data;
 
    data = newData;
 
    headIndex = 0;
 
    _size = n;
 

	
 
    // invalidate bounding rectangle
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::addSamples(double* samples, unsigned n)
 
{
 
    unsigned shift = n;
 
    if (shift < _size)
 
    {
 
        unsigned x = _size - headIndex; // distance of `head` to end
 

	
 
        if (shift <= x) // there is enough room at the end of array
 
        {
 
            for (unsigned i = 0; i < shift; i++)
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 

	
 
            if (shift == x) // we used all the room at the end
 
            {
 
                headIndex = 0;
 
            }
 
            else
 
            {
 
                headIndex += shift;
 
            }
 
        }
 
        else // there isn't enough room
 
        {
 
            for (unsigned i = 0; i < x; i++) // fill the end part
 
            {
 
                data[i+headIndex] = samples[i];
 
            }
 
            for (unsigned i = 0; i < (shift-x); i++) // continue from the beginning
 
            {
 
                data[i] = samples[i+x];
 
            }
 
            headIndex = shift-x;
 
        }
 
    }
 
    else // number of new samples equal or bigger than current size (doesn't fit)
 
    {
 
        int x = shift - _size;
 
        for (unsigned i = 0; i < _size; i++)
 
        {
 
            data[i] = samples[i+x];
 
        }
 
        headIndex = 0;
 
    }
 

	
 
    // invalidate cache
 
    limInvalid = true;
 
}
 

	
 
void RingBuffer::clear()
 
{
 
    for (unsigned i=0; i < _size; i++)
 
    {
 
        data[i] = 0.;
 
    }
 

	
 
    limCache = {0, 0};
 
    limInvalid = false;
 
}
 

	
 
void RingBuffer::updateLimits() const
 
{
 
    limCache.start = data[0];
 
    limCache.end = data[0];
 

	
 
    for (unsigned i = 0; i < _size; i++)
 
    {
 
        if (data[i] > limCache.end)
 
        {
 
            limCache.end = data[i];
 
        }
 
        else if (data[i] < limCache.start)
 
        {
 
            limCache.start = data[i];
 
        }
 
    }
 

	
 
    limInvalid = false;
 
}
src/ringbuffer.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2017 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot 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 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot 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.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef RINGBUFFER_H
 
#define RINGBUFFER_H
 

	
 
// IMPORTANT TODO: rename to "framebuffer.h" when stream work is done.
 
#include "framebuffer2.h"
 

	
 
/// A fast buffer implementation for storing data.
 
class RingBuffer : public WFrameBuffer
 
{
 
public:
 
    RingBuffer(unsigned n);
 
    ~RingBuffer();
 

	
 
    virtual unsigned size() const;
 
    virtual double sample(unsigned i) const;
 
    virtual Range limits() const;
 
    virtual void resize(unsigned n);
 
    virtual void addSamples(double* samples, unsigned n);
 
    virtual void clear();
 

	
 
private:
 
    unsigned _size;            ///< size of `data`
 
    double* data;              ///< storage
 
    unsigned headIndex;        ///< indicates the actual `0` index of the ring buffer
 

	
 
    mutable bool limInvalid;   ///< Indicates that limits needs to be re-calculated
 
    mutable Range limCache;    ///< Cache for limits()
 
    void updateLimits() const; ///< Updates limits cache
 
};
 

	
 
#endif
tests/CMakeLists.txt
Show inline comments
 
@@ -26,12 +26,13 @@ add_executable(Test EXCLUDE_FROM_ALL
 
  test.cpp
 
  ../src/samplepack.cpp
 
  ../src/sink.cpp
 
  ../src/source.cpp
 
  ../src/indexbuffer.cpp
 
  ../src/linindexbuffer.cpp
 
  ../src/ringbuffer.cpp
 
  )
 
add_test(NAME test1 COMMAND Test)
 
qt5_use_modules(Test Widgets)
 

	
 
set(CMAKE_CTEST_COMMAND ctest -V)
 
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
tests/test.cpp
Show inline comments
 
@@ -21,12 +21,13 @@
 
#include "catch.hpp"
 

	
 
#include "samplepack.h"
 
#include "source.h"
 
#include "indexbuffer.h"
 
#include "linindexbuffer.h"
 
#include "ringbuffer.h"
 

	
 
TEST_CASE("samplepack with no X", "[memory]")
 
{
 
    SamplePack pack(100, 3, false);
 

	
 
    REQUIRE_FALSE(pack.hasX());
 
@@ -233,6 +234,133 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
 
    REQUIRE(l.start == -5.0);
 
    REQUIRE(l.end == 5.0);
 

	
 
    REQUIRE(buf.sample(0) == -5.0);
 
    REQUIRE(buf.sample(19) == 5.0);
 
}
 

	
 
TEST_CASE("RingBuffer sizing", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    REQUIRE(buf.size() == 10);
 

	
 
    buf.resize(5);
 
    REQUIRE(buf.size() == 5);
 

	
 
    buf.resize(15);
 
    REQUIRE(buf.size() == 15);
 
}
 

	
 
TEST_CASE("RingBuffer initial values should be 0", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 

	
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer data access", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i]);
 
    }
 

	
 
    buf.addSamples(values, 5);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer bigger should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(5);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 5);
 
    buf.resize(10);
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0);
 
    }
 
    for (unsigned i = 5; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i-5]);
 
    }
 
}
 

	
 
TEST_CASE("making RingBuffer smaller should keep end values", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.resize(5);
 

	
 
    REQUIRE(buf.size() == 5);
 
    for (unsigned i = 0; i < 5; i++)
 
    {
 
        REQUIRE(buf.sample(i) == values[i+5]);
 
    }
 
}
 

	
 
TEST_CASE("RingBuffer limits", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 

	
 
    buf.addSamples(values, 10);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(&values[9], 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 2.);
 
    REQUIRE(lim.end == 10.);
 

	
 
    buf.addSamples(values, 9);
 
    buf.addSamples(values, 1);
 
    lim = buf.limits();
 
    REQUIRE(lim.start == 1.);
 
    REQUIRE(lim.end == 9.);
 
}
 

	
 
TEST_CASE("RingBuffer clear", "[memory, buffer]")
 
{
 
    RingBuffer buf(10);
 
    double values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 

	
 
    buf.addSamples(values, 10);
 
    buf.clear();
 

	
 
    REQUIRE(buf.size() == 10);
 
    for (unsigned i = 0; i < 10; i++)
 
    {
 
        REQUIRE(buf.sample(i) == 0.);
 
    }
 
    auto lim = buf.limits();
 
    REQUIRE(lim.start == 0.);
 
    REQUIRE(lim.end == 0.);
 
}
0 comments (0 inline, 0 general)