diff --git a/src/framebuffer2.h b/src/framebuffer2.h
--- a/src/framebuffer2.h
+++ b/src/framebuffer2.h
@@ -32,15 +32,20 @@ struct Range
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;
};
diff --git a/src/ringbuffer.cpp b/src/ringbuffer.cpp
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.cpp
@@ -0,0 +1,172 @@
+/*
+ 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 .
+*/
+
+#include
+
+#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;
+}
diff --git a/src/ringbuffer.h b/src/ringbuffer.h
new file mode 100644
--- /dev/null
+++ b/src/ringbuffer.h
@@ -0,0 +1,50 @@
+/*
+ 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 .
+*/
+
+#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
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ add_executable(Test EXCLUDE_FROM_ALL
../src/source.cpp
../src/indexbuffer.cpp
../src/linindexbuffer.cpp
+ ../src/ringbuffer.cpp
)
add_test(NAME test1 COMMAND Test)
qt5_use_modules(Test Widgets)
diff --git a/tests/test.cpp b/tests/test.cpp
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -24,6 +24,7 @@
#include "source.h"
#include "indexbuffer.h"
#include "linindexbuffer.h"
+#include "ringbuffer.h"
TEST_CASE("samplepack with no X", "[memory]")
{
@@ -236,3 +237,130 @@ TEST_CASE("LinIndexBuffer", "[memory, bu
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.);
+}