Useful dynamic data structures for plain C. Currently only implements tiny-hashmaps.
Include the library in your CMake project by modifying your CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
S_tructures
GIT_REPOSITORY https://github.com/Schwungus/S_tructures.git
GIT_TAG master) # or pin to a specific version by commit SHA
FetchContent_MakeAvailable(S_tructures)
# Don't forget to add S_tructures to your program:
target_link_libraries(myProgram S_tructures)Then add a S_tructures.c file with the library's implementation details:
#define S_TRUCTURES_IMPLEMENTATION
#include "S_tructures.h" // IWYU pragma: keepAnd you're ready to go! Just #include "S_tructures.h" in your code, and have fun using these crutches.
For now, we only have "tiny" hashmaps to offer. Here's a quick appetizer to get you started:
TinyMap* map = MakeTinyMap();
TinyMapPut(map, StHashStr("greeting"), "hello", strlen("hello!") + 1);
TinyMapPut(map, StHashStr("name"), "Bob!", strlen("Bob!") + 1);
ST_FOREACH (map, it)
printf("%s\n", (char*)it.data);
FreeTinyMap(map), map = NULL;The tiny hashmaps have the following properties:
-
Tiny-maps don't store the whole key you put in them, only an 8 byte hash at most. This is also why you need to use
StHashStrto get a tiny-map-compatible key from a string key. -
Tiny-maps don't handle hash collisions, at all, due to the point above. If this issue breaks your program, you should probably buy a lottery ticket!
-
Values inside tiny-map buckets are dynamically typed. As long as you're handling your data in a sane way, you should be able to pointer-cast
StTinyBucket.datato anything, without your program hardcrashing. E.g. whenitis the bucket:typedef struct { int x, y; } Enemy; void DrawEnemy(Enemy this) { /* ... */ } TinyMap* enemies = MakeTinyMap(); ST_FOREACH (enemies, it) { DrawEnemy(*(Enemy*)it.data); }
-
Iterating over key-value pairs isn't guaranteed to result in the pairs coming in the same order they were inserted.
Take a look into our testbed for an overview of what other things our tiny-map hashmaps implementation can do.
In your S_tructures.c, you can customize the memory allocator by defining StAlloc, StFree, StMemset, and StMemcpy. For example, to use memory utilities provided by SDL3, you can define:
#include <SDL3/SDL_stdinc.h>
#define StAlloc SDL_malloc
#define StFree SDL_free
#define StMemset SDL_memset
#define StMemcpy SDL_memcpy
#define S_TRUCTURES_IMPLEMENTATION
#include "S_tructures.h" // IWYU pragma: keepMake sure to define StAlloc & StFree and StMemset & StMemcpy in pairs. Doing otherwise will not compile as it's a logic error; i.e. your custom malloc implementation should almost always come with its own custom free if you get the gist.
StLog is called when spitting useful error messages, and can be customized as well. Here's how it's defined by default:
#include <stdio.h>
#define StLog(...) \
do { \
fprintf(stdout, "[S_tr]: %c" __VA_ARGS__, '\n'); \
fflush(stdout); \
} while (0)You can set a custom cleanup function to call before deallocating data from a bucket. For example:
static void CleanupTexture(void* ptr) {
// pointer-cast & dereference since the function takes a value of type `Texture`:
UnloadTexture(*(Texture*)ptr);
}
TinyMap* map = MakeTinyMap();
Texture tx = LoadTexture("...");
TinyBucket* bucket = TinyDictPut(map, "MyTex", &tx, sizeof(tx)); // copies `tx` into `map`
bucket->cleanup = CleanupTexture;
FreeTinyMap(map); // `CleanupTexture` will be called with a pointer to texture data as the argument