Initial commit, yay

This commit is contained in:
VegOwOtenks 2024-06-13 15:28:21 +02:00
commit 25e26756cd
85 changed files with 7077 additions and 0 deletions

View file

@ -0,0 +1 @@
add_library(hashmap STATIC hashmap.c)

359
src/hashmap/hashmap.c Normal file
View file

@ -0,0 +1,359 @@
/*
* This code is part of the strategy game operational-space.
* operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0.
* Copyright (C) 2024 VegOwOtenks, Sleppo04
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "hashmap.h"
// ! Caution with value_size if you care to store something else than pointers
// ! Set the key if your application is security relevant and increase c and d
struct HashMapConfig HashMap_DefaultConfig(void)
{
struct HashMapConfig config = {
.load_factor = 0.6,
.growth_factor = 2,
.value_size = sizeof(void*),
.allocator = NULL,
.siphash_config = SipHash_DefaultConfig(),
};
return config;
}
size_t _GetEntrySize(struct HashMapConfig* config)
{
return sizeof(struct HashMapEntry) + config->value_size;
}
int HashMap_Create(hashmap_t* restrict destination, struct HashMapConfig* restrict config, size_t initial_capacity)
{
if (destination == NULL) {
return EDESTADDRREQ;
}
if (initial_capacity == 0) {
return EINVAL;
}
hashmapentry_t** buckets = Allocator_AllocateArray(config->allocator, sizeof(hashmapentry_t*), initial_capacity);
if (buckets == NULL) {
return ENOMEM;
}
memset(buckets, 0, sizeof(hashmapentry_t*) * initial_capacity);
destination->buckets = buckets;
destination->capacity = initial_capacity;
destination->used = 0;
destination->config = *config;
return EXIT_SUCCESS;
}
void HashMap_Destroy(hashmap_t* restrict hashmap)
{
if (hashmap == NULL) {
return;
}
for (size_t i = 0; i < hashmap->capacity; i++) {
hashmapentry_t* entry = hashmap->buckets[i];
while (entry != NULL) {
hashmapentry_t* next = entry->next;
Allocator_Free(hashmap->config.allocator, entry->key, entry->key_length);
Allocator_Free(hashmap->config.allocator, entry, _GetEntrySize(&hashmap->config));
entry = next;
}
}
Allocator_FreeArray(hashmap->config.allocator, hashmap->buckets, sizeof(hashmapentry_t*), hashmap->capacity);
memset(hashmap, 0, sizeof(*hashmap));
return;
}
uint64_t _HashMap_GetKeyBucketIndex(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length)
{
return siphash2(key, key_length, &hashmap->config.siphash_config) % hashmap->capacity;
}
hashmapentry_t* _HashMap_CreateEntry(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length, void* restrict value)
{
hashmapentry_t* new_entry = Allocator_Allocate(hashmap->config.allocator, _GetEntrySize(&hashmap->config));
if (new_entry == NULL) {
return NULL;
}
new_entry->key = Allocator_Allocate(hashmap->config.allocator, key_length);
if (new_entry->key == NULL) {
Allocator_Free(hashmap->config.allocator, new_entry, _GetEntrySize(&hashmap->config));
return NULL;
}
memcpy(new_entry->key, key, key_length);
new_entry->key_length = key_length;
memcpy(new_entry->value, value, hashmap->config.value_size);
new_entry->next = NULL;
return new_entry;
}
// Take care with the linking before
void _HashMap_DestroyEntry(hashmap_t* restrict hashmap, hashmapentry_t* restrict delete)
{
Allocator_Free(hashmap->config.allocator, delete->key, delete->key_length);
delete->key = NULL;
Allocator_Free(hashmap->config.allocator, delete, _GetEntrySize(&hashmap->config));
return;
}
bool _HashMap_EntryHasKey(hashmapentry_t* entry, const void* key, size_t key_length)
{
return entry->key_length == key_length
&& memcmp(entry->key, key, key_length) == 0;
}
hashmapentry_t** _HashMap_GetEntryWithKey(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length)
{
uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, key, key_length);
hashmapentry_t** entry = hashmap->buckets + bucket_index;
while (*entry != NULL) {
if (_HashMap_EntryHasKey(*entry, key, key_length)) {
return entry;
}
entry = &entry[0]->next;
}
return NULL;
}
// This operation can never fail
hashmapentry_t* _HashMap_CollectEntries(hashmap_t* restrict hashmap)
{
hashmapentry_t first = {
.next = NULL,
.key = NULL,
.key_length = 0,
};
hashmapentry_t* start = &first;
hashmapentry_t* end = start;
for (uint64_t i = 0; i < hashmap->capacity; i++) {
hashmapentry_t* current = hashmap->buckets[i];
if (current == NULL) {
continue;
}
end->next = current;
while (end->next != NULL) end = end->next;
}
return first.next;
}
void _HashMap_EmplaceEntry(hashmap_t* restrict hashmap, hashmapentry_t* restrict entry)
{
uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, entry->key, entry->key_length);
hashmapentry_t** destination = hashmap->buckets + bucket_index;
entry->next = *destination;
*destination = entry;
return;
}
int _HashMap_PreCheckLoadFactor(hashmap_t* restrict hashmap)
{
double capacity = (double) hashmap->capacity;
double used = 1 + (double) hashmap->used;
if ((used / capacity) < hashmap->config.load_factor) {
return EXIT_SUCCESS;
}
size_t new_capacity = hashmap->capacity * hashmap->config.growth_factor;
size_t old_bytes = hashmap->capacity * _GetEntrySize(&hashmap->config);
size_t new_bytes = new_capacity * _GetEntrySize(&hashmap->config);
hashmapentry_t** new_buckets = Allocator_Reallocate(hashmap->config.allocator, hashmap->buckets, old_bytes, new_bytes);
if (new_buckets == NULL) {
return ENOMEM;
}
hashmap->buckets = new_buckets;
// Cannot fail
hashmapentry_t* entries = _HashMap_CollectEntries(hashmap);
// NULL it all
memset(new_buckets, 0, new_bytes);
// NOW update the capacity
hashmap->capacity = new_capacity;
while (entries != NULL) {
hashmapentry_t* next = entries->next;
_HashMap_EmplaceEntry(hashmap, entries);
entries = next;
}
return EXIT_SUCCESS;
}
int HashMap_Put(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value)
{
if (hashmap == NULL) {
return EINVAL;
}
if (_HashMap_PreCheckLoadFactor(hashmap)) {
return ENOMEM;
}
// I don't call the substitute single functions here because it's slightly more effective not to do so
uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, key, key_length);
hashmapentry_t** entry = hashmap->buckets + bucket_index;
while (entry[0] != NULL) {
if (_HashMap_EntryHasKey(*entry, key, key_length)) {
// Key is already present?
return EBADSTATE;
}
entry = &entry[0]->next;
}
hashmapentry_t* new_entry = _HashMap_CreateEntry(hashmap, key, key_length, value);
if (new_entry == NULL) {
return ENOMEM;
}
*entry = new_entry;
hashmap->used++;
return EXIT_SUCCESS;
}
int HashMap_Get(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value_storage)
{
if (hashmap == NULL) {
return EINVAL;
}
if (key == NULL) {
return EINVAL;
}
hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length);
if (entry == NULL) {
return ENOTFOUND;
}
memcpy(value_storage, entry[0]->value, hashmap->config.value_size);
return EXIT_SUCCESS;
}
int HashMap_Update(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* new_value)
{
hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length);
if (entry == NULL) {
return ENOTFOUND;
}
memcpy(entry[0]->value, new_value, hashmap->config.value_size);
return EXIT_SUCCESS;
}
int HashMap_Remove(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length)
{
if (hashmap == NULL) {
return EINVAL;
}
if (key == NULL) {
return EINVAL;
}
hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length);
if (entry == NULL) {
return ENOTFOUND;
}
hashmapentry_t* delete = *entry;
// Make the linked list skip it
*entry = entry[0]->next;
_HashMap_DestroyEntry(hashmap, delete);
hashmap->used--;
return EXIT_SUCCESS;
}
int HashMap_ValueByIndex(hashmap_t* restrict hashmap, size_t index, void* store_here)
{
hashmapentry_t* entry;
int fetch_code = HashMap_EntryByIndex(hashmap, index, &entry);
if (fetch_code) {
return fetch_code;
}
memcpy(store_here, entry->value, hashmap->config.value_size);
return EXIT_SUCCESS;
}
int HashMap_EntryByIndex(hashmap_t* restrict hashmap, size_t index, hashmapentry_t** store_here)
{
if (hashmap == NULL) {
return EINVAL;
}
if (store_here == NULL) {
return EDESTADDRREQ;
}
if (index > hashmap->used) {
return EBOUNDS;
}
size_t current_index = SIZE_MAX;
size_t bucket_index = 0;
hashmapentry_t* entry = hashmap->buckets[bucket_index];
while (current_index != index || entry == NULL) {
if (entry != NULL) {
entry = entry->next;
} else {
bucket_index++;
entry = hashmap->buckets[bucket_index];
}
current_index += (entry != NULL);
}
memcpy(store_here, &entry, sizeof(entry));
return EXIT_SUCCESS;
}
size_t HashMap_Size(hashmap_t* restrict hashmap)
{
return hashmap->used;
}

67
src/hashmap/hashmap.h Normal file
View file

@ -0,0 +1,67 @@
/*
* This code is part of the strategy game operational-space.
* operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0.
* Copyright (C) 2024 VegOwOtenks, Sleppo04
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef COMMONHASHMAP_H
#define COMMONHASHMAP_H
#include <stdbool.h>
#include <stddef.h>
#include "../siphash/siphash.h"
#include "../allocator-interface/allocator-interface.h"
typedef struct HashMapEntry {
void* key;
size_t key_length;
struct HashMapEntry* next;
char value[];
} hashmapentry_t;
typedef struct HashMapConfig {
double load_factor;
double growth_factor;
size_t value_size;
allocator_t* allocator;
siphashconfig_t siphash_config;
} hashmapconfig_t;
typedef struct HashMap {
size_t capacity;
size_t used;
struct HashMapEntry** buckets;
struct HashMapConfig config;
} hashmap_t;
struct HashMapConfig HashMap_DefaultConfig(void);
int HashMap_Create(hashmap_t* restrict destination, struct HashMapConfig* restrict config, size_t initial_capacity);
void HashMap_Destroy(hashmap_t* restrict hashmap);
int HashMap_Put(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value);
int HashMap_Remove(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length);
int HashMap_Get(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value_storage);
int HashMap_Update(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* new_value);
int HashMap_ValueByIndex(hashmap_t* restrict hashmap, size_t index, void* store_here);
int HashMap_EntryByIndex(hashmap_t* restrict hashmap, size_t index, hashmapentry_t** store_here);
size_t HashMap_Size(hashmap_t* restrict hashmap);
#endif /* SRC_COMMON_HASHMAP_HASHMAP_H_ */