dqn: Remove Map and reimplement DSMap
This commit is contained in:
parent
d827448ac2
commit
0f15694de9
@ -236,228 +236,163 @@ Dqn_Tester Dqn_Test_DSMap()
|
|||||||
{
|
{
|
||||||
Dqn_Tester test = {};
|
Dqn_Tester test = {};
|
||||||
DQN_TESTER_GROUP(test, "Dqn_DSMap") {
|
DQN_TESTER_GROUP(test, "Dqn_DSMap") {
|
||||||
DQN_TESTER_TEST("Add r-value item to map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
Dqn_DSMapItem<int> *entry = Dqn_DSMap_Add(&map, 3 /*hash*/, 5 /*value*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 128, "size: %I64d", map.size);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 3, "hash: %zu", entry->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 5, "value: %d", entry->value);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add l-value item to map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
int value = 5;
|
|
||||||
Dqn_DSMapItem<int> *entry = Dqn_DSMap_Add(&map, 3 /*hash*/, value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 128, "size: %I64d", map.size);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 3, "hash: %zu", entry->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 5, "value: %d", entry->value);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Get item from map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
Dqn_DSMapItem<int> *entry = Dqn_DSMap_Add(&map, 3 /*hash*/, 5 /*value*/);
|
|
||||||
Dqn_DSMapItem<int> *get_entry = Dqn_DSMap_Get(&map, 3 /*hash*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, get_entry == entry, "get_entry: %p, entry: %p", get_entry, entry);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Get non-existent item from map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
Dqn_DSMapItem<int> *entry = Dqn_DSMap_Get(&map, 3 /*hash*/);
|
|
||||||
DQN_TESTER_ASSERT(&test, entry == nullptr);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Erase item from map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
Dqn_DSMap_Add(&map, 3 /*hash*/, 5 /*value*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %I64d", map.count);
|
|
||||||
Dqn_DSMap_Erase(&map, 3 /*hash*/, Dqn_ZeroMem_No);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 0, "count: %I64d", map.count);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Erase non-existent item from map") {
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(128);
|
|
||||||
Dqn_DSMap_Erase(&map, 3 /*hash*/, Dqn_ZeroMem_No);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 0, "count: %I64d", map.count);
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
|
|
||||||
DQN_TESTER_TEST("Test resize on maximum load") {
|
|
||||||
const Dqn_isize INIT_SIZE = 4;
|
|
||||||
Dqn_DSMap<int> map = Dqn_DSMap_Init<int>(INIT_SIZE);
|
|
||||||
Dqn_DSMap_Add(&map, 0 /*hash*/, 5 /*value*/);
|
|
||||||
Dqn_DSMap_Add(&map, 1 /*hash*/, 5 /*value*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 2, "count: %I64d", map.count);
|
|
||||||
|
|
||||||
// This *should* cause a resize because 3/4 slots filled is 75% load
|
|
||||||
Dqn_DSMap_Add(&map, 6 /*hash*/, 5 /*value*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 3, "count: %I64d", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == INIT_SIZE * 2, "size: %I64d", map.size);
|
|
||||||
|
|
||||||
// Check that the elements are rehashed where we expected them to be
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[0].occupied == DQN_CAST(uint8_t)true);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[1].occupied == DQN_CAST(uint8_t)true);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[2].occupied == DQN_CAST(uint8_t)false);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[3].occupied == DQN_CAST(uint8_t)false);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[4].occupied == DQN_CAST(uint8_t)false);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[5].occupied == DQN_CAST(uint8_t)false);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[6].occupied == DQN_CAST(uint8_t)true);
|
|
||||||
DQN_TESTER_ASSERT(&test, map.slots[7].occupied == DQN_CAST(uint8_t)false);
|
|
||||||
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.slots[0].value == 5, "value: %d", map.slots[0].value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.slots[1].value == 5, "value: %d", map.slots[1].value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.slots[6].value == 5, "value: %d", map.slots[6].value);
|
|
||||||
|
|
||||||
Dqn_DSMap_Free(&map, Dqn_ZeroMem_No);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return test;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dqn_Tester Dqn_Test_Map()
|
|
||||||
{
|
|
||||||
Dqn_Tester test = {};
|
|
||||||
DQN_TESTER_GROUP(test, "Dqn_Map") {
|
|
||||||
DQN_TESTER_TEST("Add r-value item to map") {
|
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
{
|
||||||
Dqn_MapEntry<int> *entry = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5 /*value*/, Dqn_MapCollideRule::Overwrite);
|
uint32_t const MAP_SIZE = 64;
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %I64d", map.size);
|
Dqn_DSMap<uint64_t> map = Dqn_DSMap_Init<uint64_t>(MAP_SIZE);
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
DQN_DEFER { Dqn_DSMap_Deinit(&map); };
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
DQN_TESTER_TEST("Find non-existent value") {
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 3, "hash: %zu", entry->hash);
|
uint64_t *value = Dqn_DSMap_Find(&map, Dqn_DSMap_KeyCStringLit("Foo"));
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 5, "value: %d", entry->value);
|
DQN_ASSERT(!value);
|
||||||
DQN_TESTER_ASSERTF(&test, entry->next == nullptr, "next: %p", entry->next);
|
DQN_ASSERT(map.size == MAP_SIZE);
|
||||||
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
|
DQN_ASSERT(map.occupied == 1 /*Sentinel*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add l-value item to map") {
|
Dqn_DSMapKey key = Dqn_DSMap_KeyCStringLit("Bar");
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
DQN_TESTER_TEST("Insert value and lookup") {
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
uint64_t desired_value = 0xF00BAA;
|
||||||
int value = 5;
|
Dqn_DSMapSlot<uint64_t> *slot = Dqn_DSMap_Set(&map, key, desired_value);
|
||||||
Dqn_MapEntry<int> *entry = Dqn_Map_Add(&map, 3 /*hash*/, value, Dqn_MapCollideRule::Overwrite);
|
DQN_ASSERT(slot);
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %I64d", map.size);
|
DQN_ASSERT(map.size == MAP_SIZE);
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
DQN_ASSERT(map.occupied == 2);
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 3, "hash: %zu", entry->hash);
|
uint64_t *value = Dqn_DSMap_Find(&map, key);
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 5, "value: %d", entry->value);
|
DQN_ASSERT(value);
|
||||||
DQN_TESTER_ASSERTF(&test, entry->next == nullptr, "next: %p", entry->next);
|
DQN_ASSERT(*value == desired_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and overwrite on collision") {
|
DQN_TESTER_TEST("Remove key") {
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_DSMap_Erase(&map, key);
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
DQN_ASSERT(map.size == MAP_SIZE);
|
||||||
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Overwrite);
|
DQN_ASSERT(map.occupied == 1 /*Sentinel*/);
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
}
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_a == entry_b, "Expected entry to be overwritten");
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->hash == 4, "hash: %zu", entry_b->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->value == 6, "value: %d", entry_b->value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->next == nullptr, "next: %p", entry_b->next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and fail on collision") {
|
{
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
DQN_ARENA_TEMP_MEMORY_SCOPE(scratch.arena);
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
uint32_t const MAP_SIZE = 64;
|
||||||
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
Dqn_DSMap<uint64_t> map = Dqn_DSMap_Init<uint64_t>(MAP_SIZE);
|
||||||
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Fail);
|
DQN_DEFER { Dqn_DSMap_Deinit(&map); };
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b == nullptr, "Expected entry to be overwritten");
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
DQN_TESTER_TEST("Test growing") {
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
uint64_t map_start_size = map.size;
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
uint64_t value = 0;
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
uint64_t grow_threshold = map_start_size * 3 / 4;
|
||||||
|
for (; map.occupied != grow_threshold; value++) {
|
||||||
|
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
|
||||||
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy));
|
||||||
|
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
DQN_ASSERT(!Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key)));
|
||||||
|
Dqn_DSMap_Set(&map, key, value);
|
||||||
|
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key)));
|
||||||
|
}
|
||||||
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
|
DQN_ASSERT(map.size == map_start_size);
|
||||||
|
DQN_ASSERT(map.occupied == 1 /*Sentinel*/ + value);
|
||||||
|
|
||||||
|
{ // NOTE: One more item should cause the table to grow by 2x
|
||||||
|
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
|
||||||
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy));
|
||||||
|
Dqn_DSMap_Set(&map, key, value++);
|
||||||
|
|
||||||
|
DQN_ASSERT(map.size == map_start_size * 2);
|
||||||
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
|
DQN_ASSERT(map.occupied == 1 /*Sentinel*/ + value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and chain on collision") {
|
DQN_TESTER_TEST("Check the sentinel is present") {
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_DSMapSlot<uint64_t> NIL_SLOT = {};
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
Dqn_DSMapSlot<uint64_t> sentinel = map.slots[DQN_DS_MAP_SENTINEL_SLOT];
|
||||||
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
DQN_ASSERT(DQN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0);
|
||||||
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 1, "chain_count: %zu", map.chain_count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_a != entry_b, "Expected colliding entry to be chained");
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_a->next == entry_b, "Expected chained entry to be next to our first map entry");
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->hash == 4, "hash: %zu", entry_b->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->value == 6, "value: %d", entry_b->value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry_b->next == nullptr, "next: %p", entry_b->next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and get them back out again") {
|
DQN_TESTER_TEST("Recheck all the hash tables values after growing") {
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) {
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
Dqn_DSMapSlot<uint64_t> const *slot = map.slots + index;
|
||||||
Dqn_MapEntry<int> *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
|
||||||
Dqn_MapEntry<int> *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
|
|
||||||
|
|
||||||
Dqn_MapEntry<int> *entry_a_copy = Dqn_Map_Get(&map, 3 /*hash*/);
|
// NOTE: Validate each slot value
|
||||||
Dqn_MapEntry<int> *entry_b_copy = Dqn_Map_Get(&map, 4 /*hash*/);
|
uint64_t value_test = index - 1;
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&value_test, sizeof(value_test));
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
DQN_ASSERT(Dqn_DSMap_KeyEquals(slot->key, key));
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 1, "chain_count: %zu", map.chain_count);
|
DQN_ASSERT(slot->value == value_test);
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list);
|
DQN_ASSERT(slot->hash == Dqn_DSMap_Hash(&map, slot->key));
|
||||||
DQN_TESTER_ASSERT(&test, entry_a_copy == entry_a);
|
|
||||||
DQN_TESTER_ASSERT(&test, entry_b_copy == entry_b);
|
// NOTE: Check the reverse lookup is correct
|
||||||
|
Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot<uint64_t>(&map, slot->key, slot->hash);
|
||||||
|
DQN_ASSERT(slot == check);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and erase it") {
|
DQN_TESTER_TEST("Test shrinking") {
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
uint64_t start_map_size = map.size;
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
uint64_t start_map_occupied = map.occupied;
|
||||||
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
uint64_t value = 0;
|
||||||
Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
|
uint64_t shrink_threshold = map.size * 1 / 4;
|
||||||
Dqn_Map_Get(&map, 3 /*hash*/);
|
for (; map.occupied != shrink_threshold; value++) {
|
||||||
|
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
|
||||||
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy));
|
||||||
|
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key)));
|
||||||
|
Dqn_DSMap_Erase(&map, key);
|
||||||
|
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
DQN_ASSERT(!Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key)));
|
||||||
|
}
|
||||||
|
DQN_ASSERT(map.size == start_map_size);
|
||||||
|
DQN_ASSERT(map.occupied == start_map_occupied - value);
|
||||||
|
|
||||||
Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem_No);
|
{ // NOTE: One more item should cause the table to grow by 2x
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy));
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
Dqn_DSMap_Erase(&map, key);
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list != nullptr, "free_list: %p", map.free_list);
|
value++;
|
||||||
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->hash == 3, "Entry should not be zeroed out on erase");
|
DQN_ASSERT(map.size == start_map_size / 2);
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->value == 5, "Entry should not be zeroed out on erase");
|
DQN_ASSERT(map.occupied == start_map_occupied - value);
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->next == nullptr, "This should be the first and only entry in the free list");
|
|
||||||
|
|
||||||
Dqn_MapEntry<int> *entry = Dqn_Map_Get(&map, 4 /*hash*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 4, "hash: %zu", entry->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 6, "value: %d", entry->value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->next == nullptr, "next: %p", entry->next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("Add r-value item and erase it, zeroing the memory out") {
|
{ // NOTE: Check the sentinel is present
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_DSMapSlot<uint64_t> NIL_SLOT = {};
|
||||||
Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1);
|
Dqn_DSMapSlot<uint64_t> sentinel = map.slots[DQN_DS_MAP_SENTINEL_SLOT];
|
||||||
Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite);
|
DQN_ASSERT(DQN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0);
|
||||||
Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain);
|
|
||||||
Dqn_Map_Get(&map, 3 /*hash*/);
|
|
||||||
|
|
||||||
Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem_Yes);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %zu", map.size);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count);
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list != nullptr, "free_list: %p", map.free_list);
|
|
||||||
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->hash == 0, "Entry should be zeroed out on erase");
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->value == 0, "Entry should be zeroed out on erase");
|
|
||||||
DQN_TESTER_ASSERTF(&test, map.free_list->next == nullptr, "This should be the first and only entry in the free list");
|
|
||||||
|
|
||||||
Dqn_MapEntry<int> *entry = Dqn_Map_Get(&map, 4 /*hash*/);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->hash == 4, "hash: %zu", entry->hash);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->value == 6, "value: %d", entry->value);
|
|
||||||
DQN_TESTER_ASSERTF(&test, entry->next == nullptr, "next: %p", entry->next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dqn): Test free list is chained correctly
|
// NOTE: Recheck all the hash table values after growing
|
||||||
// TODO(dqn): Test deleting 'b' from the list in the situation [map] - [a]->[b], we currently only test deleting a
|
for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) {
|
||||||
|
|
||||||
|
// NOTE: Generate the key
|
||||||
|
uint64_t value_test = value + (index - 1);
|
||||||
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)&value_test, sizeof(value_test));
|
||||||
|
|
||||||
|
// NOTE: Validate each slot value
|
||||||
|
Dqn_DSMapSlot<uint64_t> const *slot = Dqn_DSMap_FindSlot(&map, key, Dqn_DSMap_Hash(&map, key));
|
||||||
|
DQN_ASSERT(slot);
|
||||||
|
DQN_ASSERT(slot->key == key);
|
||||||
|
DQN_ASSERT(slot->value == value_test);
|
||||||
|
DQN_ASSERT(slot->hash == Dqn_DSMap_Hash(&map, slot->key));
|
||||||
|
|
||||||
|
// NOTE: Check the reverse lookup is correct
|
||||||
|
Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot<uint64_t>(&map, slot->key, slot->hash);
|
||||||
|
DQN_ASSERT(slot == check);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table
|
||||||
|
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
|
||||||
|
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy));
|
||||||
|
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
Dqn_DSMap_Erase(&map, key);
|
||||||
|
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));
|
||||||
|
}
|
||||||
|
DQN_ASSERT(map.initial_size == MAP_SIZE);
|
||||||
|
DQN_ASSERT(map.size == map.initial_size);
|
||||||
|
DQN_ASSERT(map.occupied == 1 /*Sentinel*/);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
@ -848,7 +783,7 @@ Dqn_Tester Dqn_Test_CString8()
|
|||||||
char const prefix[] = "@123";
|
char const prefix[] = "@123";
|
||||||
char const buf[] = "@123string";
|
char const buf[] = "@123string";
|
||||||
Dqn_isize trimmed_size = 0;
|
Dqn_isize trimmed_size = 0;
|
||||||
char const *result = Dqn_CString8_TrimPrefix(buf, prefix, Dqn_CString8_ArrayCountI(buf), Dqn_CString8_ArrayCountI(prefix), Dqn_CString8EqCase::Sensitive, &trimmed_size);
|
char const *result = Dqn_CString8_TrimPrefix(buf, prefix, Dqn_CString8_ArrayCountI(buf), Dqn_CString8_ArrayCountI(prefix), Dqn_CString8EqCase_Sensitive, &trimmed_size);
|
||||||
DQN_TESTER_ASSERTF(&test, trimmed_size == 6, "size: %I64d", trimmed_size);
|
DQN_TESTER_ASSERTF(&test, trimmed_size == 6, "size: %I64d", trimmed_size);
|
||||||
DQN_TESTER_ASSERTF(&test, Dqn_String8_Eq(Dqn_String8_Init(result, trimmed_size), DQN_STRING8("string")), "%.*s", (int)trimmed_size, result);
|
DQN_TESTER_ASSERTF(&test, Dqn_String8_Eq(Dqn_String8_Init(result, trimmed_size), DQN_STRING8("string")), "%.*s", (int)trimmed_size, result);
|
||||||
}
|
}
|
||||||
@ -856,7 +791,7 @@ Dqn_Tester Dqn_Test_CString8()
|
|||||||
DQN_TESTER_TEST("Trim prefix, nullptr trimmed size") {
|
DQN_TESTER_TEST("Trim prefix, nullptr trimmed size") {
|
||||||
char const prefix[] = "@123";
|
char const prefix[] = "@123";
|
||||||
char const buf[] = "@123string";
|
char const buf[] = "@123string";
|
||||||
char const *result = Dqn_CString8_TrimPrefix(buf, prefix, Dqn_CString8_ArrayCountI(buf), Dqn_CString8_ArrayCountI(prefix), Dqn_CString8EqCase::Sensitive, nullptr);
|
char const *result = Dqn_CString8_TrimPrefix(buf, prefix, Dqn_CString8_ArrayCountI(buf), Dqn_CString8_ArrayCountI(prefix), Dqn_CString8EqCase_Sensitive, nullptr);
|
||||||
DQN_TESTER_ASSERT(&test, result);
|
DQN_TESTER_ASSERT(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1073,38 +1008,38 @@ Dqn_Tester Dqn_Test_Win()
|
|||||||
DQN_TESTER_GROUP(test, "Dqn_Win") {
|
DQN_TESTER_GROUP(test, "Dqn_Win") {
|
||||||
DQN_TESTER_TEST("String8 to String16 size required") {
|
DQN_TESTER_TEST("String8 to String16 size required") {
|
||||||
int result = Dqn_Win_String8ToCString16(DQN_STRING8("a"), nullptr, 0);
|
int result = Dqn_Win_String8ToCString16(DQN_STRING8("a"), nullptr, 0);
|
||||||
DQN_TESTER_ASSERTF(&test, result == 2, "Size returned: %d. This size should include the null-terminator", result);
|
DQN_TESTER_ASSERTF(&test, result == 1, "Size returned: %d. This size should not include the null-terminator", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("String16 to String8 size required") {
|
DQN_TESTER_TEST("String16 to String8 size required") {
|
||||||
int result = Dqn_Win_String16ToCString8(DQN_STRING16(L"a"), nullptr, 0);
|
int result = Dqn_Win_String16ToCString8(DQN_STRING16(L"a"), nullptr, 0);
|
||||||
DQN_TESTER_ASSERTF(&test, result == 2, "Size returned: %d. This size should include the null-terminator", result);
|
DQN_TESTER_ASSERTF(&test, result == 1, "Size returned: %d. This size should not include the null-terminator", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("String8 to String16 size required") {
|
DQN_TESTER_TEST("String8 to String16 size required") {
|
||||||
int result = Dqn_Win_String8ToCString16(DQN_STRING8("String"), nullptr, 0);
|
int result = Dqn_Win_String8ToCString16(DQN_STRING8("String"), nullptr, 0);
|
||||||
DQN_TESTER_ASSERTF(&test, result == 7, "Size returned: %d. This size should include the null-terminator", result);
|
DQN_TESTER_ASSERTF(&test, result == 6, "Size returned: %d. This size should not include the null-terminator", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("String16 to String8 size required") {
|
DQN_TESTER_TEST("String16 to String8 size required") {
|
||||||
int result = Dqn_Win_String16ToCString8(DQN_STRING16(L"String"), nullptr, 0);
|
int result = Dqn_Win_String16ToCString8(DQN_STRING16(L"String"), nullptr, 0);
|
||||||
DQN_TESTER_ASSERTF(&test, result == 7, "Size returned: %d. This size should include the null-terminator", result);
|
DQN_TESTER_ASSERTF(&test, result == 6, "Size returned: %d. This size should not include the null-terminator", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_TESTER_TEST("String8 to String16") {
|
DQN_TESTER_TEST("String8 to String16") {
|
||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
||||||
Dqn_String8 const INPUT = DQN_STRING8("String");
|
Dqn_String8 const INPUT = DQN_STRING8("String");
|
||||||
int size_required = Dqn_Win_String8ToCString16(INPUT, nullptr, 0);
|
int size_required = Dqn_Win_String8ToCString16(INPUT, nullptr, 0);
|
||||||
wchar_t *string = Dqn_Arena_NewArray(scratch.arena, wchar_t, size_required, Dqn_ZeroMem_No);
|
wchar_t *string = Dqn_Arena_NewArray(scratch.arena, wchar_t, size_required + 1, Dqn_ZeroMem_No);
|
||||||
|
|
||||||
// Fill the string with error sentinels, which ensures the string is zero terminated
|
// Fill the string with error sentinels, which ensures the string is zero terminated
|
||||||
DQN_MEMSET(string, 'Z', size_required);
|
DQN_MEMSET(string, 'Z', size_required + 1);
|
||||||
|
|
||||||
int size_returned = Dqn_Win_String8ToCString16(INPUT, string, size_required);
|
int size_returned = Dqn_Win_String8ToCString16(INPUT, string, size_required + 1);
|
||||||
wchar_t const EXPECTED[] = {L'S', L't', L'r', L'i', L'n', L'g', 0};
|
wchar_t const EXPECTED[] = {L'S', L't', L'r', L'i', L'n', L'g', 0};
|
||||||
|
|
||||||
DQN_TESTER_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned);
|
DQN_TESTER_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned);
|
||||||
DQN_TESTER_ASSERTF(&test, size_returned == Dqn_CArray_Count(EXPECTED), "string_size: %d, expected: %zu", size_returned, Dqn_CArray_Count(EXPECTED));
|
DQN_TESTER_ASSERTF(&test, size_returned == Dqn_CArray_Count(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, Dqn_CArray_Count(EXPECTED) - 1);
|
||||||
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0);
|
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1112,16 +1047,16 @@ Dqn_Tester Dqn_Test_Win()
|
|||||||
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
|
||||||
Dqn_String16 INPUT = DQN_STRING16(L"String");
|
Dqn_String16 INPUT = DQN_STRING16(L"String");
|
||||||
int size_required = Dqn_Win_String16ToCString8(INPUT, nullptr, 0);
|
int size_required = Dqn_Win_String16ToCString8(INPUT, nullptr, 0);
|
||||||
char *string = Dqn_Arena_NewArray(scratch.arena, char, size_required, Dqn_ZeroMem_No);
|
char *string = Dqn_Arena_NewArray(scratch.arena, char, size_required + 1, Dqn_ZeroMem_No);
|
||||||
|
|
||||||
// Fill the string with error sentinels, which ensures the string is zero terminated
|
// Fill the string with error sentinels, which ensures the string is zero terminated
|
||||||
DQN_MEMSET(string, 'Z', size_required);
|
DQN_MEMSET(string, 'Z', size_required + 1);
|
||||||
|
|
||||||
int size_returned = Dqn_Win_String16ToCString8(INPUT, string, size_required);
|
int size_returned = Dqn_Win_String16ToCString8(INPUT, string, size_required + 1);
|
||||||
char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0};
|
char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0};
|
||||||
|
|
||||||
DQN_TESTER_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned);
|
DQN_TESTER_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned);
|
||||||
DQN_TESTER_ASSERTF(&test, size_returned == Dqn_CArray_Count(EXPECTED), "string_size: %d, expected: %zu", size_returned, Dqn_CArray_Count(EXPECTED));
|
DQN_TESTER_ASSERTF(&test, size_returned == Dqn_CArray_Count(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, Dqn_CArray_Count(EXPECTED) - 1);
|
||||||
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0);
|
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1350,7 +1285,6 @@ void Dqn_Test_RunSuite()
|
|||||||
Dqn_Test_Intrinsics(),
|
Dqn_Test_Intrinsics(),
|
||||||
Dqn_Test_M4(),
|
Dqn_Test_M4(),
|
||||||
Dqn_Test_DSMap(),
|
Dqn_Test_DSMap(),
|
||||||
Dqn_Test_Map(),
|
|
||||||
Dqn_Test_Rect(),
|
Dqn_Test_Rect(),
|
||||||
Dqn_Test_PerfCounter(),
|
Dqn_Test_PerfCounter(),
|
||||||
Dqn_Test_OS(),
|
Dqn_Test_OS(),
|
||||||
|
Loading…
Reference in New Issue
Block a user