From 568902b64e708fd54f9c6857a615cf4c767ff0ed Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 9 Oct 2023 19:17:05 -0400 Subject: [PATCH] hashtable: Added src/SDL_hashtable.[ch]. Reference Issue #7799. --- VisualC-GDK/SDL/SDL.vcxproj | 2 + VisualC-GDK/SDL/SDL.vcxproj.filters | 2 + VisualC-WinRT/SDL-UWP.vcxproj | 2 + VisualC-WinRT/SDL-UWP.vcxproj.filters | 2 + VisualC/SDL/SDL.vcxproj | 4 +- VisualC/SDL/SDL.vcxproj.filters | 2 + Xcode/SDL/SDL.xcodeproj/project.pbxproj | 7 + src/SDL_hashtable.c | 259 ++++++++++++++++++++++++ src/SDL_hashtable.h | 53 +++++ 9 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 src/SDL_hashtable.c create mode 100644 src/SDL_hashtable.h diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 0545d3c42..d222f42cd 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -468,6 +468,7 @@ $(IntDir)$(TargetName)_cpp.pch + @@ -540,6 +541,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 62f79c6b1..683d6b4f3 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -397,6 +397,7 @@ API Headers + API Headers @@ -845,6 +846,7 @@ + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index f45e82404..863b0e3e3 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -151,6 +151,7 @@ + @@ -391,6 +392,7 @@ + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index 8edd716fe..a1c99a51e 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -324,6 +324,7 @@ Source Files + Source Files @@ -639,6 +640,7 @@ Source Files + Source Files diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index e35540408..31263605c 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -395,6 +395,7 @@ Create + @@ -466,6 +467,7 @@ + @@ -662,4 +664,4 @@ - \ No newline at end of file + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index dbf4e543e..df44db6b2 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -388,6 +388,7 @@ API Headers + API Headers @@ -824,6 +825,7 @@ + diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 90e2ccb99..2ed36dcb0 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -469,6 +469,8 @@ F3F7D9E12933074E00816151 /* SDL_begin_code.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E72933074E00816151 /* SDL_begin_code.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3F7D9E52933074E00816151 /* SDL_system.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E82933074E00816151 /* SDL_system.h */; settings = {ATTRIBUTES = (Public, ); }; }; FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); }; + 000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = 000078E1881E857EBB6C0000 /* SDL_hashtable.c */; }; + 0000CE8F0EF0E7EF781D0000 /* SDL_hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -962,6 +964,8 @@ F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = ""; }; F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; }; FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; + 000078E1881E857EBB6C0000 /* SDL_hashtable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_hashtable.c; path = SDL_hashtable.c; sourceTree = ""; }; + 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_hashtable.h; path = SDL_hashtable.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1129,6 +1133,8 @@ F386F6E52884663E001840AA /* SDL_utils_c.h */, F386F6E62884663E001840AA /* SDL_utils.c */, A7D8A57123E2513D00DCD162 /* SDL.c */, + 000078E1881E857EBB6C0000 /* SDL_hashtable.c */, + 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */, ); name = "Library Source"; path = ../../src; @@ -2584,6 +2590,7 @@ A7D8AEA023E2514100DCD162 /* SDL_cocoavulkan.m in Sources */, A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */, 566E26D8246274CC00718109 /* SDL_locale.c in Sources */, + 000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/SDL_hashtable.c b/src/SDL_hashtable.c new file mode 100644 index 000000000..59f2999c9 --- /dev/null +++ b/src/SDL_hashtable.c @@ -0,0 +1,259 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" +#include "SDL_hashtable.h" + +typedef struct SDL_HashItem +{ + const void *key; + const void *value; + struct SDL_HashItem *next; +} SDL_HashItem; + +struct SDL_HashTable +{ + SDL_HashItem **table; + Uint32 table_len; + SDL_bool stackable; + void *data; + SDL_HashTable_HashFn hash; + SDL_HashTable_KeyMatchFn keymatch; + SDL_HashTable_NukeFn nuke; +}; + +SDL_HashTable *SDL_NewHashTable(void *data, const Uint32 num_buckets, const SDL_HashTable_HashFn hashfn, + const SDL_HashTable_KeyMatchFn keymatchfn, + const SDL_HashTable_NukeFn nukefn, + const SDL_bool stackable) +{ + SDL_HashTable *table; + + /* num_buckets must be a power of two so we get a solid block of bits to mask hash values against. */ + if ((num_buckets == 0) || ((num_buckets & (num_buckets - 1)) != 0)) { + SDL_SetError("num_buckets must be a power of two"); + return NULL; + } + + table = (SDL_HashTable *) SDL_calloc(1, sizeof (SDL_HashTable)); + if (table == NULL) { + SDL_OutOfMemory(); + return NULL; + } + + table->table = (SDL_HashItem **) SDL_calloc(num_buckets, sizeof (SDL_HashItem *)); + if (table->table == NULL) { + SDL_free(table); + SDL_OutOfMemory(); + return NULL; + } + + table->table_len = num_buckets; + table->stackable = stackable; + table->data = data; + table->hash = hashfn; + table->keymatch = keymatchfn; + table->nuke = nukefn; + return table; +} + +static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key) +{ + return table->hash(key, table->data) & (table->table_len - 1); +} + + +SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value) +{ + SDL_HashItem *item; + const Uint32 hash = calc_hash(table, key); + + if ( (!table->stackable) && (SDL_FindInHashTable(table, key, NULL)) ) { + return SDL_FALSE; + } + + /* !!! FIXME: grow and rehash table if it gets too saturated. */ + item = (SDL_HashItem *) SDL_malloc(sizeof (SDL_HashItem)); + if (item == NULL) { + SDL_OutOfMemory(); + return SDL_FALSE; + } + + item->key = key; + item->value = value; + item->next = table->table[hash]; + table->table[hash] = item; + + return SDL_TRUE; +} + +SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value) +{ + const Uint32 hash = calc_hash(table, key); + void *data = table->data; + SDL_HashItem *prev = NULL; + SDL_HashItem *i; + + for (i = table->table[hash]; i != NULL; i = i->next) { + if (table->keymatch(key, i->key, data)) { + if (_value != NULL) { + *_value = i->value; + } + + /* Matched! Move to the front of list for faster lookup next time. + (stackable tables have to remain in the same order, though!) */ + if ((!table->stackable) && (prev != NULL)) { + SDL_assert(prev->next == i); + prev->next = i->next; + i->next = table->table[hash]; + table->table[hash] = i; + } + + return SDL_TRUE; + } + + prev = i; + } + + return SDL_FALSE; +} + +SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key) +{ + const Uint32 hash = calc_hash(table, key); + SDL_HashItem *item = NULL; + SDL_HashItem *prev = NULL; + void *data = table->data; + + for (item = table->table[hash]; item != NULL; item = item->next) { + if (table->keymatch(key, item->key, data)) { + if (prev != NULL) { + prev->next = item->next; + } else { + table->table[hash] = item->next; + } + + table->nuke(item->key, item->value, data); + SDL_free(item); + return SDL_TRUE; + } + + prev = item; + } + + return SDL_FALSE; +} + +SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void *key, const void **_value, void **iter) +{ + SDL_HashItem *item = iter ? ((SDL_HashItem *) *iter)->next : table->table[calc_hash(table, key)]; + + while (item != NULL) { + if (table->keymatch(key, item->key, table->data)) { + *_value = item->value; + *iter = item; + return SDL_TRUE; + } + item = item->next; + } + + /* no more matches. */ + *_value = NULL; + *iter = NULL; + return SDL_FALSE; +} + +SDL_bool SDL_IterateHashTableKeys(const SDL_HashTable *table, const void **_key, void **iter) +{ + SDL_HashItem *item = (SDL_HashItem *) *iter; + Uint32 idx = 0; + + if (item != NULL) { + const SDL_HashItem *orig = item; + item = item->next; + if (item == NULL) { + idx = calc_hash(table, orig->key) + 1; + } + } + + while (!item && (idx < table->table_len)) { + item = table->table[idx++]; /* skip empty buckets... */ + } + + if (item == NULL) { /* no more matches? */ + *_key = NULL; + *iter = NULL; + return SDL_FALSE; + } + + *_key = item->key; + *iter = item; + return SDL_TRUE; +} + +void SDL_FreeHashTable(SDL_HashTable *table) +{ + if (table != NULL) { + void *data = table->data; + Uint32 i; + + for (i = 0; i < table->table_len; i++) { + SDL_HashItem *item = table->table[i]; + while (item != NULL) { + SDL_HashItem *next = item->next; + table->nuke(item->key, item->value, data); + SDL_free(item); + item = next; + } + } + + SDL_free(table->table); + SDL_free(table); + } +} + +/* this is djb's xor hashing function. */ +static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len) +{ + Uint32 hash = 5381; + while (len--) { + hash = ((hash << 5) + hash) ^ *(str++); + } + return hash; +} + +Uint32 SDL_HashString(const void *sym, void *data) +{ + const char *str = (const char*) sym; + return hash_string_djbxor(str, SDL_strlen((const char *) str)); +} + +SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *data) +{ + if (a == b) { + return SDL_TRUE; /* same pointer, must match. */ + } else if (!a || !b) { + return SDL_FALSE; /* one pointer is NULL (and first test shows they aren't the same pointer), must not match. */ + } + return (SDL_strcmp((const char *) a, (const char *) b) == 0) ? SDL_TRUE : SDL_FALSE; /* Check against actual string contents. */ +} + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/SDL_hashtable.h b/src/SDL_hashtable.h new file mode 100644 index 000000000..1e0a611c1 --- /dev/null +++ b/src/SDL_hashtable.h @@ -0,0 +1,53 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2022 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifndef SDL_hashtable_h_ +#define SDL_hashtable_h_ + +/* this is not (currently) a public API. But maybe it should be! */ + +struct SDL_HashTable; +typedef struct SDL_HashTable SDL_HashTable; +typedef Uint32 (*SDL_HashTable_HashFn)(const void *key, void *data); +typedef SDL_bool (*SDL_HashTable_KeyMatchFn)(const void *a, const void *b, void *data); +typedef void (*SDL_HashTable_NukeFn)(const void *key, const void *value, void *data); + +SDL_HashTable *SDL_NewHashTable(void *data, + const Uint32 num_buckets, + const SDL_HashTable_HashFn hashfn, + const SDL_HashTable_KeyMatchFn keymatchfn, + const SDL_HashTable_NukeFn nukefn, + const SDL_bool stackable); + +void SDL_FreeHashTable(SDL_HashTable *table); +SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value); +SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key); +SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value); + +SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void *key, const void **_value, void **iter); +SDL_bool SDL_IterateHashTableKeys(const SDL_HashTable *table, const void **_key, void **iter); + +Uint32 SDL_HashString(const void *sym, void *unused); +SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *unused); + +#endif /* SDL_hashtable_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ +