diff --git a/.gitignore b/.gitignore index 95c30da..ebae272 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # executable -run +CircularBuffer +CircularBuffer_test # gcov files *.gcov diff --git a/Makefile b/Makefile index 5ea8524..ebfeb2c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ BUILD_PATH = build BIN_PATH = $(BUILD_PATH)/bin # executable # -BIN_NAME = CppQuickStart +BIN_NAME = CircularBuffer # extensions # SRC_EXT = cpp diff --git a/README.md b/README.md index 1541520..942aae1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -CppQuickStart -============= -"Empty" project for a quicker start writing something in c++. +Circular Buffer +=============== +Efficient custom all-template fixed-size circular buffer in C++. diff --git a/include/CircularBuffer.hpp b/include/CircularBuffer.hpp new file mode 100644 index 0000000..f52eaef --- /dev/null +++ b/include/CircularBuffer.hpp @@ -0,0 +1,98 @@ +#ifndef DEF_CircularBuffer +#define DEF_CircularBuffer + +template +class CircularBuffer +{ + public: + /// Creates a circular buffer of size N containing items of type T, with values uninitialized. + CircularBuffer() + : data(NULL), + readPointer(0), + writePointer(0) + { data = new T[N]; } + + /// Creates a circular buffer of size N containing items of type T, initialized with the value initValue. + CircularBuffer(T const& initValue) + { + data = new T[N]; + Fill(initValue); + } + + /// Destroys the circular buffer. + ~CircularBuffer() { delete[] data; } + + void Fill(T const& initValue) + { + for(size_t i = 0 ; i < N ; i++) + data[i] = initValue; + } + + /// Sets the read pointer's absolute position. + void SetReadPointer(int i) { readPointer = BoundIndex(i); } + + /// Gets the read pointer's absolute position. + int GetReadPointer() const { return readPointer; } + + /// Sets the write pointer's absolute position. + void SetWritePointer(int i) { writePointer = BoundIndex(i); } + + /// Gets the write pointer's absolute position. + int GetWritePointer() const { return writePointer; } + + /// Returns a reference to the element i in the buffer. The given index is absolute. + T & operator[](int i) { return data[i]; } + + /// Returns a constant reference to the element i in the buffer. The given index is absolute. + T const& operator[](int i) const { return data[i]; } + + /// Returns a reference to the element i in the buffer. The given index is relative to the read pointer. + T & operator()(int i) { return data[(readPointer + i) % (int)N]; } + + /// Returns a constant reference to the element i in the buffer. The given index is relative to the read pointer. + T const& operator()(int i) const { return data[(readPointer + i) % (int)N]; } + + /// Writes the value at the current write pointer's location and moves it forward by 1. If the write pointer catches back with the read pointer, false is returned. + bool WriteFwd(T const& v) + { + data[writePointer] = v; + writePointer = BoundIndex(writePointer + 1); + return writePointer != readPointer; + } + + /// Writes the value at the current write pointer's location and moves it backwards by 1. If the write pointer catches back with the read pointer, false is returned. + bool WriteBck(T const& v) + { + data[writePointer] = v; + writePointer = BoundIndex(writePointer - 1); + return writePointer != readPointer; + } + + /// Reads the value at the current read pointer's location and moves it forward by 1. If the read pointer is co-located with the the write pointer at the time of the read, false is returned. + bool ReadFwd(T & v) + { + v = data[readPointer]; + int readPointerWhenRead = readPointer; + readPointer = BoundIndex(readPointer + 1); + return writePointer != readPointerWhenRead; + } + + /// Reads the value at the current read pointer's location and moves it backwards by 1. If the read pointer is co-located with the the write pointer at the time of the read, false is returned. + bool ReadBck(T & v) + { + v = data[readPointer]; + int readPointerWhenRead = readPointer; + readPointer = BoundIndex(readPointer - 1); + return writePointer != readPointerWhenRead; + } + + /// Bounds the index between 0 and N; + static inline int BoundIndex(int i) { return (i >= 0) ? i % (int)N : ((int)N + (i % (int)N)) % (int)N; } + + protected: + T *data; + int readPointer; //!< Read pointer location + int writePointer; //!< Write pointer location +}; + +#endif diff --git a/include/TestClass.hpp b/include/TestClass.hpp deleted file mode 100644 index 0b97a7b..0000000 --- a/include/TestClass.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef DEF_TestClass -#define DEF_TestClass - - - -#endif diff --git a/src/TestClass.cpp b/src/TestClass.cpp deleted file mode 100644 index d8b75fb..0000000 --- a/src/TestClass.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "TestClass.hpp" diff --git a/src/main.cpp b/src/main.cpp index 6ca6a6b..30d3f29 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,14 @@ #include #include -#include +#include using std::cout; using std::endl; int main() { - + return 0; } diff --git a/test/Makefile b/test/Makefile index 31152fd..88fd4ae 100644 --- a/test/Makefile +++ b/test/Makefile @@ -6,7 +6,7 @@ BUILD_PATH = build BIN_PATH = $(BUILD_PATH)/bin # executable # -BIN_NAME = CppQuickStart_test +BIN_NAME = CircularBuffer_test # extensions # SRC_EXT = cpp diff --git a/test/src/1_test.cpp b/test/src/1_test.cpp index 4d0920b..86b1b97 100644 --- a/test/src/1_test.cpp +++ b/test/src/1_test.cpp @@ -3,32 +3,214 @@ #include #include +#include #define print(x) PRINT_VAR(x); #define printvec(x) PRINT_VEC(x); #define printstr(x) PRINT_STR(x); -TEST_CASE( "Test case 1", "[test1]" ) +TEST_CASE( "Circular Buffer test 1", "[CircularBufferTest1]" ) { std::cout.precision(16); - SECTION( "Check almost equal" ) { - CHECK(check_almost_equal(1.00, 1.01, 0.1)); - CHECK(check_almost_equal(1.00, 3.01, 0.01)); - CHECK(check_almost_equal(1.00, 1.01, 0.001)); - REQUIRE(true); + SECTION( "Check BoundIndex" ) { + constexpr unsigned int N = 5; + + for(int i = 0 ; i < (int)(2*N) ; i++) + CHECK(CircularBuffer::BoundIndex(i) == (i % N)); + + CHECK(CircularBuffer::BoundIndex(0) == 0); + CHECK(CircularBuffer::BoundIndex(-1) == 4); + CHECK(CircularBuffer::BoundIndex(-2) == 3); + CHECK(CircularBuffer::BoundIndex(-3) == 2); + CHECK(CircularBuffer::BoundIndex(-4) == 1); + CHECK(CircularBuffer::BoundIndex(-5) == 0); } - SECTION( "Check almost equal on vectors" ) { - unsigned int N = 5; - std::vector v1(N), v2(N); - for (size_t i = 0; i < N; i++) { - v1[i] = double(i); - v2[i] = v1[i] + 0.0001; - } + SECTION( "Check constructors" ) { + constexpr unsigned int N = 5; + CircularBuffer buf(0); - CHECK(check_almost_equalV(v1, v2, 0.001)); - CHECK(check_almost_equalV(v1, v2, 0.0001)); - CHECK(check_almost_equalV(v1, v2, 0.00001)); + for(size_t i = 0 ; i < N ; i++) + CHECK(buf[i] == 0); + } + + SECTION( "Check operator[]" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + for(size_t i = 0 ; i < N ; i++) + CHECK(buf[i] == i); + } + + SECTION( "Check operator()" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetReadPointer(2); + + for(size_t i = 0 ; i < N ; i++) + CHECK(buf(i) == buf[(buf.GetReadPointer() + i) % (int)N]); + } + + SECTION( "Check ReadFwd() and ReadBck() nominal" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + int v; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetWritePointer(1); + buf.SetReadPointer(2); + + // read forwards + CHECK(buf.ReadFwd(v) == true); + CHECK(v == 2); + CHECK(buf.GetReadPointer() == 3); + + // read backwards + CHECK(buf.ReadBck(v) == true); + CHECK(v == 3); + CHECK(buf.GetReadPointer() == 2); + } + + SECTION( "Check ReadFwd() and ReadBck() error" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + int v; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetWritePointer(2); + buf.SetReadPointer(2); + + // read forwards + CHECK(buf.ReadFwd(v) == false); + CHECK(v == 2); + CHECK(buf.GetReadPointer() == 3); + + // read backwards + buf.SetWritePointer(3); + CHECK(buf.ReadBck(v) == false); + CHECK(v == 3); + CHECK(buf.GetReadPointer() == 2); + } + + SECTION( "Check ReadFwd() and ReadBck() wrap-around" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + int v; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetWritePointer(0); + buf.SetReadPointer(4); + + // read forwards + CHECK(buf.ReadFwd(v) == true); + CHECK(v == 4); + CHECK(buf.GetReadPointer() == 0); + + // read backwards + buf.SetWritePointer(3); + CHECK(buf.ReadBck(v) == true); + CHECK(v == 0); + CHECK(buf.GetReadPointer() == 4); + } + + SECTION( "Check WriteFwd() and WriteBck() nominal" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetWritePointer(2); + buf.SetReadPointer(0); + + // write forwards + CHECK(buf.WriteFwd(10) == true); + CHECK(buf.GetWritePointer() == 3); + CHECK(buf[2] == 10); + + // write backwards + CHECK(buf.WriteBck(15) == true); + CHECK(buf[3] == 15); + CHECK(buf.GetWritePointer() == 2); + } + + SECTION( "Check WriteFwd() and WriteBck() end-of-buffer" ) { + constexpr unsigned int N = 5; + CircularBuffer buf; + + for(size_t i = 0 ; i < N ; i++) + buf[i] = i; + + buf.SetWritePointer(2); + buf.SetReadPointer(3); + + // write forwards + CHECK(buf.WriteFwd(10) == false); // write pointer reaches read pointer after write + CHECK(buf.GetWritePointer() == 3); + CHECK(buf[2] == 10); + + // write backwards + buf.SetReadPointer(2); + CHECK(buf.WriteBck(15) == false); // write pointer reaches read pointer after write + CHECK(buf[3] == 15); + CHECK(buf.GetWritePointer() == 2); + } +} + +TEST_CASE( "Circular Buffer practical test", "[CircularBufferTestPractical]" ) +{ + std::cout.precision(16); + + SECTION( "Moving average" ) { + constexpr size_t Npts = 10; + std::vector x(Npts); + for(size_t i = 0 ; i < Npts ; i++) + x[i] = rand() % 100; + + PRINT_VEC(x); + + // Compute moving average with absolute coordinates + constexpr size_t N = 3; + std::vector xAvg(Npts-N); + + for(size_t i = 0 ; i < Npts-N ; i++) + xAvg[i] = (x[i] + x[i+1] + x[i+2]) / 3; + + PRINT_VEC(xAvg); + + // Compute moving average with circular buffer + std::vector xAvgBuf(Npts-N); + CircularBuffer buf; + + // Initialise the buffer + buf[0] = x[0]; + buf[1] = x[1]; + + buf.SetWritePointer(2); + + for(size_t i = 0 ; i < Npts-N ; i++) + { + buf.WriteFwd(x[i+2]); + xAvgBuf[i] = (buf[0] + buf[1] + buf[2]) / 3; + } + + PRINT_VEC(xAvgBuf); + + for(size_t i = 0 ; i < Npts-N ; i++) + CHECK(xAvg[i] == xAvgBuf[i]); } }