Browse Source

New ring buffer

Kajetan Johannes Hammerle 2 năm trước cách đây
mục cha
commit
3efaeed7b8
2 tập tin đã thay đổi với 310 bổ sung40 xóa
  1. 97 21
      data/RingBuffer.h
  2. 213 19
      tests/RingBufferTests.cpp

+ 97 - 21
data/RingBuffer.h

@@ -1,25 +1,74 @@
 #ifndef CORE_RINGBUFFER_H
 #define CORE_RINGBUFFER_H
 
-#include "data/Array.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename T, int N>
     class RingBuffer final {
-        Array<T, N> data;
-        int writeIndex = 0;
-        int readIndex = 0;
-        int values = 0;
+        static_assert(N > 0, "RingBuffer size must be positive");
+
+        struct alignas(T) Aligned {
+            char data[sizeof(T)];
+        };
+
+        Aligned data[static_cast<unsigned int>(N)];
+        int writeIndex;
+        int readIndex;
+        int values;
 
     public:
-        bool add(const T& t) {
-            if(values >= N) {
-                return true;
+        RingBuffer() : writeIndex(0), readIndex(0), values(0) {
+        }
+
+        RingBuffer(const RingBuffer& other) : RingBuffer() {
+            copy(other);
+        }
+
+        RingBuffer(RingBuffer&& other) : RingBuffer() {
+            move(Core::move(other));
+            other.clear();
+        }
+
+        ~RingBuffer() {
+            clear();
+        }
+
+        RingBuffer& operator=(const RingBuffer& other) {
+            if(&other != this) {
+                clear();
+                copy(other);
             }
-            data[writeIndex] = t;
+            return *this;
+        }
+
+        RingBuffer& operator=(RingBuffer&& other) {
+            if(&other != this) {
+                clear();
+                move(Core::move(other));
+                other.clear();
+            }
+            return *this;
+        }
+
+        // returns a nullptr on error
+        template<typename... Args>
+        check_return T* add(Args&&... args) {
+            if(getLength() >= N) {
+                return nullptr;
+            }
+            T* t = new(data + writeIndex) T(Core::forward<Args>(args)...);
             writeIndex = (writeIndex + 1) % N;
             values++;
-            return false;
+            return t;
+        }
+
+        T& operator[](int index) {
+            return reinterpret_cast<T*>(data)[(index + readIndex) % N];
+        }
+
+        const T& operator[](int index) const {
+            return reinterpret_cast<const T*>(data)[(index + readIndex) % N];
         }
 
         int getLength() const {
@@ -27,30 +76,57 @@ namespace Core {
         }
 
         bool canRemove() const {
-            return values > 0;
+            return getLength() > 0;
         }
 
-        bool remove() {
-            if(values <= 0) {
+        void clear() {
+            for(int i = 0; i < getLength(); i++) {
+                (*this)[i].~T();
+            }
+            writeIndex = 0;
+            readIndex = 0;
+            values = 0;
+        }
+
+        // returns true on error
+        check_return bool remove() {
+            if(!canRemove()) {
                 return true;
             }
             values--;
+            (*this)[0].~T();
             readIndex = (readIndex + 1) % N;
             return false;
         }
 
-        T& operator[](int index) {
-            return data[(index + readIndex) % N];
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
+            for(int i = 0; i < getLength() - 1; i++) {
+                if(s.append((*this)[i]) || s.append(", ")) {
+                    return true;
+                }
+            }
+            if(getLength() > 0 && s.append((*this)[getLength() - 1])) {
+                return true;
+            }
+            return s.append("]");
         }
 
-        const T& operator[](int index) const {
-            return data[(index + readIndex) % N];
+    private:
+        void copy(const RingBuffer& other) {
+            for(int i = 0; i < other.getLength(); i++) {
+                (void)add(other[i]);
+            }
         }
 
-        void clear() {
-            writeIndex = 0;
-            readIndex = 0;
-            values = 0;
+        void move(RingBuffer&& other) {
+            for(int i = 0; i < other.getLength(); i++) {
+                (void)add(Core::move(other[i]));
+            }
         }
     };
 }

+ 213 - 19
tests/RingBufferTests.cpp

@@ -3,11 +3,64 @@
 #include "data/RingBuffer.h"
 #include "test/Test.h"
 
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(const T& t) {
+    String s;
+    CORE_TEST_FALSE(s.append(t));
+    return s;
+}
+
+struct Tester final {
+    static int ids;
+    static int sum;
+    int id;
+
+    Tester() : id(++ids) {
+        sum += id;
+    }
+
+    Tester(const Tester&) : id(++ids) {
+        sum += id;
+    }
+
+    Tester(Tester&&) : id(++ids) {
+        sum += id;
+    }
+
+    Tester& operator=(const Tester&) {
+        return *this;
+    }
+
+    Tester& operator=(Tester&&) {
+        return *this;
+    }
+
+    ~Tester() {
+        sum -= id;
+    }
+
+    // returns true on error
+    template<int L>
+    check_return bool toString(Core::ArrayString<L>& s) const {
+        return s.append(id);
+    }
+};
+
+int Tester::ids = 0;
+int Tester::sum = 0;
+
+static void resetTester() {
+    Tester::ids = 0;
+    Tester::sum = 0;
+}
+
 static void testReadAndWrite() {
     Core::RingBuffer<int, 5> buffer;
     CORE_TEST_FALSE(buffer.canRemove());
     CORE_TEST_EQUAL(0, buffer.getLength());
-    buffer.add(4);
+    CORE_TEST_NOT_NULL(buffer.add(4));
     CORE_TEST_EQUAL(1, buffer.getLength());
     CORE_TEST_TRUE(buffer.canRemove());
     CORE_TEST_EQUAL(4, buffer[0]);
@@ -18,17 +71,12 @@ static void testReadAndWrite() {
 
 static void testOverflow() {
     Core::RingBuffer<int, 3> buffer;
-    bool r1 = buffer.add(1);
-    bool r2 = buffer.add(2);
-    bool r3 = buffer.add(3);
-    bool r4 = buffer.add(4);
-    bool r5 = buffer.add(5);
+    CORE_TEST_NOT_NULL(buffer.add(1));
+    CORE_TEST_NOT_NULL(buffer.add(2));
+    CORE_TEST_NOT_NULL(buffer.add(3));
+    CORE_TEST_NULL(buffer.add(4));
+    CORE_TEST_NULL(buffer.add(5));
     CORE_TEST_EQUAL(3, buffer.getLength());
-    CORE_TEST_FALSE(r1);
-    CORE_TEST_FALSE(r2);
-    CORE_TEST_FALSE(r3);
-    CORE_TEST_TRUE(r4);
-    CORE_TEST_TRUE(r5);
     CORE_TEST_EQUAL(1, buffer[0]);
     CORE_TEST_FALSE(buffer.remove());
     CORE_TEST_EQUAL(2, buffer.getLength());
@@ -43,10 +91,10 @@ static void testOverflow() {
 
 static void testRefill() {
     Core::RingBuffer<int, 3> buffer;
-    buffer.add(1);
-    buffer.add(2);
-    buffer.add(3);
-    buffer.add(4);
+    CORE_TEST_NOT_NULL(buffer.add(1));
+    CORE_TEST_NOT_NULL(buffer.add(2));
+    CORE_TEST_NOT_NULL(buffer.add(3));
+    CORE_TEST_NULL(buffer.add(4));
     CORE_TEST_EQUAL(3, buffer.getLength());
     CORE_TEST_TRUE(buffer.canRemove());
     CORE_TEST_EQUAL(1, buffer[0]);
@@ -58,8 +106,8 @@ static void testRefill() {
     CORE_TEST_EQUAL(0, buffer.getLength());
 
     CORE_TEST_FALSE(buffer.canRemove());
-    buffer.add(5);
-    buffer.add(6);
+    CORE_TEST_NOT_NULL(buffer.add(5));
+    CORE_TEST_NOT_NULL(buffer.add(6));
     CORE_TEST_EQUAL(2, buffer.getLength());
     CORE_TEST_TRUE(buffer.canRemove());
     CORE_TEST_EQUAL(5, buffer[0]);
@@ -72,17 +120,163 @@ static void testRefill() {
 
 static void testClear() {
     Core::RingBuffer<int, 3> buffer;
-    buffer.add(1);
-    buffer.add(2);
+    CORE_TEST_NOT_NULL(buffer.add(1));
+    CORE_TEST_NOT_NULL(buffer.add(2));
     CORE_TEST_EQUAL(2, buffer.getLength());
     buffer.clear();
     CORE_TEST_FALSE(buffer.canRemove());
     CORE_TEST_EQUAL(0, buffer.getLength());
 }
 
+static void testConstructDestruct() {
+    resetTester();
+    Core::RingBuffer<Tester, 3> buffer;
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(1, Tester::sum);
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(3, Tester::sum);
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(6, Tester::sum);
+
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(5, Tester::sum);
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(3, Tester::sum);
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
+static void testCopyDestruct() {
+    resetTester();
+    {
+        Core::RingBuffer<Tester, 3> buffer;
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(1, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(3, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(6, Tester::sum);
+        {
+            Core::RingBuffer<Tester, 3> copy = buffer;
+            CORE_TEST_EQUAL(6 + 4 + 5 + 6, Tester::sum);
+        }
+        CORE_TEST_EQUAL(6, Tester::sum);
+    }
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
+static void testCopyAssignmentDestruct() {
+    resetTester();
+    {
+        Core::RingBuffer<Tester, 3> buffer;
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(1, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(3, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(6, Tester::sum);
+        {
+            Core::RingBuffer<Tester, 3> copy;
+            copy = buffer;
+            CORE_TEST_EQUAL(6 + 4 + 5 + 6, Tester::sum);
+        }
+        CORE_TEST_EQUAL(6, Tester::sum);
+    }
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
+static void testMoveDestruct() {
+    resetTester();
+    {
+        Core::RingBuffer<Tester, 3> buffer;
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(1, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(3, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(6, Tester::sum);
+        {
+            Core::RingBuffer<Tester, 3> move = Core::move(buffer);
+            CORE_TEST_EQUAL(4 + 5 + 6, Tester::sum);
+            CORE_TEST_EQUAL(0, buffer.getLength());
+        }
+        CORE_TEST_EQUAL(0, Tester::sum);
+    }
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
+static void testMoveAssignmentDestruct() {
+    resetTester();
+    {
+        Core::RingBuffer<Tester, 3> buffer;
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(1, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(3, Tester::sum);
+        CORE_TEST_NOT_NULL(buffer.add());
+        CORE_TEST_EQUAL(6, Tester::sum);
+        {
+            Core::RingBuffer<Tester, 3> move;
+            move = Core::move(buffer);
+            CORE_TEST_EQUAL(4 + 5 + 6, Tester::sum);
+            CORE_TEST_EQUAL(0, buffer.getLength());
+        }
+        CORE_TEST_EQUAL(0, Tester::sum);
+    }
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
+static void testOverall() {
+    resetTester();
+    Core::RingBuffer<Tester, 3> buffer;
+
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(build("[1, 2, 3]"), build(buffer));
+    CORE_TEST_EQUAL(3, buffer.getLength());
+    CORE_TEST_EQUAL(6, Tester::sum);
+
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(build("[2, 3]"), build(buffer));
+    CORE_TEST_EQUAL(2, buffer.getLength());
+    CORE_TEST_EQUAL(5, Tester::sum);
+
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(build("[2, 3, 4]"), build(buffer));
+    CORE_TEST_EQUAL(3, buffer.getLength());
+    CORE_TEST_EQUAL(9, Tester::sum);
+
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(build("[3, 4]"), build(buffer));
+    CORE_TEST_EQUAL(2, buffer.getLength());
+    CORE_TEST_EQUAL(7, Tester::sum);
+
+    CORE_TEST_NOT_NULL(buffer.add());
+    CORE_TEST_EQUAL(build("[3, 4, 5]"), build(buffer));
+    CORE_TEST_EQUAL(3, buffer.getLength());
+    CORE_TEST_EQUAL(12, Tester::sum);
+
+    CORE_TEST_FALSE(buffer.remove());
+    CORE_TEST_EQUAL(build("[4, 5]"), build(buffer));
+    CORE_TEST_EQUAL(2, buffer.getLength());
+    CORE_TEST_EQUAL(9, Tester::sum);
+
+    buffer.clear();
+    CORE_TEST_EQUAL(build("[]"), build(buffer));
+    CORE_TEST_EQUAL(0, buffer.getLength());
+    CORE_TEST_EQUAL(0, Tester::sum);
+}
+
 void Core::RingBufferTests::test() {
     testReadAndWrite();
     testOverflow();
     testRefill();
     testClear();
+    testConstructDestruct();
+    testCopyDestruct();
+    testCopyAssignmentDestruct();
+    testMoveDestruct();
+    testMoveAssignmentDestruct();
+    testOverall();
 }