Working basic circular buffer with associated unit tests.
This commit is contained in:
parent
1df7427f7d
commit
a34fac85fc
9 changed files with 305 additions and 31 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
# executable
|
# executable
|
||||||
run
|
CircularBuffer
|
||||||
|
CircularBuffer_test
|
||||||
|
|
||||||
# gcov files
|
# gcov files
|
||||||
*.gcov
|
*.gcov
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -6,7 +6,7 @@ BUILD_PATH = build
|
||||||
BIN_PATH = $(BUILD_PATH)/bin
|
BIN_PATH = $(BUILD_PATH)/bin
|
||||||
|
|
||||||
# executable #
|
# executable #
|
||||||
BIN_NAME = CppQuickStart
|
BIN_NAME = CircularBuffer
|
||||||
|
|
||||||
# extensions #
|
# extensions #
|
||||||
SRC_EXT = cpp
|
SRC_EXT = cpp
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
CppQuickStart
|
Circular Buffer
|
||||||
=============
|
===============
|
||||||
"Empty" project for a quicker start writing something in c++.
|
Efficient custom all-template fixed-size circular buffer in C++.
|
||||||
|
|
|
||||||
98
include/CircularBuffer.hpp
Normal file
98
include/CircularBuffer.hpp
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#ifndef DEF_CircularBuffer
|
||||||
|
#define DEF_CircularBuffer
|
||||||
|
|
||||||
|
template<size_t N, typename T>
|
||||||
|
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
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#ifndef DEF_TestClass
|
|
||||||
#define DEF_TestClass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include "TestClass.hpp"
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <TestClass.hpp>
|
#include <CircularBuffer.hpp>
|
||||||
|
|
||||||
using std::cout;
|
using std::cout;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ BUILD_PATH = build
|
||||||
BIN_PATH = $(BUILD_PATH)/bin
|
BIN_PATH = $(BUILD_PATH)/bin
|
||||||
|
|
||||||
# executable #
|
# executable #
|
||||||
BIN_NAME = CppQuickStart_test
|
BIN_NAME = CircularBuffer_test
|
||||||
|
|
||||||
# extensions #
|
# extensions #
|
||||||
SRC_EXT = cpp
|
SRC_EXT = cpp
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,214 @@
|
||||||
|
|
||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <utils_test.hpp>
|
#include <utils_test.hpp>
|
||||||
|
#include <CircularBuffer.hpp>
|
||||||
|
|
||||||
#define print(x) PRINT_VAR(x);
|
#define print(x) PRINT_VAR(x);
|
||||||
#define printvec(x) PRINT_VEC(x);
|
#define printvec(x) PRINT_VEC(x);
|
||||||
#define printstr(x) PRINT_STR(x);
|
#define printstr(x) PRINT_STR(x);
|
||||||
|
|
||||||
TEST_CASE( "Test case 1", "[test1]" )
|
TEST_CASE( "Circular Buffer test 1", "[CircularBufferTest1]" )
|
||||||
{
|
{
|
||||||
std::cout.precision(16);
|
std::cout.precision(16);
|
||||||
|
|
||||||
SECTION( "Check almost equal" ) {
|
SECTION( "Check BoundIndex" ) {
|
||||||
CHECK(check_almost_equal(1.00, 1.01, 0.1));
|
constexpr unsigned int N = 5;
|
||||||
CHECK(check_almost_equal(1.00, 3.01, 0.01));
|
|
||||||
CHECK(check_almost_equal(1.00, 1.01, 0.001));
|
for(int i = 0 ; i < (int)(2*N) ; i++)
|
||||||
REQUIRE(true);
|
CHECK(CircularBuffer<N,int>::BoundIndex(i) == (i % N));
|
||||||
|
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(0) == 0);
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(-1) == 4);
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(-2) == 3);
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(-3) == 2);
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(-4) == 1);
|
||||||
|
CHECK(CircularBuffer<N,int>::BoundIndex(-5) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION( "Check almost equal on vectors" ) {
|
SECTION( "Check constructors" ) {
|
||||||
unsigned int N = 5;
|
constexpr unsigned int N = 5;
|
||||||
std::vector<double> v1(N), v2(N);
|
CircularBuffer<N,int> buf(0);
|
||||||
for (size_t i = 0; i < N; i++) {
|
|
||||||
v1[i] = double(i);
|
for(size_t i = 0 ; i < N ; i++)
|
||||||
v2[i] = v1[i] + 0.0001;
|
CHECK(buf[i] == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK(check_almost_equalV(v1, v2, 0.001));
|
SECTION( "Check operator[]" ) {
|
||||||
CHECK(check_almost_equalV(v1, v2, 0.0001));
|
constexpr unsigned int N = 5;
|
||||||
CHECK(check_almost_equalV(v1, v2, 0.00001));
|
CircularBuffer<N,int> 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<N,int> 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<N,int> 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<N,int> 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<N,int> 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<N,int> 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<N,int> 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<int> 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<int> 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<int> xAvgBuf(Npts-N);
|
||||||
|
CircularBuffer<N,int> 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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue