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_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); | ||||||
|             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 = {}; |             uint32_t const MAP_SIZE = 64; | ||||||
|     DQN_TESTER_GROUP(test, "Dqn_Map") { |             Dqn_DSMap<uint64_t>  map      = Dqn_DSMap_Init<uint64_t>(MAP_SIZE); | ||||||
|         DQN_TESTER_TEST("Add r-value item to map") { |             DQN_DEFER { Dqn_DSMap_Deinit(&map); }; | ||||||
|             Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); | 
 | ||||||
|             Dqn_Map<int> map = Dqn_Map_InitWithArena<int>(scratch.arena, 1); |             DQN_TESTER_TEST("Find non-existent value") { | ||||||
|             Dqn_MapEntry<int> *entry = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5 /*value*/, Dqn_MapCollideRule::Overwrite); |                 uint64_t *value = Dqn_DSMap_Find(&map, Dqn_DSMap_KeyCStringLit("Foo")); | ||||||
|             DQN_TESTER_ASSERTF(&test, map.size == 1, "size: %I64d", map.size); |                 DQN_ASSERT(!value); | ||||||
|             DQN_TESTER_ASSERTF(&test, map.count == 1, "count: %zu", map.count); |                 DQN_ASSERT(map.size         == MAP_SIZE); | ||||||
|             DQN_TESTER_ASSERTF(&test, map.chain_count == 0, "chain_count: %zu", map.chain_count); |                 DQN_ASSERT(map.initial_size == MAP_SIZE); | ||||||
|             DQN_TESTER_ASSERTF(&test, map.free_list == nullptr, "free_list: %p", map.free_list); |                 DQN_ASSERT(map.occupied     == 1 /*Sentinel*/); | ||||||
|             DQN_TESTER_ASSERTF(&test, entry->hash == 3, "hash: %zu", entry->hash); |  | ||||||
|             DQN_TESTER_ASSERTF(&test, entry->value == 5, "value: %d", entry->value); |  | ||||||
|             DQN_TESTER_ASSERTF(&test, entry->next == nullptr, "next: %p", entry->next); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user