All Projects → Srekel → the-entitytainer

Srekel / the-entitytainer

Licence: other
A single header library for managing game entity hierarchies.

Programming Languages

c
50402 projects - #5 most used programming language

Projects that are alternatives of or similar to the-entitytainer

data-structures-algorithms
Self-practice in Data Structures & Algorithms
Stars: ✭ 29 (-6.45%)
Mutual labels:  data-structure
Algorithms
Java implementation for Introduction to Algorithms book.
Stars: ✭ 58 (+87.1%)
Mutual labels:  data-structure
hypergraph
Hypergraph is data structure library to create a directed hypergraph in which a hyperedge can join any number of vertices.
Stars: ✭ 205 (+561.29%)
Mutual labels:  data-structure
10weeks-codingtest
구름EDU 10주완성 알고리즘 코딩테스트의 해설 답안집입니다
Stars: ✭ 122 (+293.55%)
Mutual labels:  data-structure
smtp-client
SMTP Client Library in C
Stars: ✭ 68 (+119.35%)
Mutual labels:  public-domain
lc-wikidata
Library Carpentry Wikidata
Stars: ✭ 17 (-45.16%)
Mutual labels:  pre-alpha
html3dutil
A public domain JavaScript library to ease developing HTML 3D apps
Stars: ✭ 29 (-6.45%)
Mutual labels:  public-domain
Swift-X-Algorithms
🔨 Algorithms & Data Structures implemented in Swift X. `let X = 5.0`
Stars: ✭ 22 (-29.03%)
Mutual labels:  data-structure
dask-awkward
Native Dask collection for awkward arrays, and the library to use it.
Stars: ✭ 25 (-19.35%)
Mutual labels:  data-structure
data-structure-ts
Basic data structures and popular algorithms implemented in Typescript.
Stars: ✭ 14 (-54.84%)
Mutual labels:  data-structure
array-keyed-map
JS datastructure, like Map, but the keys are arrays
Stars: ✭ 29 (-6.45%)
Mutual labels:  data-structure
multimap
A map in which more than one value may be stored under each key.
Stars: ✭ 28 (-9.68%)
Mutual labels:  multimap
Swift-AppleScriptObjC
How to call AppleScript handlers from Swift via AppleScript-ObjC bridge.
Stars: ✭ 45 (+45.16%)
Mutual labels:  public-domain
playground
A place to play programming
Stars: ✭ 21 (-32.26%)
Mutual labels:  data-structure
ds
🔗 Common Data Structures and Algorithms
Stars: ✭ 40 (+29.03%)
Mutual labels:  data-structure
sun
Simple library and application that shows sunset and sunrise based on your latitude,longitude
Stars: ✭ 23 (-25.81%)
Mutual labels:  public-domain
C-language
C语言练习代码
Stars: ✭ 186 (+500%)
Mutual labels:  data-structure
exercises.json
Open Public Domain Exercise Dataset in JSON format
Stars: ✭ 49 (+58.06%)
Mutual labels:  public-domain
gnuplot-examples
GNUPlot Examples
Stars: ✭ 50 (+61.29%)
Mutual labels:  public-domain
Rboxlo
Roblox private server
Stars: ✭ 173 (+458.06%)
Mutual labels:  public-domain

The Entitytainer

:bowtie:

A C99 single header library for managing entity hierarchies.

Basically a multimap (not really) implementation in C, aimed at game development.

Its main purpose is to keep track of hierarchies of entities. This can be useful for:

  • Attachments (e.g. holding a weapon in the hand) i
  • Inventory (having a piece of cheese in a bag in the backpack on the back of a character)
  • A workplace hierarchy, keeping track of who's the boss of who, for example.
  • Managing a files-and-folders structure (not necessarily related to either games or entities!)

Problem statement

You want a one-to-many relationship but you don't want lots and lots of small array allocations.

For example, let's say you are trying to add support for characters in your game to have an inventory. Of course you want to do this by placing zero or more entities (items) into your inventory. Furiously, you start typing. Hours later you gaze upon your creation:

typedef int Entity;

struct Inventory {
    Entity m_Entity;
    std::vector<Entity> m_ItemEntities;
};

struct InventorySystem {
    std::vector<Inventory> m_Inventories;
};

You realize that this is bad. And not just because you're using STL. But how to do it properly?

This is what The Entitytainer solves.

Features

  • It only needs one allocation.
    • It's up to the application to decide on an appropriate size beforehand. This means it's using more memory than necessary until you start to fill it up. On the other hand, generally you have a worst case that you need to handle anyway. ¯\_(ツ)_/¯
  • Can be dynamically reallocated (i.e. grown) - controlled by application.
    • And it's pretty quick too, just a couple of memcpy's.
  • A hierarchical bucket system is used to not waste memory.
  • O(1) lookup, add, removal.
    • That said, you have to pay the price of a few indirections and a bit of math. Only you and your platform can say whether that's better or worse than a lot of small allocations.
  • Reverse lookup to get parent from a child.
  • Optionally supports child lists with holes, for when you don't want to rearrange elements when you remove something in the middle.
  • Provides Save/Load that only does a single memcpy + a few pointer fixups.
  • Optionally supports not shrinking to a smaller bucket when removing children.
  • Politely coded:
    • C99 compatible (or aims to be).
    • Platform agnostic (or aims to be).
    • Zero dependencies. (overridable #defines for all standard functions, e.g. memcpy)
    • Built with maximum/pedantic warnings, and warnings as error.
    • Code formatted with clang-format.
    • There are unit tests! A fair amount of them actually.

Current status

Seems to work. Is used in production.

Known issues

  • Only tested on Windows 10 using VS 2017 running x64.
  • Reallocation is currently commented out due to some refactoring.
  • API is not finalized. Would like to add a bit more customization and allow for passing in arrays of entities instead of one at a time.

How to use

int   max_num_entries     = 1024;
int   bucket_sizes[]      = { 4, 16, 256 };
int   bucket_list_sizes[] = { 4, 2, 2 };
int   needed_memory_size  = entitytainer_needed_size( max_num_entries, bucket_sizes, bucket_list_sizes, 3 );
void* memory              = malloc( needed_memory_size );
TheEntitytainer* entitytainer =
  entitytainer_create( memory, needed_memory_size, max_num_entries, bucket_sizes, bucket_list_sizes, 3 );

entitytainer_add_entity( entitytainer, 3 );
entitytainer_add_child( entitytainer, 3, 10 );

int                    num_children;
TheEntitytainerEntity* children;
entitytainer_get_children( entitytainer, 3, &children, &num_children );
ASSERT( num_children == 1 );
ASSERT( children[0] == 10 );

Save / Load

void save( TheEntitytainer* entitytainer ) {
    int            buffer_size = entitytainer_save( entitytainer, NULL, 0 );
    unsigned char* buffer      = malloc( buffer_size );
    memset( buffer, 0, buffer_size );
    entitytainer_save( entitytainer, buffer, buffer_size );
    my_save_buffer_to_disk(buffer, buffer_size);
}

TheEntitytainer* load( const unsigned char* file_buffer, int buffer_size ) {
    unsigned char* loaded_buffer = malloc( buffer_size );
    memcpy( loaded_buffer, file_buffer, buffer_size );
    TheEntitytainer* loaded = entitytainer_load( loaded_buffer, buffer_size );
    return loaded; // Needs to be free'd
}

How it works

This image describes it at a high level.

A graph that's... colorful and complicated.

Not that you need me to explain in text what is so clearly described in the image, but...

First you decide how many entries you want. This is your maximum entity count. Note, it's NOT the maximum amount of entities you will maximally put into the entitytainer. At some point I might fix hashing but for now it's just a direct lookup based on the entity ID.

This number is used to create an array of entries. An entry is a 16 bit value that contains of two parts: The bucket list lookup and the bucket index.

The list lookup is 2 bits and shows which bucket list the entity's children are stored in. In the image example, Entity 2's children are stored in the second bucket list (index 1).

The bucket index says which bucket in the bucket list the children are stored at. To look up the children of an entity, you first get the bucket list, and then the bucket inside that list.

The bucket consists of a number of elements. The first one is a counter, saying how many children there are. This is a simple way of storing the size so The Entitytainer doesn't has to iterate every time to figure it out. It's a bit wasteful in memory but it makes things a bit easier.

After the counter comes the children.

Each bucket list has buckets of different sizes. When a child is added to an entity and the bucket is full, the bucket is copied to a new bucket in the next bucket list. Note that you probably don't want your first bucket list to have bucket size 2, like in the image, unless it's very common to have just one child. Also, this means that if you add more children than the last bucket list can have (256 in the image), The Entitytainer will fail an ASSERT.

Memory reuse

When you remove an entity, its bucket will of course be available to be used by other entities in the future. The way this works is that each bucket list has an index to the first free bucket. When you free a bucket, the bucket space is repurposed and the previous value of the first free bucket is stored there. Then the first free bucket is re-pointed to your newly freed bucket. I call this an intrinsically linked bucketed slot allocator. Do I really? No. Maybe. Is there a name for this?

It's kinda neat because it's fast to "allocate" or deallocate a bucket, and yet it needs practically no memory for bookkeeping.

But it's not really a multimap is it?

No, not really. Cause it'll complain (or ought to) if you assign the same child to two different parents. (Just to be clear - you can have deeper-than-one hierarchies though.)

License

Dual licensed under Public Domain and MIT.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].