Kajetan Johannes Hammerle il y a 10 mois
Parent
commit
7f6a50c31a
7 fichiers modifiés avec 541 ajouts et 5 suppressions
  1. 4 4
      CMakeLists.txt
  2. 45 0
      include/core/HashMap.h
  3. 1 0
      include/core/Utility.h
  4. 219 0
      src/HashMap.c
  5. 5 0
      src/Utility.c
  6. 1 1
      test/Main.c
  7. 266 0
      test/modules/HashMapTests.c

+ 4 - 4
CMakeLists.txt

@@ -8,6 +8,7 @@ set(SRC
     "src/Box.c"
     "src/Buffer.c"
     "src/Frustum.c"
+    "src/HashMap.c"
     "src/LinkedList.c"
     "src/List.c"
     "src/Logger.c"
@@ -29,6 +30,7 @@ set(SRC_TESTS
     "test/modules/BoxTests.c"
     "test/modules/BufferTests.c"
     "test/modules/FrustumTests.c"
+    "test/modules/HashMapTests.c"
     "test/modules/LinkedListTests.c"
     "test/modules/ListTests.c"
     "test/modules/MatrixTests.c"
@@ -41,7 +43,6 @@ set(SRC_TESTS
     "test/modules/VectorTests.c"
     "test/modules/ViewTests.c"
     #"test/modules/ComponentsTests.cpp"
-    #"test/modules/HashMapTests.cpp"
 )
 
 set(SRC_PERFORMANCE
@@ -102,6 +103,7 @@ target_sources(core PUBLIC
         ./include/core/Buffer.h
         ./include/core/Check.h
         ./include/core/Frustum.h
+        ./include/core/HashMap.h
         ./include/core/LinkedList.h
         ./include/core/List.h
         ./include/core/Logger.h
@@ -109,15 +111,13 @@ target_sources(core PUBLIC
         ./include/core/Plane.h
         ./include/core/Quaternion.h
         ./include/core/Random.h
+        ./include/core/RingBuffer.h
         ./include/core/SpinLock.h
         ./include/core/Types.h
         ./include/core/Utility.h
         ./include/core/Vector.h
         ./include/core/View.h
 #        ./include/core/Components.hpp
-#        ./include/core/HashMap.hpp
-#        ./include/core/ProbingHashMap.hpp
-        ./include/core/RingBuffer.h
 )
 install(TARGETS core FILE_SET HEADERS)
 

+ 45 - 0
include/core/HashMap.h

@@ -0,0 +1,45 @@
+#ifndef CORE_HASHMAP_H
+#define CORE_HASHMAP_H
+
+#include "core/Types.h"
+
+typedef size_t (*CoreHasher)(const void* key, size_t n);
+typedef bool (*CoreEqual)(const void* keyA, const void* keyB, size_t n);
+
+typedef struct {
+    void* keys;
+    void* values;
+    size_t keySize;
+    size_t valueSize;
+    size_t capacity;
+    size_t entries;
+    CoreHasher hasher;
+    CoreEqual equal;
+} CoreHashMap;
+
+#define CORE_HASH_MAP(keySize, valueSize, hasher, equal)                       \
+    ((CoreHashMap){nullptr, nullptr, keySize, valueSize, 0, 0, hasher, equal})
+void coreDestroyHashMap(CoreHashMap* m);
+void coreRehashHashMap(CoreHashMap* m, size_t minCapacity);
+void* coreHashMapPutPointer(CoreHashMap* m, const void* key, const void* value);
+#define coreHashMapPut(m, kType, key, vType, ...)                              \
+    coreHashMapPutPointer(m, &(kType){key}, &(vType){__VA_ARGS__})
+void* coreHashMapSearchPointer(CoreHashMap* m, const void* key);
+#define coreHashMapSearch(m, kType, key, vType)                                \
+    ((vType*)coreHashMapSearchPointer(m, &(kType){key}))
+const void* coreHashMapSearchPointerC(const CoreHashMap* m, const void* key);
+#define coreHashMapSearchC(m, kType, key, vType)                               \
+    ((const vType*)coreHashMapSearchPointerC(m, &(kType){key}))
+bool coreHashMapContainsPointer(const CoreHashMap* m, const void* key);
+#define coreHashMapContains(m, kType, key)                                     \
+    coreHashMapContainsPointer(m, &(kType){key})
+void coreClearHashMap(CoreHashMap* m);
+size_t coreToStringHashMap(const CoreHashMap* m, char* buffer, size_t n,
+                           CoreToString keyString, CoreToString valueString);
+void coreSwapHashMap(CoreHashMap* a, CoreHashMap* b);
+
+size_t coreHashString(const void* key, size_t n);
+size_t coreHash(const void* key, size_t n);
+bool coreEqual(const void* keyA, const void* keyB, size_t n);
+
+#endif

+ 1 - 0
include/core/Utility.h

@@ -19,6 +19,7 @@ inline size_t coreMinSize(size_t a, size_t b) {
 }
 
 size_t coreToStringSize(const void* p, char* buffer, size_t n);
+size_t coreToStringInt(const void* p, char* buffer, size_t n);
 void coreStringAdd(size_t* w, char** buffer, size_t* n, size_t shift);
 void coreStringAddI(size_t* w, char** buffer, size_t* n, int shift);
 

+ 219 - 0
src/HashMap.c

@@ -0,0 +1,219 @@
+#include "core/HashMap.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "core/Utility.h"
+
+static bool isInvalidKey(const CoreHashMap* m, const void* key) {
+    const char* c = key;
+    for(size_t i = m->keySize; i > 0; i--) {
+        if(*(c++) != 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void* getKey(const CoreHashMap* m, size_t index) {
+    return (char*)m->keys + m->keySize * index;
+}
+
+static void* getValue(const CoreHashMap* m, size_t index) {
+    return (char*)m->values + m->valueSize * index;
+}
+
+static size_t searchSlot(CoreHashMap* m, const void* key) {
+    size_t rehashFactor = 2;
+    while(true) {
+        coreRehashHashMap(m, m->entries * rehashFactor + 1);
+        if(isInvalidKey(m, key)) {
+            return m->capacity - 1;
+        }
+        size_t baseHash = m->hasher(key, m->keySize) * 514685581u;
+        size_t end = m->capacity - 2;
+        // rehash on bad clustering
+        for(size_t i = 0; i <= 5; i++) {
+            size_t hash = (baseHash + i) & end;
+            void* keyEntry = getKey(m, hash);
+            if(isInvalidKey(m, keyEntry) ||
+               m->equal(keyEntry, key, m->keySize)) {
+                return hash;
+            }
+        }
+        rehashFactor *= 2;
+    }
+}
+
+static void* searchValue(const CoreHashMap* m, const void* key) {
+    if(m->capacity != 0) {
+        if(isInvalidKey(m, key)) {
+            size_t i = m->capacity - 1;
+            return isInvalidKey(m, getKey(m, i)) ? getValue(m, i) : nullptr;
+        }
+        size_t baseHash = m->hasher(key, m->keySize) * 514685581u;
+        size_t end = m->capacity - 2;
+        for(size_t i = 0; i <= end; i++) {
+            size_t hash = (baseHash + i) & end;
+            void* keyEntry = getKey(m, hash);
+            if(m->equal(keyEntry, key, m->keySize)) {
+                return getValue(m, hash);
+            } else if(isInvalidKey(m, keyEntry)) {
+                return nullptr;
+            }
+        }
+    }
+    return nullptr;
+}
+
+static size_t roundUp2(size_t n) {
+    size_t w = 1;
+    while(w < n) {
+        w *= 2;
+    }
+    return w;
+}
+
+void coreDestroyHashMap(CoreHashMap* m) {
+    coreFree(m->keys);
+    coreFree(m->values);
+    *m = (CoreHashMap){0};
+}
+
+void coreRehashHashMap(CoreHashMap* m, size_t minCapacity) {
+    if(minCapacity <= m->capacity) {
+        return;
+    }
+    size_t l = roundUp2(coreMaxSize(minCapacity, 8lu)) + 1;
+    CoreHashMap map =
+        CORE_HASH_MAP(m->keySize, m->valueSize, m->hasher, m->equal);
+    size_t keyBytes = l * m->keySize;
+    map.keys = coreAllocate(l * m->keySize);
+    memset(map.keys, 0, keyBytes);
+    map.values = coreAllocate(l * m->valueSize);
+    map.capacity = l;
+
+    size_t length = m->capacity;
+    if(length > 0) {
+        length--;
+        for(size_t i = 0; i < length; i++) {
+            void* keyEntry = getKey(m, i);
+            if(!isInvalidKey(m, keyEntry)) {
+                coreHashMapPutPointer(&map, keyEntry, getValue(m, i));
+            }
+        }
+        void* keyEntry = getKey(m, length);
+        if(isInvalidKey(m, keyEntry)) {
+            coreHashMapPutPointer(&map, keyEntry, getValue(m, length));
+        }
+    }
+    coreSwapHashMap(&map, m);
+    coreDestroyHashMap(&map);
+}
+
+void* coreHashMapPutPointer(CoreHashMap* m, const void* key,
+                            const void* value) {
+    size_t index = searchSlot(m, key);
+    void* keyEntry = getKey(m, index);
+    if(m->equal(keyEntry, key, m->keySize)) {
+        void* v = getValue(m, index);
+        memcpy(v, value, m->valueSize);
+        return v;
+    }
+    void* v = getValue(m, index);
+    memcpy(v, value, m->valueSize);
+    m->entries++;
+    memcpy(keyEntry, key, m->keySize);
+    return v;
+}
+
+void* coreHashMapSearchPointer(CoreHashMap* m, const void* key) {
+    return searchValue(m, key);
+}
+
+const void* coreHashMapSearchPointerC(const CoreHashMap* m, const void* key) {
+    return searchValue(m, key);
+}
+
+bool coreHashMapContainsPointer(const CoreHashMap* m, const void* key) {
+    return coreHashMapSearchPointerC(m, key) != nullptr;
+}
+
+void coreClearHashMap(CoreHashMap* m) {
+    if(m->keys == nullptr) {
+        return;
+    }
+    m->entries = 0;
+    memset(m->keys, 0, m->capacity * m->keySize);
+    memset(getKey(m, m->capacity - 1), 1, m->keySize);
+}
+
+size_t coreToStringHashMap(const CoreHashMap* m, char* buffer, size_t n,
+                           CoreToString keyString, CoreToString valueString) {
+    size_t w = 0;
+    coreStringAddI(&w, &buffer, &n, snprintf(buffer, n, "["));
+    char* end = getKey(m, m->capacity);
+    char* key = m->keys;
+    char* value = m->values;
+    bool notFirst = false;
+    while(key != end) {
+        if(isInvalidKey(m, key) == (key + m->keySize == end)) {
+            if(notFirst) {
+                coreStringAddI(&w, &buffer, &n, snprintf(buffer, n, ", "));
+            }
+            notFirst = true;
+            coreStringAdd(&w, &buffer, &n, keyString(key, buffer, n));
+            coreStringAddI(&w, &buffer, &n, snprintf(buffer, n, " = "));
+            coreStringAdd(&w, &buffer, &n, valueString(value, buffer, n));
+        }
+        key += m->keySize;
+        value += m->valueSize;
+    }
+    coreStringAddI(&w, &buffer, &n, snprintf(buffer, n, "]"));
+    return w;
+}
+
+void coreSwapHashMap(CoreHashMap* a, CoreHashMap* b) {
+    CoreHashMap tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+size_t coreHashString(const void* key, size_t n) {
+    if(n != sizeof(char*)) {
+        return 0;
+    }
+    const char* s = *(const char* const*)key;
+    size_t h = 0;
+    while(*s != '\0') {
+        h = 2120251889lu * h + (size_t)(*(s++));
+    }
+    return h;
+}
+
+size_t coreHash(const void* key, size_t n) {
+    size_t h = 0;
+    if(n <= sizeof(size_t)) {
+        memcpy(&h, key, n);
+        return h;
+    }
+    const char* cKey = key;
+    while(n > 0) {
+        size_t m = 0;
+        if(n >= sizeof(size_t)) {
+            memcpy(&m, cKey, sizeof(size_t));
+            cKey += sizeof(size_t);
+            n -= sizeof(size_t);
+        } else {
+            memcpy(&m, cKey, n);
+            cKey += n;
+            n = 0;
+        }
+        h ^= m;
+    }
+    return h;
+}
+
+bool coreEqual(const void* keyA, const void* keyB, size_t n) {
+    return memcmp(keyA, keyB, n) == 0;
+}

+ 5 - 0
src/Utility.c

@@ -31,6 +31,11 @@ size_t coreToStringSize(const void* p, char* buffer, size_t n) {
     return w < 0 ? 0 : (size_t)w;
 }
 
+size_t coreToStringInt(const void* p, char* buffer, size_t n) {
+    int w = snprintf(buffer, n, "%d", *(const int*)p);
+    return w < 0 ? 0 : (size_t)w;
+}
+
 void coreStringAdd(size_t* w, char** buffer, size_t* n, size_t shift) {
     *w += shift;
     if(*n > shift) {

+ 1 - 1
test/Main.c

@@ -32,11 +32,11 @@ int main(int argAmount, const char** args) {
     }
 
     // coreTestComponents();
-    // coreTestHashMap(light);
     coreTestBitArray();
     coreTestBox();
     coreTestBuffer(light);
     coreTestFrustum();
+    coreTestHashMap(light);
     coreTestLinkedList(light);
     coreTestList(light);
     coreTestMatrix();

+ 266 - 0
test/modules/HashMapTests.c

@@ -0,0 +1,266 @@
+#include "../Tests.h"
+#include "core/HashMap.h"
+#include "core/Utility.h"
+
+#define INT_MAP CORE_HASH_MAP(sizeof(int), sizeof(int), coreHash, coreEqual)
+
+static CoreHashMap getTestIntMap() {
+    CoreHashMap map = INT_MAP;
+    coreHashMapPut(&map, int, 1, int, 3);
+    coreHashMapPut(&map, int, 2, int, 4);
+    coreHashMapPut(&map, int, 3, int, 5);
+    coreHashMapPut(&map, int, 0, int, 20);
+    return map;
+}
+
+static void checkIntMap(CoreHashMap* map) {
+    int* a = coreHashMapSearch(map, int, 1, int);
+    int* b = coreHashMapSearch(map, int, 2, int);
+    const int* c = coreHashMapSearchC(map, int, 3, int);
+    const int* d = coreHashMapSearchC(map, int, 0, int);
+    if(CORE_TEST_NOT_NULL(a) && CORE_TEST_NOT_NULL(b) &&
+       CORE_TEST_NOT_NULL(c) && CORE_TEST_NOT_NULL(d)) {
+        CORE_TEST_INT(3, *a);
+        CORE_TEST_INT(4, *b);
+        CORE_TEST_INT(5, *c);
+        CORE_TEST_INT(20, *d);
+    }
+}
+
+static void testAdd() {
+    CoreHashMap map = INT_MAP;
+    coreHashMapPut(&map, int, 5, int, 4);
+    int* value = coreHashMapSearch(&map, int, 5, int);
+    if(CORE_TEST_NOT_NULL(value)) {
+        CORE_TEST_INT(4, *value);
+    }
+    coreDestroyHashMap(&map);
+}
+
+static void testMultipleAdd() {
+    CoreHashMap map = getTestIntMap();
+    CORE_TEST_TRUE(coreHashMapContains(&map, int, 0));
+    CORE_TEST_TRUE(coreHashMapContains(&map, int, 1));
+    CORE_TEST_TRUE(coreHashMapContains(&map, int, 2));
+    CORE_TEST_TRUE(coreHashMapContains(&map, int, 3));
+    checkIntMap(&map);
+    coreDestroyHashMap(&map);
+}
+
+static void testSearch() {
+    CoreHashMap map = getTestIntMap();
+    CORE_TEST_NULL(coreHashMapSearch(&map, int, 6, int));
+    coreHashMapPut(&map, int, 5, int, 4);
+    coreHashMapPut(&map, int, 10, int, 3);
+    coreHashMapPut(&map, int, 15, int, 2);
+    CORE_TEST_NULL(coreHashMapSearch(&map, int, 6, int));
+    coreDestroyHashMap(&map);
+}
+
+static void testAddReplace() {
+    CoreHashMap map = getTestIntMap();
+    coreHashMapPut(&map, int, 5, int, 4);
+    coreHashMapPut(&map, int, 5, int, 10);
+    CORE_TEST_TRUE(coreHashMapContains(&map, int, 5));
+    int* a = coreHashMapSearch(&map, int, 5, int);
+    if(CORE_TEST_NOT_NULL(a)) {
+        CORE_TEST_INT(10, *a);
+    }
+    coreDestroyHashMap(&map);
+}
+
+static void testClear() {
+    CoreHashMap map = getTestIntMap();
+    coreHashMapPut(&map, int, 5, int, 4);
+    coreHashMapPut(&map, int, 4, int, 10);
+    coreClearHashMap(&map);
+    CORE_TEST_FALSE(coreHashMapContains(&map, int, 5));
+    CORE_TEST_FALSE(coreHashMapContains(&map, int, 4));
+    coreDestroyHashMap(&map);
+}
+
+static void testOverflow(bool light) {
+    int limit = light ? 10000 : 100000;
+    CoreHashMap map = getTestIntMap();
+    for(int i = 0; i < limit; i++) {
+        coreHashMapPut(&map, int, i, int, i);
+    }
+    for(int i = 0; i < limit; i++) {
+        CORE_TEST_TRUE(coreHashMapContains(&map, int, i));
+    }
+    coreDestroyHashMap(&map);
+}
+
+static void testToString() {
+    CoreHashMap map = getTestIntMap();
+    char buffer[128];
+    size_t n = coreToStringHashMap(&map, buffer, sizeof(buffer),
+                                   coreToStringInt, coreToStringInt);
+    CORE_TEST_SIZE(29, n);
+    CORE_TEST_STRING("[2 = 4, 1 = 3, 3 = 5, 0 = 20]", buffer);
+
+    coreClearHashMap(&map);
+    coreHashMapPut(&map, int, 1, int, 3);
+    n = coreToStringHashMap(&map, buffer, sizeof(buffer), coreToStringInt,
+                            coreToStringInt);
+    CORE_TEST_SIZE(7, n);
+    CORE_TEST_STRING("[1 = 3]", buffer);
+
+    coreClearHashMap(&map);
+    n = coreToStringHashMap(&map, buffer, sizeof(buffer), coreToStringInt,
+                            coreToStringInt);
+    CORE_TEST_SIZE(2, n);
+    CORE_TEST_STRING("[]", buffer);
+
+    coreDestroyHashMap(&map);
+}
+
+static void testEntryForEach() {
+    //     T map;
+    //     map.add(5, 4).add(10, 3).add(15, 2);
+    //
+    //     int counter = 0;
+    //     for(auto entry : map) {
+    //         counter += entry.getKey() + entry.value;
+    //     }
+    //     CORE_TEST_EQUAL(39, counter);
+    //
+    //     const T& cmap = map;
+    //     counter = 0;
+    //     for(auto entry : cmap) {
+    //         counter += entry.getKey() + entry.value;
+    //     }
+    //     CORE_TEST_EQUAL(39, counter);
+}
+
+static void testKeyForEach() {
+    //     T map;
+    //     map.add(5, 4).add(10, 3).add(15, 2);
+    //
+    //     int counter = 0;
+    //     for(const int& key : map.getKeys()) {
+    //         counter += key;
+    //     }
+    //     CORE_TEST_EQUAL(30, counter);
+    //
+    //     const T& cmap = map;
+    //     counter = 0;
+    //     for(const int& key : cmap.getKeys()) {
+    //         counter += key;
+    //     }
+    //     CORE_TEST_EQUAL(30, counter);
+}
+
+static void testValueForEach() {
+    //     T map;
+    //     map.add(5, 4).add(10, 3).add(15, 2);
+    //
+    //     int counter = 0;
+    //     for(int& value : map.getValues()) {
+    //         counter += value;
+    //     }
+    //     CORE_TEST_EQUAL(9, counter);
+    //
+    //     const T& cmap = map;
+    //     counter = 0;
+    //     for(const int& value : cmap.getValues()) {
+    //         counter += value;
+    //     }
+    //     CORE_TEST_EQUAL(9, counter);
+}
+
+static void testInvalid() {
+    //     T map;
+    //     int* v;
+    //     CORE_TEST_TRUE(map.tryEmplace(v, INVALID, 2));
+    //     if(CORE_TEST_NOT_NULL(v)) {
+    //         CORE_TEST_EQUAL(2, *v);
+    //     }
+    //     CORE_TEST_FALSE(map.tryEmplace(v, INVALID, 6));
+    //     if(CORE_TEST_NOT_NULL(v)) {
+    //         CORE_TEST_EQUAL(2, *v);
+    //     }
+    //     CORE_TEST_EQUAL(3, map.put(INVALID, 3));
+    //     v = coreHashMapSearch(&map,int,INVALID);
+    //     if(CORE_TEST_NOT_NULL(v)) {
+    //         CORE_TEST_EQUAL(3, *v);
+    //     }
+    //     coreClearHashMap(&map);
+    //     CORE_TEST_NULL(coreHashMapSearch(&map,int,INVALID));
+}
+
+static void testInvalidPut() {
+    //     T map;
+    //     CORE_TEST_EQUAL(3, map.put(INVALID, 3));
+    //     int* v = coreHashMapSearch(&map,int,INVALID);
+    //     if(CORE_TEST_NOT_NULL(v)) {
+    //         CORE_TEST_EQUAL(3, *v);
+    //     }
+    //
+    //     also check to string
+}
+
+static void testAddCollisions() {
+    //     T map;
+    //     for(int i = 0; i < 8; i++) {
+    //         map.add(i * 16, i);
+    //     }
+}
+
+static void testEmplace() {
+    //     Core::HashMap<int, HashMapTest> map;
+    //
+    //     HashMapTest* ar = nullptr;
+    //     CORE_TEST_TRUE(map.tryEmplace(ar, 0, 3, 4));
+    //     CORE_TEST_TRUE(map.tryEmplace(ar, 3, 4, 5));
+    //     CORE_TEST_TRUE(map.tryEmplace(ar, 20, 5, 6));
+    //     CORE_TEST_FALSE(map.tryEmplace(ar, 3, 6, 7));
+    //     CORE_TEST_FALSE(map.tryEmplace(ar, 20, 7, 8));
+    //
+    //     HashMapTest* a = coreHashMapSearch(&map,int,0);
+    //     HashMapTest* b = coreHashMapSearch(&map,int,3);
+    //     HashMapTest* c = coreHashMapSearch(&map,int,20);
+    //
+    //     if(CORE_TEST_NOT_NULL(a) && CORE_TEST_NOT_NULL(b) &&
+    //        CORE_TEST_NOT_NULL(c)) {
+    //         CORE_TEST_EQUAL(HashMapTest(3, 4), *a);
+    //         CORE_TEST_EQUAL(HashMapTest(4, 5), *b);
+    //         CORE_TEST_EQUAL(HashMapTest(5, 6), *c);
+    //     }
+}
+
+static void testRemove() {
+    //     IntMap map;
+    //     map.add(1, 3).add(2, 4).add(3, 5);
+    //
+    //     CORE_TEST_TRUE(map.remove(2));
+    //     CORE_TEST_FALSE(map.remove(7));
+    //
+    //     int* a = coreHashMapSearch(&map,int,1);
+    //     int* b = coreHashMapSearch(&map,int,2);
+    //     int* c = coreHashMapSearch(&map,int,3);
+    //
+    //     CORE_TEST_NULL(b);
+    //     if(CORE_TEST_NOT_NULL(a) && CORE_TEST_NOT_NULL(c)) {
+    //         CORE_TEST_EQUAL(3, *a);
+    //         CORE_TEST_EQUAL(5, *c);
+    //     }
+}
+
+void coreTestHashMap(bool light) {
+    testAdd();
+    testMultipleAdd();
+    testSearch();
+    testAddReplace();
+    testClear();
+    testOverflow(light);
+    testToString();
+    testEntryForEach();
+    testKeyForEach();
+    testValueForEach();
+    testInvalid();
+    testInvalidPut();
+    testAddCollisions();
+    testEmplace();
+    testRemove();
+}