Browse Source

component implementation and tests

Kajetan Johannes Hammerle 2 years ago
parent
commit
9ca3a59a16
5 changed files with 190 additions and 13 deletions
  1. 4 2
      Main.cpp
  2. 90 11
      ecs/Components.h
  3. 1 0
      meson.build
  4. 87 0
      tests/ComponentsTests.cpp
  5. 8 0
      tests/ComponentsTests.h

+ 4 - 2
Main.cpp

@@ -7,6 +7,7 @@
 #include "tests/BufferTests.h"
 #include "tests/ClockTests.h"
 #include "tests/ColorTests.h"
+#include "tests/ComponentsTests.h"
 #include "tests/FrustumTests.h"
 #include "tests/HashMapTests.h"
 #include "tests/ListTests.h"
@@ -56,8 +57,9 @@ int main(int argAmount, char** args) {
     TypedBufferTests::test();
     UniquePointerTests::test();
     NetworkTests::test();
+    ComponentsTests::test();
 
-    struct Game {
+    /*struct Game {
         bool isRunning() {
             return true;
         }
@@ -77,6 +79,6 @@ int main(int argAmount, char** args) {
         return 0;
     }
     Game game;
-    w.run(game, 10'000'000);
+    w.run(game, 10'000'000);*/
     return 0;
 }

+ 90 - 11
ecs/Components.h

@@ -6,42 +6,121 @@ typedef uint32 Entity;
 
 template<typename T>
 class Components final {
-    HashMap<Entity, T> components;
+    HashMap<Entity, int> entityToIndex;
+    List<Entity> indexToEntity;
+    List<T> components;
 
 public:
-    Components() {
-    }
+    template<typename R>
+    struct Node {
+        const Entity& entity;
+        R& component;
+    };
+
+    template<typename C, typename R>
+    class EntityIterator final {
+        C& components;
+        int index;
+
+    public:
+        EntityIterator(C& components, int index)
+            : components(components), index(index) {
+        }
+
+        EntityIterator& operator++() {
+            index++;
+            return *this;
+        }
+
+        bool operator!=(const EntityIterator& other) const {
+            return index != other.index;
+        }
+
+        Node<R> operator*() const {
+            return {components.indexToEntity[index],
+                    components.components[index]};
+        }
+    };
+
+    template<typename C, typename R>
+    struct EntityIteratorAdapter {
+        C& components;
+
+        EntityIterator<C, R> begin() {
+            return EntityIterator<C, R>(components, 0);
+        }
+
+        EntityIterator<C, R> end() {
+            return EntityIterator<C, R>(components,
+                                        components.components.getLength());
+        }
+    };
 
     template<typename... Args>
     void add(Entity e, Args&&... args) {
-        components.tryEmplace(e, std::forward<Args>(args)...);
+        int index = components.getLength();
+        if(entityToIndex.tryEmplace(e, index)) {
+            return;
+        }
+        components.add(std::forward<Args>(args)...);
+        indexToEntity.add(e);
     }
 
     void remove(Entity e) {
-        components.remove(e);
+        int* indexP = entityToIndex.search(e);
+        if(indexP == nullptr) {
+            return;
+        }
+        int lastIndex = components.getLength() - 1;
+        int index = *indexP;
+        entityToIndex.remove(e);
+        components.removeBySwap(index);
+        if(index == lastIndex) {
+            indexToEntity.removeBySwap(index);
+            return;
+        }
+        Entity other = indexToEntity[lastIndex];
+        indexToEntity.removeBySwap(index);
+        entityToIndex.add(other, index);
     }
 
     T* search(Entity e) {
-        return components.search(e);
+        int* index = entityToIndex.search(e);
+        if(index == nullptr) {
+            return nullptr;
+        }
+        return &(components[*index]);
     }
 
     const T* search(Entity e) const {
-        return components.search(e);
+        const int* index = entityToIndex.search(e);
+        if(index == nullptr) {
+            return nullptr;
+        }
+        return &(components[*index]);
     }
 
-    auto begin() {
+    T* begin() {
         return components.begin();
     }
 
-    const auto begin() const {
+    const T* begin() const {
         return components.begin();
     }
 
-    auto end() {
+    T* end() {
         return components.end();
     }
 
-    const auto end() const {
+    const T* end() const {
         return components.end();
     }
+
+    EntityIteratorAdapter<Components, T> entities() {
+        return {*this};
+    }
+
+    EntityIteratorAdapter<const Components, const T> entities() const {
+        return {*this};
+    }
 };

+ 1 - 0
meson.build

@@ -60,6 +60,7 @@ src_tests = [
     'tests/UniquePointerTests.cpp',
     'tests/UtilsTests.cpp',
     'tests/VectorTests.cpp',
+    'tests/ComponentsTests.cpp',
 ]
 
 compiler = meson.get_compiler('cpp')

+ 87 - 0
tests/ComponentsTests.cpp

@@ -0,0 +1,87 @@
+#include "tests/ComponentsTests.h"
+#include "ecs/Components.h"
+#include "tests/Test.h"
+
+typedef Components<int> IntComponent;
+
+static void testAddForEach(Test& test) {
+    IntComponent c;
+    c.add(1, 10);
+    c.add(5, 20);
+    c.add(10, 30);
+
+    auto iter = c.entities();
+    auto pos = iter.begin();
+    auto end = iter.end();
+
+    test.checkEqual(1u, (*pos).entity, "contains added entity 1");
+    test.checkEqual(10, (*pos).component, "contains added component 1");
+    test.checkEqual(true, pos != end, "not at end 1");
+
+    ++pos;
+
+    test.checkEqual(5u, (*pos).entity, "contains added entity 2");
+    test.checkEqual(20, (*pos).component, "contains added component 2");
+    test.checkEqual(true, pos != end, "not at end 2");
+
+    ++pos;
+
+    test.checkEqual(10u, (*pos).entity, "contains added entity 3");
+    test.checkEqual(30, (*pos).component, "contains added component 3");
+    test.checkEqual(true, pos != end, "not at end 3");
+
+    ++pos;
+
+    test.checkEqual(false, pos != end, "at end");
+}
+
+static void testRemove(Test& test) {
+    IntComponent c;
+    c.add(1, 10);
+    c.add(5, 20);
+    c.add(10, 30);
+
+    c.remove(20);
+    c.remove(5);
+    c.remove(30);
+
+    int* i1 = c.search(1);
+    int* i2 = c.search(5);
+    int* i3 = c.search(10);
+
+    test.checkEqual(true, i1 != nullptr, "remove 1");
+    test.checkEqual(true, i2 == nullptr, "remove 2");
+    test.checkEqual(true, i3 != nullptr, "remove 3");
+
+    test.checkEqual(10, *i1, "remove 4");
+    test.checkEqual(30, *i3, "remove 5");
+
+    c.remove(10);
+
+    i1 = c.search(1);
+    i2 = c.search(5);
+    i3 = c.search(10);
+
+    test.checkEqual(true, i1 != nullptr, "remove 6");
+    test.checkEqual(true, i2 == nullptr, "remove 7");
+    test.checkEqual(true, i3 == nullptr, "remove 8");
+
+    test.checkEqual(10, *i1, "remove 9");
+
+    c.remove(1);
+
+    i1 = c.search(1);
+    i2 = c.search(5);
+    i3 = c.search(10);
+
+    test.checkEqual(true, i1 == nullptr, "remove 10");
+    test.checkEqual(true, i2 == nullptr, "remove 11");
+    test.checkEqual(true, i3 == nullptr, "remove 12");
+}
+
+void ComponentsTests::test() {
+    Test test("Components");
+    testAddForEach(test);
+    testRemove(test);
+    test.finalize();
+}

+ 8 - 0
tests/ComponentsTests.h

@@ -0,0 +1,8 @@
+#ifndef COMPONENTSTESTS_H
+#define COMPONENTSTESTS_H
+
+namespace ComponentsTests {
+    void test();
+}
+
+#endif