Skip to content

Schwungus/S_tructures

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

S_tructures

Useful dynamic data structures for plain C. Currently only implements tiny-hashmaps.

Basic usage

Installation

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: keep

And you're ready to go! Just #include "S_tructures.h" in your code, and have fun using these crutches.

Tiny-hashmaps

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:

  1. 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 StHashStr to get a tiny-map-compatible key from a string key.

  2. 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!

  3. 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.data to anything, without your program hardcrashing. E.g. when it is the bucket:

    typedef struct { int x, y; } Enemy;
    
    void DrawEnemy(Enemy this) { /* ... */ }
    
    TinyMap* enemies = MakeTinyMap();
    ST_FOREACH (enemies, it) {
      DrawEnemy(*(Enemy*)it.data);
    }
  4. 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.

Advanced use-cases

Custom allocator

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: keep

Make 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.

Custom logger

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)

TinyBucket cleanup function

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

About

Premade data structures for plain C

Topics

Resources

License

Stars

Watchers

Forks

Contributors