699 lines
22 KiB
C++
699 lines
22 KiB
C++
#define DN_BASE_CONTAINERS_CPP
|
|
#if defined(_CLANGD)
|
|
#include "../dn.h"
|
|
#endif
|
|
|
|
DN_API void *DN_SliceAllocArena(void **data, DN_USize *slice_size_field, DN_USize size, DN_USize elem_size, DN_U8 align, DN_ZMem zmem, DN_Arena *arena)
|
|
{
|
|
void *result = *data;
|
|
*data = DN_ArenaAlloc(arena, size * elem_size, align, zmem);
|
|
if (*data) {
|
|
*slice_size_field = size;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayInsertArray(void *data, DN_USize *size, DN_USize max, DN_USize elem_size, DN_USize index, void const *items, DN_USize count)
|
|
{
|
|
void *result = nullptr;
|
|
if (!data || !size || !items || count <= 0 || ((*size + count) > max))
|
|
return result;
|
|
|
|
DN_USize clamped_index = DN_Min(index, *size);
|
|
if (clamped_index != *size) {
|
|
char const *src = DN_Cast(char *)data + (clamped_index * elem_size);
|
|
char const *dest = DN_Cast(char *)data + ((clamped_index + count) * elem_size);
|
|
char const *end = DN_Cast(char *)data + (size[0] * elem_size);
|
|
DN_USize bytes_to_move = end - src;
|
|
DN_Memmove(DN_Cast(void *) dest, src, bytes_to_move);
|
|
}
|
|
|
|
result = DN_Cast(char *)data + (clamped_index * elem_size);
|
|
DN_Memcpy(result, items, elem_size * count);
|
|
*size += count;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayPopFront(void *data, DN_USize *size, DN_USize elem_size, DN_USize count)
|
|
{
|
|
if (!data || !size || *size == 0 || count == 0)
|
|
return nullptr;
|
|
|
|
DN_USize pop_count = DN_Min(count, *size);
|
|
void *result = data;
|
|
|
|
if (pop_count < *size) {
|
|
char *src = DN_Cast(char *)data + (pop_count * elem_size);
|
|
char *dest = DN_Cast(char *)data;
|
|
DN_USize bytes_to_move = (*size - pop_count) * elem_size;
|
|
DN_Memmove(dest, src, bytes_to_move);
|
|
}
|
|
|
|
*size -= pop_count;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayPopBack(void *data, DN_USize *size, DN_USize elem_size, DN_USize count)
|
|
{
|
|
if (!data || !size || *size == 0 || count == 0)
|
|
return nullptr;
|
|
|
|
DN_USize pop_count = DN_Min(count, *size);
|
|
*size -= pop_count;
|
|
|
|
return DN_Cast(char *)data + (*size * elem_size);
|
|
}
|
|
|
|
DN_API DN_ArrayEraseResult DN_CArrayEraseRange(void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase)
|
|
{
|
|
DN_ArrayEraseResult result = {};
|
|
if (!data || !size || *size == 0 || count == 0)
|
|
return result;
|
|
|
|
// Compute the range to erase
|
|
DN_USize start = 0, end = 0;
|
|
if (count < 0) {
|
|
// Erase backwards from begin_index, inclusive of begin_index
|
|
// Range: [begin_index + count + 1, begin_index + 1)
|
|
// Which is: [begin_index - abs(count) + 1, begin_index + 1)
|
|
DN_USize abs_count = DN_Abs(count);
|
|
start = (begin_index + 1 > abs_count) ? (begin_index + 1 - abs_count) : 0;
|
|
end = begin_index + 1;
|
|
} else {
|
|
start = begin_index;
|
|
end = begin_index + count;
|
|
}
|
|
|
|
// Clamp indices to valid bounds
|
|
start = DN_Min(start, *size);
|
|
end = DN_Min(end, *size);
|
|
|
|
// Erase the range [start, end)
|
|
DN_USize erase_count = end > start ? end - start : 0;
|
|
if (erase_count) {
|
|
char *dest = (char *)data + (elem_size * start);
|
|
char *array_end = (char *)data + (elem_size * *size);
|
|
char *src = dest + (elem_size * erase_count);
|
|
if (erase == DN_ArrayErase_Stable) {
|
|
DN_USize move_size = array_end - src;
|
|
DN_Memmove(dest, src, move_size);
|
|
} else {
|
|
char *unstable_src = array_end - (elem_size * erase_count);
|
|
DN_USize move_size = array_end - unstable_src;
|
|
DN_Memcpy(dest, unstable_src, move_size);
|
|
}
|
|
*size -= erase_count;
|
|
}
|
|
|
|
result.items_erased = erase_count;
|
|
result.it_index = start;
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayMakeArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZMem z_mem)
|
|
{
|
|
void *result = nullptr;
|
|
DN_USize new_size = *size + make_size;
|
|
if (new_size <= max) {
|
|
result = DN_Cast(char *) data + (data_size * size[0]);
|
|
*size = new_size;
|
|
if (z_mem == DN_ZMem_Yes)
|
|
DN_Memset(result, 0, data_size * make_size);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_CArrayAddArray(void *data, DN_USize *size, DN_USize max, DN_USize data_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add)
|
|
{
|
|
void *result = DN_CArrayMakeArray(data, size, max, data_size, elems_count, DN_ZMem_No);
|
|
if (result) {
|
|
if (add == DN_ArrayAdd_Append) {
|
|
DN_Memcpy(result, elems, elems_count * data_size);
|
|
} else {
|
|
char *move_dest = DN_Cast(char *)data + (elems_count * data_size); // Shift elements forward
|
|
char *move_src = DN_Cast(char *)data;
|
|
DN_Memmove(move_dest, move_src, data_size * size[0]);
|
|
DN_Memcpy(data, elems, data_size * elems_count);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayResizeFromPool(void **data, DN_USize *size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max != *max) {
|
|
DN_USize bytes_to_alloc = data_size * new_max;
|
|
void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc);
|
|
if (buffer) {
|
|
DN_USize bytes_to_copy = data_size * DN_Min(*size, new_max);
|
|
DN_Memcpy(buffer, *data, bytes_to_copy);
|
|
DN_PoolDealloc(pool, *data);
|
|
*data = buffer;
|
|
*max = new_max;
|
|
*size = DN_Min(*size, new_max);
|
|
} else {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayGrowFromPool(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize new_max)
|
|
{
|
|
bool result = true;
|
|
if (new_max > *max)
|
|
result = DN_CArrayResizeFromPool(data, &size, max, data_size, pool, new_max);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_CArrayGrowIfNeededFromPool(void **data, DN_USize size, DN_USize *max, DN_USize data_size, DN_Pool *pool, DN_USize add_count)
|
|
{
|
|
bool result = true;
|
|
DN_USize new_size = size + add_count;
|
|
if (new_size > *max) {
|
|
DN_USize new_max = DN_Max(DN_Max(*max * 2, new_size), 8);
|
|
result = DN_CArrayResizeFromPool(data, &size, max, data_size, pool, new_max);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API void *DN_SinglyLLDetach(void **link, void **next)
|
|
{
|
|
void *result = *link;
|
|
if (*link) {
|
|
*link = *next;
|
|
*next = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RingHasSpace(DN_Ring const *ring, DN_U64 size)
|
|
{
|
|
DN_U64 avail = ring->write_pos - ring->read_pos;
|
|
DN_U64 space = ring->size - avail;
|
|
bool result = space >= size;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size)
|
|
{
|
|
DN_U64 data = ring->write_pos - ring->read_pos;
|
|
bool result = data >= size;
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size)
|
|
{
|
|
DN_Assert(src_size <= ring->size);
|
|
DN_U64 offset = ring->write_pos % ring->size;
|
|
DN_U64 bytes_before_split = ring->size - offset;
|
|
DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size);
|
|
DN_U64 post_split_bytes = src_size - pre_split_bytes;
|
|
void const *pre_split_data = src;
|
|
void const *post_split_data = ((char *)src + pre_split_bytes);
|
|
DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes);
|
|
DN_Memcpy(ring->base, post_split_data, post_split_bytes);
|
|
ring->write_pos += src_size;
|
|
}
|
|
|
|
DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size)
|
|
{
|
|
DN_Assert(dest_size <= ring->size);
|
|
DN_U64 offset = ring->read_pos % ring->size;
|
|
DN_U64 bytes_before_split = ring->size - offset;
|
|
DN_U64 pre_split_bytes = DN_Min(bytes_before_split, dest_size);
|
|
DN_U64 post_split_bytes = dest_size - pre_split_bytes;
|
|
DN_Memcpy(dest, ring->base + offset, pre_split_bytes);
|
|
DN_Memcpy((char *)dest + pre_split_bytes, ring->base, post_split_bytes);
|
|
ring->read_pos += dest_size;
|
|
}
|
|
|
|
DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49;
|
|
DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0;
|
|
|
|
template <typename T>
|
|
DN_DSMap<T> DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags)
|
|
{
|
|
DN_DSMap<T> result = {};
|
|
if (!DN_CheckF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size))
|
|
return result;
|
|
if (size <= 0)
|
|
return result;
|
|
if (!DN_Check(arena))
|
|
return result;
|
|
result.arena = arena;
|
|
result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN);
|
|
result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes);
|
|
result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot<T>, size, DN_ZMem_Yes);
|
|
result.occupied = 1; // For sentinel
|
|
result.size = size;
|
|
result.initial_size = size;
|
|
result.flags = flags;
|
|
DN_AssertF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?");
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
void DN_DSMapDeinit(DN_DSMap<T> *map, DN_ZMem z_mem)
|
|
{
|
|
if (!map)
|
|
return;
|
|
// TODO(doyle): Use z_mem
|
|
(void)z_mem;
|
|
DN_ArenaDeinit(map->arena);
|
|
*map = {};
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapIsValid(DN_DSMap<T> const *map)
|
|
{
|
|
bool result = map &&
|
|
map->arena &&
|
|
map->hash_to_slot && // Hash to slot mapping array must be allocated
|
|
map->slots && // Slots array must be allocated
|
|
(map->size & (map->size - 1)) == 0 && // Must be power of two size
|
|
map->occupied >= 1; // DN_DS_MAP_SENTINEL_SLOT takes up one slot
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_U32 DN_DSMapHash(DN_DSMap<T> const *map, DN_DSMapKey key)
|
|
{
|
|
DN_U32 result = 0;
|
|
if (!map)
|
|
return result;
|
|
|
|
if (key.type == DN_DSMapKeyType_U64NoHash) {
|
|
result = DN_Cast(DN_U32) key.u64;
|
|
return result;
|
|
}
|
|
|
|
if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) {
|
|
result = key.hash;
|
|
return result;
|
|
}
|
|
|
|
DN_U32 seed = map->hash_seed ? map->hash_seed : DN_DS_MAP_DEFAULT_HASH_SEED;
|
|
if (map->hash_function) {
|
|
map->hash_function(key, seed);
|
|
} else {
|
|
// NOTE: Courtesy of Demetri Spanos (which this hash table was inspired
|
|
// from), the following is a hashing function snippet provided for
|
|
// reliable, quick and simple quality hashing functions for hash table
|
|
// use.
|
|
// Source: https://github.com/demetri/scribbles/blob/c475464756c104c91bab83ed4e14badefef12ab5/hashing/ub_aware_hash_functions.c
|
|
|
|
char const *key_ptr = nullptr;
|
|
DN_U32 len = 0;
|
|
DN_U32 h = seed;
|
|
switch (key.type) {
|
|
case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/
|
|
case DN_DSMapKeyType_U64NoHash: DN_InvalidCodePath; /*FALLTHRU*/
|
|
case DN_DSMapKeyType_Invalid: break;
|
|
|
|
case DN_DSMapKeyType_Buffer:
|
|
key_ptr = DN_Cast(char const *) key.buffer_data;
|
|
len = key.buffer_size;
|
|
break;
|
|
|
|
case DN_DSMapKeyType_U64:
|
|
key_ptr = DN_Cast(char const *) & key.u64;
|
|
len = sizeof(key.u64);
|
|
break;
|
|
}
|
|
|
|
// Murmur3 32-bit without UB unaligned accesses
|
|
// DN_U32 mur3_32_no_UB(const void *key, int len, DN_U32 h)
|
|
|
|
// main body, work on 32-bit blocks at a time
|
|
for (DN_U32 i = 0; i < len / 4; i++) {
|
|
DN_U32 k;
|
|
memcpy(&k, &key_ptr[i * 4], sizeof(k));
|
|
|
|
k *= 0xcc9e2d51;
|
|
k = ((k << 15) | (k >> 17)) * 0x1b873593;
|
|
h = (((h ^ k) << 13) | ((h ^ k) >> 19)) * 5 + 0xe6546b64;
|
|
}
|
|
|
|
// load/mix up to 3 remaining tail bytes into a tail block
|
|
DN_U32 t = 0;
|
|
uint8_t *tail = ((uint8_t *)key_ptr) + 4 * (len / 4);
|
|
switch (len & 3) {
|
|
case 3: t ^= tail[2] << 16;
|
|
case 2: t ^= tail[1] << 8;
|
|
case 1: {
|
|
t ^= tail[0] << 0;
|
|
h ^= ((0xcc9e2d51 * t << 15) | (0xcc9e2d51 * t >> 17)) * 0x1b873593;
|
|
}
|
|
}
|
|
|
|
// finalization mix, including key length
|
|
h = ((h ^ len) ^ ((h ^ len) >> 16)) * 0x85ebca6b;
|
|
h = (h ^ (h >> 13)) * 0xc2b2ae35;
|
|
result = h ^ (h >> 16);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap<T> const *map, DN_DSMapKey key)
|
|
{
|
|
DN_Assert(key.type != DN_DSMapKeyType_Invalid);
|
|
DN_U32 result = DN_DS_MAP_SENTINEL_SLOT;
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
result = key.hash & (map->size - 1);
|
|
for (;;) {
|
|
if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved
|
|
result++;
|
|
|
|
if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use
|
|
return result;
|
|
|
|
DN_DSMapSlot<T> *slot = map->slots + map->hash_to_slot[result];
|
|
if (slot->key.type == DN_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key))
|
|
return result;
|
|
|
|
result = (result + 1) & (map->size - 1);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFind(DN_DSMap<T> const *map, DN_DSMapKey key)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (DN_DSMapIsValid(map)) {
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) {
|
|
result.slot = map->slots; // NOTE: Set to sentinel value
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.found = true;
|
|
}
|
|
result.value = &result.slot->value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMake(DN_DSMap<T> *map, DN_DSMapKey key)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) {
|
|
// NOTE: Create the slot
|
|
if (index != DN_DS_MAP_SENTINEL_SLOT)
|
|
map->hash_to_slot[index] = map->occupied++;
|
|
|
|
// NOTE: Check if resize is required
|
|
bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3);
|
|
if (map_is_75pct_full) {
|
|
if (!DN_DSMapResize(map, map->size * 2))
|
|
return result;
|
|
result = DN_DSMapMake(map, key);
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.slot->key = key; // NOTE: Assign key to new slot
|
|
if ((key.type == DN_DSMapKeyType_Buffer ||
|
|
key.type == DN_DSMapKeyType_BufferAsU64NoHash) &&
|
|
!key.no_copy_buffer)
|
|
result.slot->key.buffer_data = DN_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size);
|
|
}
|
|
} else {
|
|
result.slot = map->slots + map->hash_to_slot[index];
|
|
result.found = true;
|
|
}
|
|
|
|
result.value = &result.slot->value;
|
|
DN_Assert(result.slot->key.type != DN_DSMapKeyType_Invalid);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSet(DN_DSMap<T> *map, DN_DSMapKey key, T const &value)
|
|
{
|
|
DN_DSMapResult<T> result = {};
|
|
if (!DN_DSMapIsValid(map))
|
|
return result;
|
|
|
|
result = DN_DSMapMake(map, key);
|
|
result.slot->value = value;
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFindKeyU64(DN_DSMap<T> const *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapFind(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMakeKeyU64(DN_DSMap<T> *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapMake(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSetKeyU64(DN_DSMap<T> *map, DN_U64 key, T const &value)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapSet(map, map_key, value);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapFindKeyStr8(DN_DSMap<T> const *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapFind(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapMakeKeyStr8(DN_DSMap<T> *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapMake(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapResult<T> DN_DSMapSetKeyStr8(DN_DSMap<T> *map, DN_Str8 key, T const &value)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
DN_DSMapResult<T> result = DN_DSMapSet(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapResize(DN_DSMap<T> *map, DN_U32 size)
|
|
{
|
|
if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size)
|
|
return false;
|
|
|
|
DN_Arena *prev_arena = map->arena;
|
|
DN_Arena new_arena = {};
|
|
new_arena.mem_funcs = prev_arena->mem_funcs;
|
|
new_arena.flags = prev_arena->flags;
|
|
new_arena.label = prev_arena->label;
|
|
new_arena.prev = prev_arena->prev;
|
|
new_arena.next = prev_arena->next;
|
|
|
|
DN_DSMap<T> new_map = DN_DSMapInit<T>(&new_arena, size, map->flags);
|
|
if (!DN_DSMapIsValid(&new_map))
|
|
return false;
|
|
|
|
new_map.initial_size = map->initial_size;
|
|
for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) {
|
|
DN_DSMapSlot<T> *old_slot = map->slots + old_index;
|
|
DN_DSMapKey old_key = old_slot->key;
|
|
if (old_key.type == DN_DSMapKeyType_Invalid)
|
|
continue;
|
|
DN_DSMapSet(&new_map, old_key, old_slot->value);
|
|
}
|
|
|
|
if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0)
|
|
DN_DSMapDeinit(map, DN_ZMem_No);
|
|
*map = new_map; // Update the map inplace
|
|
map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed
|
|
*map->arena = new_arena; // Re-init the old arena with the new data
|
|
map->pool.arena = map->arena;
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapErase(DN_DSMap<T> *map, DN_DSMapKey key)
|
|
{
|
|
if (!DN_DSMapIsValid(map))
|
|
return false;
|
|
|
|
DN_U32 index = DN_DSMapHashToSlotIndex(map, key);
|
|
if (index == 0)
|
|
return true;
|
|
|
|
DN_U32 slot_index = map->hash_to_slot[index];
|
|
if (slot_index == DN_DS_MAP_SENTINEL_SLOT)
|
|
return false;
|
|
|
|
// NOTE: Mark the slot as unoccupied
|
|
map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT;
|
|
|
|
DN_DSMapSlot<T> *slot = map->slots + slot_index;
|
|
if (!slot->key.no_copy_buffer)
|
|
DN_PoolDealloc(&map->pool, DN_Cast(void *) slot->key.buffer_data);
|
|
*slot = {}; // TODO: Optional?
|
|
|
|
if (map->occupied > 1 /*Sentinel*/) {
|
|
// NOTE: Repair the hash chain, e.g. rehash all the items after the removed
|
|
// element and reposition them if necessary.
|
|
for (DN_U32 probe_index = index;;) {
|
|
probe_index = (probe_index + 1) & (map->size - 1);
|
|
if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT)
|
|
break;
|
|
|
|
DN_DSMapSlot<T> *probe = map->slots + map->hash_to_slot[probe_index];
|
|
DN_U32 new_index = probe->key.hash & (map->size - 1);
|
|
if (index <= probe_index) {
|
|
if (index < new_index && new_index <= probe_index)
|
|
continue;
|
|
} else {
|
|
if (index < new_index || new_index <= probe_index)
|
|
continue;
|
|
}
|
|
|
|
map->hash_to_slot[index] = map->hash_to_slot[probe_index];
|
|
map->hash_to_slot[probe_index] = DN_DS_MAP_SENTINEL_SLOT;
|
|
index = probe_index;
|
|
}
|
|
|
|
// NOTE: We have erased a slot from the hash table, this leaves a gap
|
|
// in our contiguous array. After repairing the chain, the hash mapping
|
|
// is correct.
|
|
// We will now fill in the vacant spot that we erased using the last
|
|
// element in the slot list.
|
|
if (map->occupied >= 3 /*Ignoring sentinel, at least 2 other elements to unstable erase*/) {
|
|
DN_U32 last_index = map->occupied - 1;
|
|
if (last_index != slot_index) {
|
|
// NOTE: Copy in last slot to the erase slot
|
|
DN_DSMapSlot<T> *last_slot = map->slots + last_index;
|
|
map->slots[slot_index] = *last_slot;
|
|
|
|
// NOTE: Update the hash-to-slot mapping for the value that was copied in
|
|
DN_U32 hash_to_slot_index = DN_DSMapHashToSlotIndex(map, last_slot->key);
|
|
map->hash_to_slot[hash_to_slot_index] = slot_index;
|
|
*last_slot = {}; // TODO: Optional?
|
|
}
|
|
}
|
|
}
|
|
|
|
map->occupied--;
|
|
bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1);
|
|
if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size)
|
|
DN_DSMapResize(map, map->size / 2);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapEraseKeyU64(DN_DSMap<T> *map, DN_U64 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyU64(map, key);
|
|
bool result = DN_DSMapErase(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
bool DN_DSMapEraseKeyStr8(DN_DSMap<T> *map, DN_Str8 key)
|
|
{
|
|
DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key);
|
|
bool result = DN_DSMapErase(map, map_key);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap<T> const *map, void const *data, DN_USize size)
|
|
{
|
|
DN_Assert(size > 0 && size <= UINT32_MAX);
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_Buffer;
|
|
result.buffer_data = data;
|
|
result.buffer_size = DN_Cast(DN_U32) size;
|
|
result.hash = DN_DSMapHash(map, result);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap<T> const *map, void const *data, DN_U32 size)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_BufferAsU64NoHash;
|
|
result.buffer_data = data;
|
|
result.buffer_size = DN_Cast(DN_U32) size;
|
|
DN_Assert(size >= sizeof(result.hash));
|
|
DN_Memcpy(&result.hash, data, sizeof(result.hash));
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyU64(DN_DSMap<T> const *map, DN_U64 u64)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_U64;
|
|
result.u64 = u64;
|
|
result.hash = DN_DSMapHash(map, result);
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap<T> const *map, DN_Str8 string)
|
|
{
|
|
DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.size);
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_DSMap
|
|
DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(DN_U64 u64)
|
|
{
|
|
DN_DSMapKey result = {};
|
|
result.type = DN_DSMapKeyType_U64NoHash;
|
|
result.u64 = u64;
|
|
result.hash = DN_Cast(DN_U32) u64;
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_DSMapKeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs)
|
|
{
|
|
bool result = false;
|
|
if (lhs.type == rhs.type && lhs.hash == rhs.hash) {
|
|
switch (lhs.type) {
|
|
case DN_DSMapKeyType_Invalid: result = true; break;
|
|
case DN_DSMapKeyType_U64NoHash: result = true; break;
|
|
case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break;
|
|
|
|
case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/
|
|
case DN_DSMapKeyType_Buffer: {
|
|
if (lhs.buffer_size == rhs.buffer_size)
|
|
result = DN_Memcmp(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0;
|
|
} break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs)
|
|
{
|
|
bool result = DN_DSMapKeyEquals(lhs, rhs);
|
|
return result;
|
|
}
|