From 568902b64e708fd54f9c6857a615cf4c767ff0ed Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
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 0545d3c4224a..d222f42cd6a3 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -468,6 +468,7 @@
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Desktop.x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
</ClCompile>
<ClCompile Include="..\..\src\SDL_guid.c" />
+ <ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_hints_c.h" />
<ClInclude Include="..\..\src\SDL_internal.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
@@ -540,6 +541,7 @@
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb.h" />
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_sse_func.h" />
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_std_func.h" />
+ <ClCompile Include="..\..\src\SDL_hashtable.c" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\atomic\SDL_atomic.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 62f79c6b1fa7..683d6b4f3e76 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -397,6 +397,7 @@
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\src\SDL_error_c.h" />
+ <ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
<ClInclude Include="..\..\include\SDL3\SDL_metal.h">
<Filter>API Headers</Filter>
@@ -845,6 +846,7 @@
<ClCompile Include="..\..\src\SDL_assert.c" />
<ClCompile Include="..\..\src\SDL_error.c" />
<ClCompile Include="..\..\src\SDL_guid.c" />
+ <ClCompile Include="..\..\src\SDL_hashtable.c" />
<ClCompile Include="..\..\src\SDL_hints.c" />
<ClCompile Include="..\..\src\SDL_list.c" />
<ClCompile Include="..\..\src\SDL_utils.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index f45e82404f95..863b0e3e397a 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -151,6 +151,7 @@
<ClInclude Include="..\src\SDL_assert_c.h" />
<ClInclude Include="..\src\SDL_error_c.h" />
<ClInclude Include="..\src\SDL_fatal.h" />
+ <ClInclude Include="..\src\SDL_hashtable.h" />
<ClInclude Include="..\src\SDL_hints_c.h" />
<ClInclude Include="..\src\SDL_internal.h" />
<ClInclude Include="..\src\SDL_list.h" />
@@ -391,6 +392,7 @@
<ClCompile Include="..\src\render\software\SDL_triangle.c" />
<ClCompile Include="..\src\SDL.c" />
<ClCompile Include="..\src\SDL_assert.c" />
+ <ClCompile Include="..\src\SDL_hashtable.c" />
<ClCompile Include="..\src\SDL_list.c" />
<ClCompile Include="..\src\SDL_error.c" />
<ClCompile Include="..\src\SDL_guid.c" />
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index 8edd716fe466..a1c99a51e712 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -324,6 +324,7 @@
<ClInclude Include="..\src\SDL_fatal.h">
<Filter>Source Files</Filter>
</ClInclude>
+ <ClInclude Include="..\src\SDL_hashtable.h" />
<ClInclude Include="..\src\SDL_hints_c.h">
<Filter>Source Files</Filter>
</ClInclude>
@@ -639,6 +640,7 @@
<ClCompile Include="..\src\SDL_guid.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\src\SDL_hashtable.c" />
<ClCompile Include="..\src\SDL_hints.c">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index e35540408280..31263605cea0 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -395,6 +395,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\..\src\SDL_guid.c" />
+ <ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_hints_c.h" />
<ClInclude Include="..\..\src\SDL_internal.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
@@ -466,6 +467,7 @@
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb.h" />
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_sse_func.h" />
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_std_func.h" />
+ <ClCompile Include="..\..\src\SDL_hashtable.c" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\atomic\SDL_atomic.c" />
@@ -662,4 +664,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index dbf4e543eef0..df44db6b2de1 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -388,6 +388,7 @@
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\src\SDL_error_c.h" />
+ <ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
<ClInclude Include="..\..\include\SDL3\SDL_metal.h">
<Filter>API Headers</Filter>
@@ -824,6 +825,7 @@
<ClCompile Include="..\..\src\SDL_assert.c" />
<ClCompile Include="..\..\src\SDL_error.c" />
<ClCompile Include="..\..\src\SDL_guid.c" />
+ <ClCompile Include="..\..\src\SDL_hashtable.c" />
<ClCompile Include="..\..\src\SDL_hints.c" />
<ClCompile Include="..\..\src\SDL_list.c" />
<ClCompile Include="..\..\src\SDL_utils.c" />
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 90e2ccb99b63..2ed36dcb0408 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 = "<group>"; };
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 = "<group>"; };
+ 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_hashtable.h; path = SDL_hashtable.h; sourceTree = "<group>"; };
/* 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 000000000000..59f2999c9906
--- /dev/null
+++ b/src/SDL_hashtable.c
@@ -0,0 +1,259 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+ 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 000000000000..1e0a611c1e76
--- /dev/null
+++ b/src/SDL_hashtable.h
@@ -0,0 +1,53 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+ 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: */
+