├── .clang-format ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── docs ├── visual_explanation.pdn └── visual_explanation.png ├── tests ├── the-entitytainer.sln └── unittest │ ├── unittest.c │ ├── unittest.h │ ├── unittest.vcxproj │ ├── unittest.vcxproj.filters │ ├── unittest.vcxproj.user │ ├── unittest_base.c │ ├── unittest_default.c │ └── unittest_entity_32.c ├── the_entitytainer.h └── the_entitytainer_2.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: '-2' 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: 'true' 6 | AlignConsecutiveDeclarations: 'true' 7 | AlignEscapedNewlinesLeft: 'true' 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'true' 10 | AllowAllParametersOfDeclarationOnNextLine: 'false' 11 | AllowShortBlocksOnASingleLine: 'false' 12 | AllowShortCaseLabelsOnASingleLine: 'false' 13 | AllowShortFunctionsOnASingleLine: Inline 14 | AllowShortIfStatementsOnASingleLine: 'false' 15 | AllowShortLoopsOnASingleLine: 'false' 16 | AlwaysBreakAfterReturnType: TopLevelDefinitions 17 | AlwaysBreakBeforeMultilineStrings: 'true' 18 | AlwaysBreakTemplateDeclarations: 'true' 19 | BinPackArguments: 'false' 20 | BinPackParameters: 'false' 21 | BraceWrapping: { 22 | AfterClass: 'false' 23 | AfterControlStatement: 'false' 24 | AfterEnum : 'false' 25 | AfterFunction : 'false' 26 | AfterNamespace : 'false' 27 | AfterStruct : 'false' 28 | AfterUnion : 'false' 29 | BeforeCatch : 'false' 30 | BeforeElse : 'true' 31 | IndentBraces : 'false' 32 | } 33 | BreakBeforeBraces: 'Custom' 34 | BreakBeforeTernaryOperators: 'true' 35 | BreakConstructorInitializersBeforeComma: 'true' 36 | BreakStringLiterals: 'false' 37 | ColumnLimit: '120' 38 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 39 | ConstructorInitializerIndentWidth: '0' 40 | ContinuationIndentWidth: '2' 41 | Cpp11BracedListStyle: 'false' 42 | DerivePointerAlignment: 'false' 43 | 44 | # Disabled for now, seems it's not supported in LLVM 4.0 on windows? 45 | # FixNamespaceComments: 'true' 46 | 47 | IndentCaseLabels: 'false' 48 | IndentWidth: '4' 49 | IndentWrappedFunctionNames: 'false' 50 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 51 | MaxEmptyLinesToKeep: '1' 52 | NamespaceIndentation: None 53 | ObjCBlockIndentWidth: '2' 54 | ObjCSpaceAfterProperty: 'true' 55 | ObjCSpaceBeforeProtocolList: 'true' 56 | PointerAlignment: Left 57 | ReflowComments: 'true' 58 | SortIncludes: 'true' 59 | SpaceAfterCStyleCast: 'false' 60 | SpaceBeforeAssignmentOperators: 'true' 61 | SpaceBeforeParens: ControlStatements 62 | SpaceInEmptyParentheses: 'false' 63 | SpacesBeforeTrailingComments: '1' 64 | SpacesInAngles: 'false' 65 | SpacesInCStyleCastParentheses: 'false' 66 | SpacesInContainerLiterals: 'false' 67 | SpacesInParentheses: 'true' 68 | SpacesInSquareBrackets: 'false' 69 | Standard: Cpp11 70 | TabWidth: '2' 71 | UseTab: Never 72 | 73 | ... 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | tests/.vs/ 34 | tests/Build/ 35 | tests/unittest/x64/ 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "entitytainer" 4 | ], 5 | "files.associations": { 6 | "*.shader": "cpp", 7 | "the_entitytainer.h": "c" 8 | } 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Entitytainer 2 | 3 | :bowtie: 4 | 5 | A C99 single header library for managing entity hierarchies. 6 | 7 | Basically a multimap (not really) implementation in C, aimed at game development. 8 | 9 | Its main purpose is to keep track of hierarchies of entities. This can be useful for: 10 | 11 | * Attachments (e.g. holding a weapon in the hand) i 12 | * Inventory (having a piece of cheese in a bag in the backpack on the back of a character) 13 | * A workplace hierarchy, keeping track of who's the boss of who, for example. 14 | * Managing a files-and-folders structure (not necessarily related to either games or entities!) 15 | 16 | ## Problem statement 17 | 18 | You want a one-to-many relationship but you don't want lots and lots of small array allocations. 19 | 20 | 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: 21 | 22 | ```C 23 | typedef int Entity; 24 | 25 | struct Inventory { 26 | Entity m_Entity; 27 | std::vector m_ItemEntities; 28 | }; 29 | 30 | struct InventorySystem { 31 | std::vector m_Inventories; 32 | }; 33 | ``` 34 | 35 | You realize that this is bad. And not just because you're using STL. But how to do it properly? 36 | 37 | This is what The Entitytainer solves. 38 | 39 | ## Features 40 | 41 | * It only needs one allocation. 42 | * 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. ¯\\\_(ツ)_/¯ 43 | * Can be dynamically reallocated (i.e. grown) - controlled by application. 44 | * And it's pretty quick too, just a couple of memcpy's. 45 | * A hierarchical bucket system is used to not waste memory. 46 | * O(1) lookup, add, removal. 47 | * 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. 48 | * Reverse lookup to get parent from a child. 49 | * Optionally supports child lists with holes, for when you don't want to rearrange elements when you remove something in the middle. 50 | * Provides Save/Load that only does a single memcpy + a few pointer fixups. 51 | * Optionally supports not shrinking to a smaller bucket when removing children. 52 | * Politely coded: 53 | * C99 compatible (or aims to be). 54 | * Platform agnostic (or aims to be). 55 | * Zero dependencies. (overridable #defines for all standard functions, e.g. memcpy) 56 | * Built with maximum/pedantic warnings, and warnings as error. 57 | * Code formatted with clang-format. 58 | * There are unit tests! A fair amount of them actually. 59 | 60 | ## Current status 61 | 62 | Seems to work. Is used in production. 63 | 64 | ## Known issues 65 | 66 | * Only tested on Windows 10 using VS 2017 running x64. 67 | * Reallocation is currently commented out due to some refactoring. 68 | * 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. 69 | 70 | ## How to use 71 | 72 | ```C 73 | int max_num_entries = 1024; 74 | int bucket_sizes[] = { 4, 16, 256 }; 75 | int bucket_list_sizes[] = { 4, 2, 2 }; 76 | int needed_memory_size = entitytainer_needed_size( max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 77 | void* memory = malloc( needed_memory_size ); 78 | TheEntitytainer* entitytainer = 79 | entitytainer_create( memory, needed_memory_size, max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 80 | 81 | entitytainer_add_entity( entitytainer, 3 ); 82 | entitytainer_add_child( entitytainer, 3, 10 ); 83 | 84 | int num_children; 85 | TheEntitytainerEntity* children; 86 | entitytainer_get_children( entitytainer, 3, &children, &num_children ); 87 | ASSERT( num_children == 1 ); 88 | ASSERT( children[0] == 10 ); 89 | 90 | ``` 91 | 92 | ### Save / Load 93 | 94 | ```C 95 | void save( TheEntitytainer* entitytainer ) { 96 | int buffer_size = entitytainer_save( entitytainer, NULL, 0 ); 97 | unsigned char* buffer = malloc( buffer_size ); 98 | memset( buffer, 0, buffer_size ); 99 | entitytainer_save( entitytainer, buffer, buffer_size ); 100 | my_save_buffer_to_disk(buffer, buffer_size); 101 | } 102 | 103 | TheEntitytainer* load( const unsigned char* file_buffer, int buffer_size ) { 104 | unsigned char* loaded_buffer = malloc( buffer_size ); 105 | memcpy( loaded_buffer, file_buffer, buffer_size ); 106 | TheEntitytainer* loaded = entitytainer_load( loaded_buffer, buffer_size ); 107 | return loaded; // Needs to be free'd 108 | } 109 | ``` 110 | 111 | 112 | ## How it works 113 | 114 | This image describes it at a high level. 115 | 116 | ![A graph that's... colorful and complicated.](docs/visual_explanation.png) 117 | 118 | Not that you need me to explain in text what is so clearly described in the image, but... 119 | 120 | 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. 121 | 122 | 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. 123 | 124 | 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). 125 | 126 | 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. 127 | 128 | 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. 129 | 130 | After the *counter* comes the children. 131 | 132 | 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. 133 | 134 | ### Memory reuse 135 | 136 | 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? 137 | 138 | It's kinda neat because it's fast to "allocate" or deallocate a bucket, and yet it needs practically no memory for bookkeeping. 139 | 140 | ## But it's not really a multimap is it? 141 | 142 | 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.) 143 | 144 | ## License 145 | 146 | Dual licensed under Public Domain and MIT. 147 | -------------------------------------------------------------------------------- /docs/visual_explanation.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Srekel/the-entitytainer/7fe725e1691fcd288726d639d996e8076a1348b9/docs/visual_explanation.pdn -------------------------------------------------------------------------------- /docs/visual_explanation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Srekel/the-entitytainer/7fe725e1691fcd288726d639d996e8076a1348b9/docs/visual_explanation.png -------------------------------------------------------------------------------- /tests/the-entitytainer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unittest", "unittest\unittest.vcxproj", "{97FE62A5-44C3-4741-882F-FC515FDC8A86}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Debug|x64.ActiveCfg = Debug|x64 17 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Debug|x64.Build.0 = Debug|x64 18 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Debug|x86.ActiveCfg = Debug|Win32 19 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Debug|x86.Build.0 = Debug|Win32 20 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Release|x64.ActiveCfg = Release|x64 21 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Release|x64.Build.0 = Release|x64 22 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Release|x86.ActiveCfg = Release|Win32 23 | {97FE62A5-44C3-4741-882F-FC515FDC8A86}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /tests/unittest/unittest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "unittest.h" 7 | 8 | static UnitTestData g_testdata; 9 | 10 | void 11 | unittest_entitytainer_assert( bool test ) { 12 | ++g_testdata.num_tests; 13 | if ( g_testdata.error_index == 256 ) { 14 | assert( false ); 15 | } 16 | if ( !test ) { 17 | memcpy( g_testdata.errors[g_testdata.error_index++], "LOL", 4 ); 18 | } 19 | // DebugBreak(); 20 | } 21 | 22 | int 23 | main( int argc, char** argv ) { 24 | (void)( argc ); 25 | (void)( argv ); 26 | 27 | memset( &g_testdata, 0, sizeof( g_testdata ) ); 28 | UnitTestData* testdata = &g_testdata; 29 | 30 | // unittest_run_entity32( testdata ); 31 | unittest_run_default( testdata ); 32 | 33 | // A bit of a hack. 34 | system( "pause" ); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /tests/unittest/unittest.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __cplusplus 3 | #include 4 | #endif 5 | 6 | typedef struct UnitTestData { 7 | char errors[256][256]; 8 | unsigned error_index; 9 | unsigned num_tests; 10 | } UnitTestData; 11 | 12 | void unittest_entitytainer_assert( bool test ); 13 | 14 | void unittest_run_default(UnitTestData* testdata); 15 | // void unittest_run_entity32(UnitTestData* testdata); 16 | -------------------------------------------------------------------------------- /tests/unittest/unittest.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {97FE62A5-44C3-4741-882F-FC515FDC8A86} 23 | Win32Proj 24 | thedebuginator 25 | 10.0.18362.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v142 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v142 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | $(SolutionDir)\Build\$(Platform)_$(Configuration)\ 78 | $(SolutionDir)\Build\_$(ProjectName)\$(Platform)_$(Configuration)\ 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | Level3 91 | Disabled 92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | 95 | 96 | Console 97 | true 98 | 99 | 100 | 101 | 102 | 103 | 104 | EnableAllWarnings 105 | Disabled 106 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 107 | true 108 | true 109 | 110 | 111 | Console 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | 119 | 120 | MaxSpeed 121 | true 122 | true 123 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 124 | true 125 | 126 | 127 | Console 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | Level3 136 | 137 | 138 | MaxSpeed 139 | true 140 | true 141 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | true 143 | 144 | 145 | Console 146 | true 147 | true 148 | true 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /tests/unittest/unittest.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/unittest/unittest.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/unittest/unittest_base.c: -------------------------------------------------------------------------------- 1 | 2 | #pragma warning( disable : 4710 ) // printf not inlined - I don't care. :) 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef __cplusplus 9 | #include 10 | #endif 11 | 12 | #include "unittest.h" 13 | 14 | // #define ENTITYTAINER_assert unittest_entitytainer_assert 15 | #define ASSERT unittest_entitytainer_assert 16 | #define ENTITYTAINER_IMPLEMENTATION 17 | #define ENTITYTAINER_DEFENSIVE_ASSERTS 0 18 | #define ENTITYTAINER_DEFENSIVE_CHECKS 1 19 | 20 | #define ENTITYTAINER_assert( condition, ... ) unittest_entitytainer_assert( condition ) 21 | 22 | #pragma warning( disable : 4464 ) // Include with ".." 23 | #include "../../the_entitytainer.h" 24 | #pragma warning( disable : 5045 ) // warning C5045: Compiler will insert Spectre mitigation for memory load if /Qspectre 25 | // switch specified 26 | 27 | static void 28 | do_single_parent_tests( TheEntitytainer* entitytainer ) { 29 | entitytainer_add_entity( entitytainer, 3 ); 30 | ASSERT( entitytainer_get_parent( entitytainer, 3 ) == 0 ); 31 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 0 ); 32 | 33 | int num_children; 34 | int capacity; 35 | TheEntitytainerEntity* children; 36 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 37 | ASSERT( *children == 0 ); 38 | ASSERT( num_children == 0 ); 39 | 40 | entitytainer_add_child( entitytainer, 3, 4 ); 41 | entitytainer_add_child( entitytainer, 3, 4 ); 42 | ASSERT( entitytainer_get_parent( entitytainer, 4 ) == 3 ); 43 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 1 ); 44 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 4 ) == 0 ); 45 | 46 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 47 | ASSERT( *children == 4 ); 48 | ASSERT( num_children == 1 ); 49 | 50 | entitytainer_add_child( entitytainer, 3, 6 ); 51 | ASSERT( entitytainer_get_parent( entitytainer, 6 ) == 3 ); 52 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 2 ); 53 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 4 ) == 0 ); 54 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 6 ) == 1 ); 55 | 56 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 57 | ASSERT( children[0] == 4 ); 58 | ASSERT( children[1] == 6 ); 59 | ASSERT( num_children == 2 ); 60 | 61 | entitytainer_remove_entity( entitytainer, 4 ); 62 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 1 ); 63 | ASSERT( entitytainer_get_parent( entitytainer, 4 ) == 0 ); 64 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 6 ) == 0 ); 65 | 66 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 67 | ASSERT( children[0] == 6 ); 68 | ASSERT( num_children == 1 ); 69 | 70 | for ( TheEntitytainerEntity i_child = 0; i_child < 4; ++i_child ) { 71 | entitytainer_add_child( entitytainer, 3, i_child + 10 ); 72 | } 73 | 74 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 75 | ASSERT( children[0] == 6 ); 76 | ASSERT( children[1] == 10 ); 77 | ASSERT( children[4] == 13 ); 78 | ASSERT( num_children == 5 ); 79 | 80 | entitytainer_remove_child_no_holes( entitytainer, 3, 6 ); 81 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 4 ); 82 | 83 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 84 | ASSERT( children[0] == 10 ); 85 | ASSERT( children[3] == 13 ); 86 | ASSERT( num_children == 4 ); 87 | 88 | entitytainer_remove_child_no_holes( entitytainer, 3, 10 ); 89 | ASSERT( entitytainer_num_children( entitytainer, 3 ) == 3 ); 90 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 11 ) == 0 ); 91 | ASSERT( entitytainer_get_child_index( entitytainer, 3, 13 ) == 2 ); 92 | 93 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 94 | ASSERT( children[0] == 11 ); 95 | ASSERT( children[2] == 13 ); 96 | ASSERT( num_children == 3 ); 97 | 98 | while ( num_children > 0 ) { 99 | entitytainer_remove_entity( entitytainer, children[0] ); 100 | entitytainer_get_children( entitytainer, 3, &children, &num_children, &capacity ); 101 | } 102 | 103 | entitytainer_remove_entity( entitytainer, 3 ); 104 | } 105 | 106 | static void 107 | do_multi_parent_tests( TheEntitytainer* entitytainer ) { 108 | entitytainer_add_entity( entitytainer, 10 ); 109 | entitytainer_add_entity( entitytainer, 20 ); 110 | 111 | for ( TheEntitytainerEntity i_child = 0; i_child < 4; ++i_child ) { 112 | entitytainer_add_child( entitytainer, 10, 11 + i_child ); 113 | entitytainer_add_child( entitytainer, 20, 21 + i_child ); 114 | } 115 | 116 | ASSERT( entitytainer_get_parent( entitytainer, 11 ) == 10 ); 117 | ASSERT( entitytainer_get_parent( entitytainer, 21 ) == 20 ); 118 | ASSERT( entitytainer_num_children( entitytainer, 10 ) == 4 ); 119 | ASSERT( entitytainer_num_children( entitytainer, 20 ) == 4 ); 120 | 121 | entitytainer_remove_child_no_holes( entitytainer, 20, 21 ); 122 | ASSERT( entitytainer_get_parent( entitytainer, 11 ) == 10 ); 123 | ASSERT( entitytainer_get_parent( entitytainer, 24 ) == 20 ); 124 | ASSERT( entitytainer_num_children( entitytainer, 10 ) == 4 ); 125 | ASSERT( entitytainer_num_children( entitytainer, 20 ) == 3 ); 126 | 127 | entitytainer_add_entity( entitytainer, 30 ); 128 | for ( TheEntitytainerEntity i_child = 0; i_child < 4; ++i_child ) { 129 | entitytainer_add_child( entitytainer, 30, 31 + i_child ); 130 | } 131 | 132 | ASSERT( entitytainer_get_parent( entitytainer, 31 ) == 30 ); 133 | ASSERT( entitytainer_num_children( entitytainer, 30 ) == 4 ); 134 | 135 | int num_children; 136 | int capacity; 137 | TheEntitytainerEntity* children; 138 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 139 | while ( num_children > 0 ) { 140 | entitytainer_remove_entity( entitytainer, children[0] ); 141 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 142 | } 143 | 144 | entitytainer_remove_entity( entitytainer, 10 ); 145 | entitytainer_add_entity( entitytainer, 10 ); 146 | ASSERT( entitytainer_num_children( entitytainer, 10 ) == 0 ); 147 | 148 | entitytainer_add_entity( entitytainer, 40 ); 149 | for ( TheEntitytainerEntity i_child = 0; i_child < 15; ++i_child ) { 150 | entitytainer_add_child( entitytainer, 40, 41 + i_child ); 151 | } 152 | 153 | for ( TheEntitytainerEntity i_child = 0; i_child < 15; ++i_child ) { 154 | ASSERT( entitytainer_get_parent( entitytainer, 41 + i_child ) == 40 ); 155 | ASSERT( entitytainer_get_child_index( entitytainer, 40, 41 + i_child ) == i_child ); 156 | } 157 | 158 | for ( TheEntitytainerEntity i_child = 0; i_child < 8; ++i_child ) { 159 | entitytainer_remove_entity( entitytainer, 41 + i_child ); 160 | } 161 | 162 | entitytainer_get_children( entitytainer, 40, &children, &num_children, &capacity ); 163 | ASSERT( children[0] == 49 ); 164 | ASSERT( num_children == 7 ); 165 | 166 | entitytainer_add_entity( entitytainer, 41 ); 167 | ASSERT( entitytainer_get_parent( entitytainer, 41 ) == 0 ); 168 | ASSERT( entitytainer_num_children( entitytainer, 41 ) == 0 ); 169 | 170 | entitytainer_get_children( entitytainer, 20, &children, &num_children, &capacity ); 171 | while ( num_children > 0 ) { 172 | entitytainer_remove_entity( entitytainer, children[0] ); 173 | entitytainer_get_children( entitytainer, 20, &children, &num_children, &capacity ); 174 | } 175 | 176 | entitytainer_get_children( entitytainer, 40, &children, &num_children, &capacity ); 177 | while ( num_children > 0 ) { 178 | entitytainer_remove_entity( entitytainer, children[0] ); 179 | entitytainer_get_children( entitytainer, 40, &children, &num_children, &capacity ); 180 | } 181 | 182 | entitytainer_remove_entity( entitytainer, 10 ); 183 | entitytainer_remove_entity( entitytainer, 20 ); 184 | entitytainer_remove_entity( entitytainer, 40 ); 185 | } 186 | 187 | static void 188 | do_single_parent_hole_tests( TheEntitytainer* entitytainer ) { 189 | entitytainer_add_entity( entitytainer, 10 ); 190 | 191 | int num_children; 192 | int capacity; 193 | TheEntitytainerEntity* children; 194 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 195 | ASSERT( num_children == 0 ); 196 | ASSERT( capacity == 3 ); 197 | 198 | entitytainer_add_child( entitytainer, 10, 20 ); 199 | entitytainer_add_child( entitytainer, 10, 21 ); 200 | entitytainer_add_child( entitytainer, 10, 22 ); 201 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 202 | ASSERT( num_children == 3 ); 203 | ASSERT( capacity == 3 ); 204 | 205 | entitytainer_add_child( entitytainer, 10, 23 ); 206 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 207 | ASSERT( num_children == 4 ); 208 | ASSERT( capacity == 7 ); 209 | 210 | entitytainer_remove_child_with_holes( entitytainer, 10, 20 ); 211 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 212 | ASSERT( num_children == 3 ); 213 | ASSERT( capacity == 7 ); 214 | ASSERT( children[0] == ENTITYTAINER_InvalidEntity ); 215 | 216 | entitytainer_add_child( entitytainer, 10, 30 ); 217 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 218 | ASSERT( num_children == 4 ); 219 | ASSERT( capacity == 7 ); 220 | ASSERT( children[0] == 30 ); 221 | 222 | entitytainer_remove_child_with_holes( entitytainer, 10, 30 ); 223 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 224 | ASSERT( num_children == 3 ); 225 | ASSERT( capacity == 7 ); 226 | ASSERT( children[0] == ENTITYTAINER_InvalidEntity ); 227 | 228 | entitytainer_remove_child_with_holes( entitytainer, 10, 21 ); 229 | entitytainer_remove_child_with_holes( entitytainer, 10, 22 ); 230 | entitytainer_remove_child_with_holes( entitytainer, 10, 23 ); 231 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 232 | ASSERT( num_children == 0 ); 233 | ASSERT( capacity == 3 ); 234 | 235 | entitytainer_add_child( entitytainer, 10, 20 ); 236 | entitytainer_add_child( entitytainer, 10, 21 ); 237 | entitytainer_add_child( entitytainer, 10, 22 ); 238 | entitytainer_add_child( entitytainer, 10, 23 ); 239 | entitytainer_remove_child_with_holes( entitytainer, 10, 23 ); 240 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 241 | ASSERT( num_children == 3 ); 242 | ASSERT( capacity == 7 ); 243 | 244 | entitytainer_remove_child_with_holes( entitytainer, 10, 22 ); 245 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 246 | ASSERT( num_children == 2 ); 247 | ASSERT( capacity == 3 ); 248 | 249 | entitytainer_remove_child_with_holes( entitytainer, 10, 20 ); 250 | entitytainer_remove_child_with_holes( entitytainer, 10, 21 ); 251 | entitytainer_get_children(entitytainer, 10, &children, &num_children, &capacity); 252 | ASSERT( num_children == 0 ); 253 | ASSERT( capacity == 3 ); 254 | 255 | entitytainer_add_child( entitytainer, 10, 20 ); 256 | entitytainer_add_child( entitytainer, 10, 21 ); 257 | entitytainer_add_child( entitytainer, 10, 22 ); 258 | entitytainer_add_child( entitytainer, 10, 23 ); 259 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 260 | ASSERT( num_children == 4 ); 261 | ASSERT( capacity == 7 ); 262 | entitytainer_remove_child_with_holes( entitytainer, 10, 21 ); 263 | entitytainer_remove_child_with_holes( entitytainer, 10, 22 ); 264 | entitytainer_remove_child_with_holes( entitytainer, 10, 23 ); 265 | } 266 | 267 | static void 268 | do_reserve_tests( TheEntitytainer* entitytainer ) { 269 | entitytainer_add_entity( entitytainer, 10 ); 270 | 271 | int num_children; 272 | int capacity; 273 | TheEntitytainerEntity* children; 274 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 275 | ASSERT( num_children == 0 ); 276 | ASSERT( capacity == 3 ); 277 | 278 | entitytainer_add_child( entitytainer, 10, 20 ); 279 | entitytainer_add_child( entitytainer, 10, 21 ); 280 | entitytainer_add_child( entitytainer, 10, 22 ); 281 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 282 | ASSERT( num_children == 3 ); 283 | ASSERT( capacity == 3 ); 284 | 285 | entitytainer_reserve( entitytainer, 10, 5 ); 286 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 287 | ASSERT( num_children == 3 ); 288 | ASSERT( capacity == 7 ); 289 | 290 | entitytainer_remove_child_with_holes( entitytainer, 10, 22 ); 291 | entitytainer_remove_child_with_holes( entitytainer, 10, 21 ); 292 | entitytainer_remove_child_with_holes( entitytainer, 10, 20 ); 293 | 294 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 295 | ASSERT( num_children == 0 ); 296 | ASSERT( capacity == 7 ); 297 | 298 | entitytainer_add_child( entitytainer, 10, 20 ); 299 | entitytainer_add_child( entitytainer, 10, 21 ); 300 | entitytainer_add_child( entitytainer, 10, 22 ); 301 | entitytainer_add_child( entitytainer, 10, 23 ); 302 | entitytainer_add_child( entitytainer, 10, 24 ); 303 | entitytainer_add_child( entitytainer, 10, 25 ); 304 | entitytainer_add_child( entitytainer, 10, 26 ); 305 | 306 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 307 | ASSERT( num_children == 7 ); 308 | ASSERT( capacity == 7 ); 309 | 310 | entitytainer_remove_child_with_holes( entitytainer, 10, 25 ); 311 | entitytainer_remove_child_with_holes( entitytainer, 10, 23 ); 312 | entitytainer_remove_child_with_holes( entitytainer, 10, 24 ); 313 | entitytainer_remove_child_with_holes( entitytainer, 10, 22 ); 314 | 315 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 316 | ASSERT( num_children == 3 ); 317 | ASSERT( capacity == 7 ); 318 | } 319 | 320 | static void 321 | do_multi_entity_tests( TheEntitytainer* entitytainer ) { 322 | entitytainer_add_entity( entitytainer, 10 ); 323 | 324 | int num_children; 325 | int capacity; 326 | TheEntitytainerEntity* children; 327 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 328 | ASSERT( num_children == 0 ); 329 | ASSERT( capacity == 3 ); 330 | 331 | entitytainer_add_child( entitytainer, 10, 20 ); 332 | entitytainer_add_child( entitytainer, 10, 21 ); 333 | entitytainer_add_child( entitytainer, 10, 22 ); 334 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 335 | ASSERT( num_children == 3 ); 336 | ASSERT( capacity == 3 ); 337 | 338 | entitytainer_add_entity( entitytainer, 30 ); 339 | entitytainer_get_children( entitytainer, 30, &children, &num_children, &capacity ); 340 | ASSERT( num_children == 0 ); 341 | ASSERT( capacity == 3 ); 342 | ASSERT( children[0] == 0 ); 343 | ASSERT( children[1] == 0 ); 344 | ASSERT( children[2] == 0 ); 345 | 346 | entitytainer_add_child( entitytainer, 30, 31 ); 347 | entitytainer_add_child( entitytainer, 30, 32 ); 348 | entitytainer_add_child( entitytainer, 30, 33 ); 349 | 350 | // This is the real test - 10's old bicked should be freed and then given to 1000 351 | entitytainer_add_child( entitytainer, 10, 23 ); 352 | entitytainer_add_child( entitytainer, 10, 24 ); 353 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 354 | ASSERT( num_children == 5 ); 355 | ASSERT( capacity == 7 ); 356 | 357 | entitytainer_add_entity( entitytainer, 40 ); 358 | entitytainer_get_children( entitytainer, 40, &children, &num_children, &capacity ); 359 | ASSERT( num_children == 0 ); 360 | ASSERT( capacity == 3 ); 361 | ASSERT( children[0] == 0 ); 362 | ASSERT( children[1] == 0 ); 363 | ASSERT( children[2] == 0 ); 364 | 365 | entitytainer_get_children( entitytainer, 30, &children, &num_children, &capacity ); 366 | ASSERT( num_children == 3 ); 367 | ASSERT( capacity == 3 ); 368 | ASSERT( children[0] == 31 ); 369 | ASSERT( children[1] == 32 ); 370 | ASSERT( children[2] == 33 ); 371 | 372 | entitytainer_remove_child_no_holes( entitytainer, 10, 23 ); 373 | entitytainer_remove_child_no_holes( entitytainer, 10, 24 ); 374 | entitytainer_get_children( entitytainer, 10, &children, &num_children, &capacity ); 375 | ASSERT( num_children == 3 ); 376 | ASSERT( capacity == 3 ); 377 | 378 | entitytainer_add_child( entitytainer, 30, 34 ); 379 | entitytainer_get_children( entitytainer, 30, &children, &num_children, &capacity ); 380 | ASSERT( num_children == 4 ); 381 | ASSERT( capacity == 7 ); 382 | ASSERT( children[0] == 31 ); 383 | ASSERT( children[1] == 32 ); 384 | ASSERT( children[2] == 33 ); 385 | ASSERT( children[3] == 34 ); 386 | ASSERT( children[4] == 0 ); 387 | ASSERT( children[5] == 0 ); 388 | ASSERT( children[6] == 0 ); 389 | } 390 | 391 | static void 392 | do_save_load_test( TheEntitytainer* entitytainer ) { 393 | int buffer_size = entitytainer_save( entitytainer, NULL, 0 ); 394 | unsigned char* buffer = malloc( buffer_size ); 395 | memset( buffer, 0, buffer_size ); 396 | entitytainer_save( entitytainer, buffer, buffer_size ); 397 | ASSERT( memcmp( entitytainer, buffer, buffer_size ) == 0 ); 398 | 399 | unsigned char* loaded_buffer = malloc( buffer_size ); 400 | memset( loaded_buffer, 0, buffer_size ); 401 | memcpy( loaded_buffer, buffer, buffer_size ); 402 | TheEntitytainer* loaded = entitytainer_load( loaded_buffer, buffer_size ); 403 | 404 | memcpy( buffer, loaded, buffer_size ); 405 | TheEntitytainer* loaded2 = entitytainer_load( buffer, buffer_size ); 406 | ASSERT( memcmp( &loaded2->config, &entitytainer->config, sizeof( loaded2->config ) ) == 0 ); 407 | // ASSERT( memcmp( entitytainer, &loaded2, buffer_size ) == 0 ); 408 | (void)loaded2; 409 | } 410 | 411 | static void 412 | do_save_load_upgrade_test( void ) { 413 | TheEntitytainer* entitytainer_1; 414 | TheEntitytainer* entitytainer_2; 415 | TheEntitytainer* entitytainer_3; 416 | 417 | { 418 | struct TheEntitytainerConfig config = { 0 }; 419 | config.num_entries = 8; 420 | config.bucket_sizes[0] = 2; 421 | config.bucket_sizes[1] = 8; 422 | config.bucket_list_sizes[0] = 2; 423 | config.bucket_list_sizes[1] = 4; 424 | config.num_bucket_lists = 2; 425 | config.remove_with_holes = false; 426 | int needed_memory_size = entitytainer_needed_size( &config ); 427 | config.memory = malloc( needed_memory_size ); 428 | config.memory_size = needed_memory_size; 429 | entitytainer_1 = entitytainer_create( &config ); 430 | 431 | entitytainer_add_entity( entitytainer_1, 1 ); 432 | entitytainer_reserve( entitytainer_1, 1, 3 ); 433 | entitytainer_add_entity( entitytainer_1, 3 ); 434 | entitytainer_add_child( entitytainer_1, 1, 2 ); 435 | entitytainer_add_child( entitytainer_1, 1, 3 ); 436 | entitytainer_add_child( entitytainer_1, 3, 4 ); 437 | } 438 | 439 | { 440 | struct TheEntitytainerConfig config = { 0 }; 441 | config.num_entries = 8; 442 | config.bucket_sizes[0] = 2; 443 | config.bucket_sizes[1] = 8; 444 | config.bucket_list_sizes[0] = 2; 445 | config.bucket_list_sizes[1] = 4; 446 | config.num_bucket_lists = 2; 447 | config.remove_with_holes = false; 448 | int needed_memory_size = entitytainer_needed_size( &config ); 449 | config.memory = malloc( needed_memory_size ); 450 | config.memory_size = needed_memory_size; 451 | entitytainer_2 = entitytainer_create( &config ); 452 | } 453 | 454 | { 455 | struct TheEntitytainerConfig config = { 0 }; 456 | config.num_entries = 64; 457 | config.bucket_sizes[0] = 4; 458 | config.bucket_sizes[1] = 16; 459 | config.bucket_list_sizes[0] = 2; 460 | config.bucket_list_sizes[1] = 8; 461 | config.num_bucket_lists = 2; 462 | config.remove_with_holes = false; 463 | int needed_memory_size = entitytainer_needed_size( &config ); 464 | config.memory = malloc( needed_memory_size ); 465 | config.memory_size = needed_memory_size; 466 | entitytainer_3 = entitytainer_create( &config ); 467 | } 468 | 469 | int buffer_size = entitytainer_save( entitytainer_1, NULL, 0 ); 470 | unsigned char* buffer = malloc( buffer_size ); 471 | memset( buffer, 0, buffer_size ); 472 | entitytainer_save( entitytainer_1, buffer, buffer_size ); 473 | 474 | TheEntitytainer* entitytainer_1b = entitytainer_load( buffer, buffer_size ); 475 | entitytainer_load_into( entitytainer_2, entitytainer_1b ); 476 | entitytainer_load_into( entitytainer_3, entitytainer_1b ); 477 | 478 | int num_children; 479 | int capacity; 480 | TheEntitytainerEntity* children; 481 | entitytainer_get_children( entitytainer_2, 1, &children, &num_children, &capacity ); 482 | ASSERT( *children == 2 ); 483 | ASSERT( num_children == 2 ); 484 | entitytainer_get_children( entitytainer_2, 3, &children, &num_children, &capacity ); 485 | ASSERT( *children == 4 ); 486 | ASSERT( num_children == 1 ); 487 | 488 | entitytainer_get_children( entitytainer_3, 1, &children, &num_children, &capacity ); 489 | ASSERT( *children == 2 ); 490 | ASSERT( num_children == 2 ); 491 | entitytainer_get_children( entitytainer_3, 3, &children, &num_children, &capacity ); 492 | ASSERT( *children == 4 ); 493 | ASSERT( num_children == 1 ); 494 | } 495 | 496 | static void 497 | unittest_run_base( UnitTestData* testdata ) { 498 | testdata->num_tests = 0; 499 | 500 | struct TheEntitytainerConfig config = { 0 }; 501 | config.num_entries = 64; 502 | config.bucket_sizes[0] = 4; 503 | config.bucket_sizes[1] = 8; 504 | config.bucket_sizes[2] = 16; 505 | config.bucket_list_sizes[0] = 4; 506 | config.bucket_list_sizes[1] = 2; 507 | config.bucket_list_sizes[2] = 2; 508 | config.num_bucket_lists = 3; 509 | config.remove_with_holes = false; 510 | int needed_memory_size = entitytainer_needed_size( &config ); 511 | config.memory = malloc( needed_memory_size ); 512 | config.memory_size = needed_memory_size; 513 | 514 | TheEntitytainer* entitytainer = entitytainer_create( &config ); 515 | 516 | printf( "\n" ); 517 | printf( "Setup errors found: %u/%u\n", testdata->error_index, testdata->num_tests ); 518 | 519 | if ( testdata->error_index > 0 ) { 520 | printf( "Errors found during setup, exiting.\n" ); 521 | goto LABEL_done; 522 | } 523 | 524 | testdata->num_tests = 0; 525 | 526 | do_single_parent_tests( entitytainer ); 527 | do_multi_parent_tests( entitytainer ); 528 | 529 | memset( config.memory, 0, config.memory_size ); 530 | config.remove_with_holes = true; 531 | entitytainer = entitytainer_create( &config ); 532 | do_single_parent_hole_tests( entitytainer ); 533 | do_save_load_test( entitytainer ); 534 | 535 | memset( config.memory, 0, config.memory_size ); 536 | config.keep_capacity_on_remove = true; 537 | entitytainer = entitytainer_create( &config ); 538 | do_reserve_tests( entitytainer ); 539 | do_save_load_test( entitytainer ); 540 | 541 | memset( config.memory, 0, config.memory_size ); 542 | config.keep_capacity_on_remove = false; 543 | config.remove_with_holes = false; 544 | entitytainer = entitytainer_create( &config ); 545 | do_multi_entity_tests( entitytainer ); 546 | do_save_load_test( entitytainer ); 547 | 548 | do_save_load_upgrade_test(); 549 | 550 | printf( "Run errors found: %u/%u\n", testdata->error_index, testdata->num_tests ); 551 | 552 | printf( "\n" ); 553 | if ( testdata->error_index == 0 ) { 554 | printf( "No errors found, YAY!\n" ); 555 | } 556 | else { 557 | printf( "U are teh sux.\n" ); 558 | } 559 | 560 | LABEL_done:; 561 | free( entitytainer ); 562 | } 563 | -------------------------------------------------------------------------------- /tests/unittest/unittest_default.c: -------------------------------------------------------------------------------- 1 | 2 | #define ENTITYTAINER_STATIC 3 | #include "unittest_base.c" 4 | 5 | void 6 | unittest_run_default( UnitTestData* testdata ) { 7 | unittest_run_base( testdata ); 8 | } 9 | -------------------------------------------------------------------------------- /tests/unittest/unittest_entity_32.c: -------------------------------------------------------------------------------- 1 | 2 | // #define ENTITYTAINER_STATIC 3 | // #define ENTITYTAINER_Entity 4 | // typedef int TheEntitytainerEntity; 5 | // #include "unittest_base.c" 6 | 7 | // void 8 | // unittest_run_entity32( UnitTestData* testdata ) { 9 | // unittest_run_base( testdata ); 10 | 11 | 12 | // } 13 | 14 | -------------------------------------------------------------------------------- /the_entitytainer.h: -------------------------------------------------------------------------------- 1 | /* clang-format on */ 2 | 3 | /* 4 | the_entitytainer.h - v0.01 - public domain - Anders Elfgren @srekel, 2019 5 | 6 | # THE ENTITYTAINER 7 | 8 | "Multimap" implementation in C, aimed at game development. 9 | 10 | Main purpose is to keep track of hierarchies of entities. This can be useful for attachments (e.g. holding a weapon in 11 | the hand) and inventory (having a piece of cheese in a bag in the backpack on the back of a character) for example. 12 | 13 | The name is a pun of entities and containers. If it wasn't obvious. 14 | 15 | See github for detailed documentation and the latest version: https://github.com/Srekel/the-entitytainer 16 | 17 | ## Usage 18 | 19 | In *ONE* source file, put: 20 | 21 | ```C 22 | #define ENTITYTAINER_IMPLEMENTATION 23 | 24 | // Define any of these if you wish to override them. 25 | // (There are more. Find them in the beginning of the code.) 26 | #define ENTITYTAINER_assert 27 | #define ENTITYTAINER_memcpy 28 | 29 | #include "the_entitytainer.h" 30 | ``` 31 | 32 | Other source files should just include the_entitytainer.h 33 | 34 | I recommend looking at the unittest.c file for an example of how to use it, but basically: 35 | 36 | 37 | ```C 38 | int max_num_entries = 1024; 39 | int bucket_sizes[] = { 4, 16, 256 }; 40 | int bucket_list_sizes[] = { 4, 2, 2 }; 41 | int needed_memory_size = entitytainer_needed_size( max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 42 | void* memory = malloc( needed_memory_size ); 43 | TheEntitytainer* entitytainer = 44 | entitytainer_create( memory, needed_memory_size, max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 45 | 46 | entitytainer_add_entity( entitytainer, 3 ); 47 | entitytainer_add_child( entitytainer, 3, 10 ); 48 | 49 | int num_children; 50 | TheEntitytainerEntity* children; 51 | entitytainer_get_children( entitytainer, 3, &children, &num_children ); 52 | ASSERT( num_children == 1 ); 53 | ASSERT( children[0] == 10 ); 54 | ``` 55 | 56 | ## Notes 57 | 58 | See the accompanying unit test projects for references on how to use it. 59 | Or the documentation on the github page. 60 | 61 | ## References and related stuff 62 | 63 | * https://github.com/nothings/stb 64 | * https://github.com/incrediblejr/ijhandlealloc 65 | 66 | ## License 67 | 68 | Public Domain / MIT. 69 | See end of file for license information. 70 | 71 | */ 72 | 73 | #ifndef INCLUDE_THE_ENTITYTAINER_H 74 | #define INCLUDE_THE_ENTITYTAINER_H 75 | 76 | #ifdef __cplusplus 77 | extern "C" { 78 | #endif 79 | 80 | #ifndef ENTITYTAINER_ENABLE_WARNINGS 81 | #ifdef _MSC_VER 82 | #pragma warning( push, 0 ) 83 | #pragma warning( disable : 4365 ) 84 | #pragma warning( \ 85 | disable : 5045 ) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified 86 | #endif 87 | 88 | #ifdef __clang__ 89 | #pragma clang diagnostic push 90 | #pragma clang diagnostic ignored "-Wold-style-cast" 91 | #pragma clang diagnostic ignored "-Wsign-conversion" 92 | #pragma clang diagnostic ignored "-Wcast-align" 93 | #pragma clang diagnostic ignored "-Wunused-function" 94 | #endif 95 | #endif // ENTITYTAINER_ENABLE_WARNINGS 96 | 97 | #ifndef ENTITYTAINER_assert 98 | // #include 99 | #define ENTITYTAINER_assert 100 | #endif 101 | 102 | #ifndef ENTITYTAINER_memcpy 103 | #include 104 | #define ENTITYTAINER_memcpy memcpy 105 | #endif 106 | 107 | #ifndef ENTITYTAINER_memset 108 | #include 109 | #define ENTITYTAINER_memset memset 110 | #endif 111 | 112 | #ifndef ENTITYTAINER_alignof 113 | #define ENTITYTAINER_alignof( type ) \ 114 | offsetof( \ 115 | struct { \ 116 | char c; \ 117 | type d; \ 118 | }, \ 119 | d ) 120 | #endif 121 | 122 | #ifndef ENTITYTAINER_Entity 123 | typedef unsigned short TheEntitytainerEntity; 124 | #define ENTITYTAINER_InvalidEntity ( (TheEntitytainerEntity)0u ) 125 | #define ENTITYTAINER_EntityFormat "%hu" 126 | #endif 127 | 128 | #ifndef ENTITYTAINER_Entry 129 | typedef unsigned short TheEntitytainerEntry; 130 | #define ENTITYTAINER_BucketMask 0x3fff 131 | #define ENTITYTAINER_BucketListBitCount 2 132 | #define ENTITYTAINER_BucketListOffset ( sizeof( TheEntitytainerEntry ) * 8 - ENTITYTAINER_BucketListBitCount ) 133 | #endif 134 | 135 | #define ENTITYTAINER_NoFreeBucket ( (TheEntitytainerEntity)-1 ) 136 | #define ENTITYTAINER_ShrinkMargin 1 137 | 138 | #if defined( ENTITYTAINER_STATIC ) 139 | #define ENTITYTAINER_API static 140 | #else 141 | #define ENTITYTAINER_API extern 142 | #endif 143 | 144 | #define ENTITYTAINER_MAX_BUCKET_LISTS 8 145 | 146 | #ifndef ENTITYTAINER_DEFENSIVE_CHECKS 147 | #define ENTITYTAINER_DEFENSIVE_CHECKS 0 148 | #endif 149 | 150 | #ifndef ENTITYTAINER_DEFENSIVE_ASSERTS 151 | #define ENTITYTAINER_DEFENSIVE_ASSERTS 0 152 | #endif 153 | 154 | struct TheEntitytainerConfig { 155 | void* memory; 156 | int memory_size; 157 | int num_entries; 158 | int bucket_sizes[ENTITYTAINER_MAX_BUCKET_LISTS]; 159 | int bucket_list_sizes[ENTITYTAINER_MAX_BUCKET_LISTS]; 160 | int num_bucket_lists; 161 | bool remove_with_holes; 162 | bool keep_capacity_on_remove; 163 | // char name[256]; 164 | }; 165 | 166 | typedef struct { 167 | TheEntitytainerEntity* bucket_data; 168 | int bucket_size; 169 | int total_buckets; 170 | int first_free_bucket; 171 | int used_buckets; 172 | } TheEntitytainerBucketList; 173 | 174 | typedef struct { 175 | struct TheEntitytainerConfig config; 176 | TheEntitytainerEntry* entry_lookup; 177 | TheEntitytainerEntity* entry_parent_lookup; 178 | TheEntitytainerBucketList* bucket_lists; 179 | int num_bucket_lists; 180 | int entry_lookup_size; 181 | bool remove_with_holes; 182 | bool keep_capacity_on_remove; 183 | } TheEntitytainer; 184 | 185 | ENTITYTAINER_API int entitytainer_needed_size( struct TheEntitytainerConfig* config ); 186 | ENTITYTAINER_API TheEntitytainer* entitytainer_create( struct TheEntitytainerConfig* config ); 187 | 188 | ENTITYTAINER_API TheEntitytainer* 189 | entitytainer_realloc( TheEntitytainer* entitytainer_old, void* memory, int memory_size, float growth ); 190 | ENTITYTAINER_API bool 191 | entitytainer_needs_realloc( TheEntitytainer* entitytainer, float percent_free, int num_free_buckets ); 192 | 193 | ENTITYTAINER_API void entitytainer_add_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ); 194 | ENTITYTAINER_API void entitytainer_remove_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ); 195 | ENTITYTAINER_API void entitytainer_reserve( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, int capacity ); 196 | 197 | ENTITYTAINER_API void 198 | entitytainer_add_child( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, TheEntitytainerEntity child ); 199 | ENTITYTAINER_API void entitytainer_add_child_at_index( TheEntitytainer* entitytainer, 200 | TheEntitytainerEntity parent, 201 | TheEntitytainerEntity child, 202 | int index ); 203 | ENTITYTAINER_API void entitytainer_remove_child_no_holes( TheEntitytainer* entitytainer, 204 | TheEntitytainerEntity parent, 205 | TheEntitytainerEntity child ); 206 | ENTITYTAINER_API void entitytainer_remove_child_with_holes( TheEntitytainer* entitytainer, 207 | TheEntitytainerEntity parent, 208 | TheEntitytainerEntity child ); 209 | 210 | ENTITYTAINER_API void entitytainer_get_children( TheEntitytainer* entitytainer, 211 | TheEntitytainerEntity parent, 212 | TheEntitytainerEntity** children, 213 | int* num_children, 214 | int* capacity ); 215 | ENTITYTAINER_API int entitytainer_num_children( TheEntitytainer* entitytainer, TheEntitytainerEntity parent ); 216 | ENTITYTAINER_API int entitytainer_get_child_index( TheEntitytainer* entitytainer, 217 | TheEntitytainerEntity parent, 218 | TheEntitytainerEntity child ); 219 | ENTITYTAINER_API TheEntitytainerEntity entitytainer_get_parent( TheEntitytainer* entitytainer, 220 | TheEntitytainerEntity child ); 221 | 222 | ENTITYTAINER_API bool entitytainer_is_added( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ); 223 | ENTITYTAINER_API void entitytainer_remove_holes( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ); 224 | 225 | ENTITYTAINER_API int entitytainer_save( TheEntitytainer* entitytainer, unsigned char* buffer, int buffer_size ); 226 | ENTITYTAINER_API TheEntitytainer* entitytainer_load( unsigned char* buffer, int buffer_size ); 227 | ENTITYTAINER_API void entitytainer_load_into( TheEntitytainer* entitytainer_dst, 228 | const TheEntitytainer* entitytainer_src ); 229 | 230 | #ifdef ENTITYTAINER_IMPLEMENTATION 231 | 232 | static void* entitytainer__ptr_to_aligned_ptr( void* ptr, int align ); 233 | static bool entitytainer__child_in_bucket( TheEntitytainerEntity* bucket, 234 | TheEntitytainerBucketList* bucket_list, 235 | TheEntitytainerEntity child ); 236 | 237 | ENTITYTAINER_API int 238 | entitytainer_needed_size( struct TheEntitytainerConfig* config ) { 239 | int size_needed = sizeof( TheEntitytainer ); 240 | size_needed += config->num_entries * sizeof( TheEntitytainerEntry ); // Lookup 241 | size_needed += config->num_entries * sizeof( TheEntitytainerEntity ); // Reverse lookup 242 | size_needed += config->num_bucket_lists * sizeof( TheEntitytainerBucketList ); // List structs 243 | 244 | // Bucket lists 245 | for ( int i = 0; i < config->num_bucket_lists; ++i ) { 246 | size_needed += config->bucket_list_sizes[i] * config->bucket_sizes[i] * sizeof( TheEntitytainerEntity ); 247 | } 248 | 249 | // Account for struct alignment, with good margins :D 250 | int things_to_align = 1 + config->num_bucket_lists; 251 | int safe_alignment = sizeof( void* ) * 16; 252 | size_needed += things_to_align * safe_alignment; 253 | 254 | return size_needed; 255 | } 256 | 257 | ENTITYTAINER_API TheEntitytainer* 258 | entitytainer_create( struct TheEntitytainerConfig* config ) { 259 | 260 | unsigned char* buffer_start = (unsigned char*)config->memory; 261 | ENTITYTAINER_memset( buffer_start, 0, config->memory_size ); 262 | unsigned char* buffer = buffer_start; 263 | buffer = (unsigned char*)entitytainer__ptr_to_aligned_ptr( buffer, (int)ENTITYTAINER_alignof( TheEntitytainer ) ); 264 | 265 | TheEntitytainer* entitytainer = (TheEntitytainer*)buffer; 266 | entitytainer->num_bucket_lists = config->num_bucket_lists; 267 | entitytainer->remove_with_holes = config->remove_with_holes; 268 | entitytainer->keep_capacity_on_remove = config->keep_capacity_on_remove; 269 | entitytainer->entry_lookup_size = config->num_entries; 270 | 271 | ENTITYTAINER_memcpy( &entitytainer->config, config, sizeof( *config ) ); 272 | // if ( entitytainer->config.name[0] == 0 ) { 273 | // const char* default_name = "entitytainer"; 274 | // ENTITYTAINER_memcpy( entitytainer->config.name, default_name, 12 ); 275 | // entitytainer->config.name[12] = 0; 276 | // } 277 | 278 | buffer += sizeof( TheEntitytainer ); 279 | entitytainer->entry_lookup = (TheEntitytainerEntry*)buffer; 280 | buffer += sizeof( TheEntitytainerEntry ) * config->num_entries; 281 | entitytainer->entry_parent_lookup = (TheEntitytainerEntity*)buffer; 282 | buffer += sizeof( TheEntitytainerEntity ) * config->num_entries; 283 | 284 | buffer = (unsigned char*)entitytainer__ptr_to_aligned_ptr( buffer, 285 | (int)ENTITYTAINER_alignof( TheEntitytainerBucketList ) ); 286 | entitytainer->bucket_lists = (TheEntitytainerBucketList*)buffer; 287 | 288 | unsigned char* bucket_list_end = buffer + sizeof( TheEntitytainerBucketList ) * config->num_bucket_lists; 289 | TheEntitytainerEntity* bucket_data_start = (TheEntitytainerEntity*)bucket_list_end; 290 | TheEntitytainerEntity* bucket_data = bucket_data_start; 291 | for ( int i = 0; i < config->num_bucket_lists; ++i ) { 292 | // Just making sure that we don't go into the bucket data area 293 | ENTITYTAINER_assert( buffer + sizeof( TheEntitytainerBucketList ) <= bucket_list_end, 294 | "Passing end of bucket area" ); 295 | 296 | // We need to do this because first_free_bucket is stored as an int. 297 | ENTITYTAINER_assert( config->bucket_sizes[i] * sizeof( TheEntitytainerEntity ) >= sizeof( int ) ); 298 | 299 | TheEntitytainerBucketList* list = (TheEntitytainerBucketList*)buffer; 300 | list->bucket_data = bucket_data; 301 | list->bucket_size = config->bucket_sizes[i]; 302 | list->total_buckets = config->bucket_list_sizes[i]; 303 | list->first_free_bucket = ENTITYTAINER_NoFreeBucket; 304 | list->used_buckets = 0; 305 | 306 | if ( i == 0 ) { 307 | // We need this in order to ensure that we can use 0 as the default "invalid" entry. 308 | list->used_buckets = 1; 309 | } 310 | 311 | buffer += sizeof( TheEntitytainerBucketList ); 312 | bucket_data += list->bucket_size * list->total_buckets; 313 | } 314 | 315 | ENTITYTAINER_assert( *bucket_data_start == 0 ); 316 | ENTITYTAINER_assert( (unsigned char*)bucket_data <= buffer_start + config->memory_size ); 317 | return entitytainer; 318 | } 319 | 320 | ENTITYTAINER_API TheEntitytainer* 321 | entitytainer_realloc( TheEntitytainer* entitytainer_old, void* memory, int memory_size, float growth ) { 322 | ENTITYTAINER_assert( false ); // Not yet implemented 323 | (void)memory_size; 324 | 325 | int num_entries = entitytainer_old->entry_lookup_size; // * growth; 326 | int size_needed = sizeof( TheEntitytainer ); 327 | size_needed += (int)( num_entries * sizeof( TheEntitytainerEntry ) ); 328 | size_needed += (int)( num_entries * sizeof( TheEntitytainerEntity ) ); 329 | size_needed += entitytainer_old->num_bucket_lists * sizeof( TheEntitytainerBucketList ); 330 | 331 | for ( int i = 0; i < entitytainer_old->num_bucket_lists; ++i ) { 332 | TheEntitytainerBucketList* bucket_list = &entitytainer_old->bucket_lists[i]; 333 | int old_bucket_size = bucket_list->total_buckets * bucket_list->bucket_size * sizeof( TheEntitytainerEntity ); 334 | size_needed += (int)( old_bucket_size * growth ); 335 | } 336 | 337 | char* buffer = (char*)memory; 338 | 339 | TheEntitytainer* entitytainer = (TheEntitytainer*)buffer; 340 | *entitytainer = *entitytainer_old; 341 | entitytainer->entry_lookup_size = num_entries; 342 | buffer += sizeof( TheEntitytainer ); 343 | entitytainer->entry_lookup = (TheEntitytainerEntry*)buffer; 344 | buffer += sizeof( TheEntitytainerEntry ) * num_entries; 345 | entitytainer->entry_parent_lookup = (TheEntitytainerEntity*)buffer; 346 | buffer += sizeof( TheEntitytainerEntity ) * num_entries; 347 | 348 | // char* bucket_data = buffer + sizeof( TheEntitytainerBucketList ) * entitytainer_old->num_bucket_lists; 349 | // for ( int i = 0; i < entitytainer_old->num_bucket_lists; ++i ) { 350 | // // ENTITYTAINER_assert( bucket_data - buffer > bucket_sizes[i] * bucket_list_sizes[i] ); // >= ? 351 | // TheEntitytainerBucketList* list = (TheEntitytainerBucketList*)buffer; 352 | // list->bucket_data = bucket_data; 353 | // list->bucket_size = entitytainer_old->bucket_lists[i].bucket_size; 354 | // list->total_buckets = entitytainer_old->bucket_lists[i].total_buckets; 355 | // list->first_free_bucket = entitytainer_old->bucket_lists[i].first_free_bucket; 356 | // list->used_buckets = entitytainer_old->bucket_lists[i].used_buckets; 357 | 358 | // int old_buffer_size = entitytainer_old->bucket_lists[i].total_buckets * sizeof( TheEntitytainerEntry ); 359 | // ENTITYTAINER_memcpy( list->bucket_data, entitytainer_old->bucket_lists[i].bucket_data, old_buffer_size ); 360 | // buffer += sizeof( TheEntitytainerBucketList ); 361 | // bucket_data += list->bucket_size * list->total_buckets; 362 | // } 363 | 364 | // ENTITYTAINER_assert( bucket_data == buffer + buffer_size ); 365 | return entitytainer; 366 | } 367 | 368 | ENTITYTAINER_API bool 369 | entitytainer_needs_realloc( TheEntitytainer* entitytainer, float percent_free, int num_free_buckets ) { 370 | for ( int i = 0; i < entitytainer->num_bucket_lists; ++i ) { 371 | // ENTITYTAINER_assert( bucket_data - buffer > bucket_sizes[i] * bucket_list_sizes[i] ); // >= ? 372 | TheEntitytainerBucketList* list = &entitytainer->bucket_lists[i]; 373 | if ( percent_free >= 0 ) { 374 | num_free_buckets = (int)( list->total_buckets * percent_free ); 375 | } 376 | 377 | if ( list->total_buckets - list->used_buckets <= num_free_buckets ) { 378 | return true; 379 | } 380 | } 381 | 382 | return false; 383 | } 384 | 385 | ENTITYTAINER_API void 386 | entitytainer_add_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 387 | ENTITYTAINER_assert( entitytainer->entry_lookup[entity] == 0, 388 | "Entitytainer[%s] Tried to add entity " ENTITYTAINER_EntityFormat " but it was already added.", 389 | "", 390 | entity ); 391 | 392 | TheEntitytainerBucketList* bucket_list = &entitytainer->bucket_lists[0]; 393 | int bucket_index = bucket_list->used_buckets; 394 | if ( bucket_list->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 395 | // There's a freed bucket available 396 | bucket_index = bucket_list->first_free_bucket; 397 | int bucket_offset = bucket_index * bucket_list->bucket_size; 398 | bucket_list->first_free_bucket = bucket_list->bucket_data[bucket_offset]; 399 | } 400 | 401 | // TODO: Move to larger bucket list if this one is full 402 | ENTITYTAINER_assert( bucket_list->used_buckets < bucket_list->total_buckets ); 403 | ++bucket_list->used_buckets; 404 | 405 | TheEntitytainerEntry* lookup = &entitytainer->entry_lookup[entity]; 406 | ENTITYTAINER_assert( *lookup == 0 ); 407 | *lookup = (TheEntitytainerEntry)bucket_index; // bucket list index is 0 408 | 409 | int bucket_offset = bucket_index * bucket_list->bucket_size; 410 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 411 | ENTITYTAINER_memset( bucket, 0, bucket_list->bucket_size * sizeof( TheEntitytainerEntity ) ); 412 | } 413 | 414 | ENTITYTAINER_API void 415 | entitytainer_remove_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 416 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[entity]; 417 | 418 | if ( entitytainer->entry_parent_lookup[entity] != ENTITYTAINER_InvalidEntity ) { 419 | if ( entitytainer->remove_with_holes ) { 420 | entitytainer_remove_child_with_holes( entitytainer, entitytainer->entry_parent_lookup[entity], entity ); 421 | } 422 | else { 423 | entitytainer_remove_child_no_holes( entitytainer, entitytainer->entry_parent_lookup[entity], entity ); 424 | } 425 | 426 | lookup = entitytainer->entry_lookup[entity]; 427 | } 428 | 429 | if ( lookup == 0 ) { 430 | // lookup is 0 for entities that don't have children (or haven't been added by _add_entity) 431 | return; 432 | } 433 | 434 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 435 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 436 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 437 | int bucket_offset = bucket_index * bucket_list->bucket_size; 438 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 439 | ENTITYTAINER_assert( bucket[0] == 0, 440 | "Entitytainer[%s] Tried to remove " ENTITYTAINER_EntityFormat 441 | " but it still had children. First child=" ENTITYTAINER_EntityFormat, 442 | "", 443 | entity, 444 | bucket[1] ); 445 | *bucket = (TheEntitytainerEntity)bucket_list->first_free_bucket; 446 | bucket_list->first_free_bucket = bucket_index; 447 | 448 | entitytainer->entry_lookup[entity] = 0; 449 | --bucket_list->used_buckets; 450 | } 451 | 452 | ENTITYTAINER_API void 453 | entitytainer_reserve( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, int capacity ) { 454 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 455 | ENTITYTAINER_assert( lookup != 0 ); 456 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 457 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 458 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 459 | int bucket_offset = bucket_index * bucket_list->bucket_size; 460 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 461 | if ( bucket_list->bucket_size > capacity ) { 462 | return; 463 | } 464 | 465 | TheEntitytainerBucketList* bucket_list_new = NULL; 466 | int bucket_list_index_new = -1; 467 | for ( int i_bl = 0; i_bl < entitytainer->num_bucket_lists; ++i_bl ) { 468 | bucket_list_new = &entitytainer->bucket_lists[i_bl]; 469 | if ( bucket_list_new->bucket_size > capacity + 1 ) { 470 | bucket_list_index_new = i_bl; 471 | break; 472 | } 473 | } 474 | 475 | ENTITYTAINER_assert( bucket_list_index_new != -1 ); 476 | 477 | int bucket_index_new = bucket_list_new->used_buckets; 478 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 479 | // There's a freed bucket available 480 | bucket_index_new = bucket_list_new->first_free_bucket; 481 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 482 | bucket_list_new->first_free_bucket = bucket_list_new->bucket_data[bucket_offset_new]; 483 | } 484 | 485 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 486 | TheEntitytainerEntity* bucket_new = bucket_list_new->bucket_data + bucket_offset_new; 487 | ENTITYTAINER_memset( bucket_new, 0, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 488 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list->bucket_size * sizeof( TheEntitytainerEntity ) ); 489 | 490 | *bucket = (TheEntitytainerEntity)bucket_list->first_free_bucket; 491 | bucket_list->first_free_bucket = bucket_index; 492 | bucket = bucket_new; 493 | 494 | bucket_list_new->used_buckets++; 495 | bucket_list->used_buckets--; 496 | 497 | // Update lookup 498 | TheEntitytainerEntry lookup_new = 499 | ( TheEntitytainerEntry )( bucket_list_index_new << ENTITYTAINER_BucketListOffset ); 500 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 501 | entitytainer->entry_lookup[parent] = lookup_new; 502 | } 503 | 504 | ENTITYTAINER_API void 505 | entitytainer_add_child( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, TheEntitytainerEntity child ) { 506 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 507 | ENTITYTAINER_assert( lookup != 0, 508 | "Entitytainer[%s] Tried to add " ENTITYTAINER_EntityFormat 509 | " as child to " ENTITYTAINER_EntityFormat " who was not added.", 510 | "", 511 | child, 512 | parent ); 513 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 514 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 515 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 516 | int bucket_offset = bucket_index * bucket_list->bucket_size; 517 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 518 | 519 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 520 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ), 521 | "Entitytainer[%s] Tried to add " ENTITYTAINER_EntityFormat 522 | " as child to " ENTITYTAINER_EntityFormat " but it was already its child.", 523 | "", 524 | child, 525 | parent ); 526 | #endif 527 | 528 | #if ENTITYTAINER_DEFENSIVE_CHECKS 529 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 530 | ENTITYTAINER_assert( entitytainer_get_parent( entitytainer, child ) == parent ); 531 | return; 532 | } 533 | #endif 534 | 535 | if ( bucket[0] + 1 == bucket_list->bucket_size ) { 536 | ENTITYTAINER_assert( bucket_list_index != 3 ); 537 | TheEntitytainerBucketList* bucket_list_new = bucket_list + 1; 538 | int bucket_index_new = bucket_list_new->used_buckets; 539 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 540 | // There's a freed bucket available 541 | bucket_index_new = bucket_list_new->first_free_bucket; 542 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 543 | bucket_list_new->first_free_bucket = bucket_list_new->bucket_data[bucket_offset_new]; 544 | } 545 | 546 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 547 | TheEntitytainerEntity* bucket_new = bucket_list_new->bucket_data + bucket_offset_new; 548 | ENTITYTAINER_memset( bucket_new, 0, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 549 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list->bucket_size * sizeof( TheEntitytainerEntity ) ); 550 | 551 | *bucket = (TheEntitytainerEntity)bucket_list->first_free_bucket; 552 | bucket_list->first_free_bucket = bucket_index; 553 | bucket = bucket_new; 554 | 555 | bucket_list_new->used_buckets++; 556 | bucket_list->used_buckets--; 557 | 558 | // Update lookup 559 | int bucket_list_index_new = ( bucket_list_index + 1 ) << ENTITYTAINER_BucketListOffset; 560 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 561 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 562 | entitytainer->entry_lookup[parent] = lookup_new; 563 | } 564 | 565 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 566 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 567 | #endif 568 | 569 | // Update count and insert child into bucket 570 | TheEntitytainerEntity count = bucket[0] + (TheEntitytainerEntity)1; 571 | bucket[0] = count; 572 | if ( entitytainer->remove_with_holes ) { 573 | int i = 1; 574 | for ( ; i < count; ++i ) { 575 | if ( bucket[i] == ENTITYTAINER_InvalidEntity ) { 576 | bucket[i] = child; 577 | break; 578 | } 579 | } 580 | if ( i == count ) { 581 | // Didn't find a "holed" slot, add child to the end. 582 | bucket[i] = child; 583 | } 584 | } 585 | else { 586 | bucket[count] = child; 587 | } 588 | 589 | ENTITYTAINER_assert( entitytainer->entry_parent_lookup[child] == ENTITYTAINER_InvalidEntity ); 590 | ENTITYTAINER_assert( entitytainer->entry_parent_lookup[child] == ENTITYTAINER_InvalidEntity, 591 | "Entitytainer[%s] Tried to add " ENTITYTAINER_EntityFormat 592 | " as child to " ENTITYTAINER_EntityFormat 593 | " but it was already parented to " ENTITYTAINER_EntityFormat, 594 | "", 595 | child, 596 | parent, 597 | entitytainer->entry_parent_lookup[child] ); 598 | entitytainer->entry_parent_lookup[child] = parent; 599 | } 600 | 601 | ENTITYTAINER_API void 602 | entitytainer_add_child_at_index( TheEntitytainer* entitytainer, 603 | TheEntitytainerEntity parent, 604 | TheEntitytainerEntity child, 605 | int index ) { 606 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 607 | ENTITYTAINER_assert( lookup != 0 ); 608 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 609 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 610 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 611 | int bucket_offset = bucket_index * bucket_list->bucket_size; 612 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 613 | 614 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 615 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 616 | #endif 617 | 618 | #if ENTITYTAINER_DEFENSIVE_CHECKS 619 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 620 | ENTITYTAINER_assert( entitytainer_get_parent( entitytainer, child ) == parent ); 621 | return; 622 | } 623 | #endif 624 | 625 | while ( index + 1 >= bucket_list->bucket_size ) { 626 | ENTITYTAINER_assert( bucket_list_index != 3 ); // No bucket lists with buckets of this size 627 | TheEntitytainerBucketList* bucket_list_new = bucket_list + 1; 628 | int bucket_index_new = bucket_list_new->used_buckets; 629 | if ( index + 1 >= bucket_list_new->bucket_size ) { 630 | bucket_list_index += 1; 631 | bucket_list = bucket_list_new; 632 | continue; 633 | } 634 | 635 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 636 | // There's a freed bucket available 637 | bucket_index_new = bucket_list_new->first_free_bucket; 638 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 639 | bucket_list_new->first_free_bucket = bucket_list_new->bucket_data[bucket_offset_new]; 640 | } 641 | 642 | ENTITYTAINER_assert( bucket_index_new < bucket_list_new->total_buckets ); // No free buckets at all 643 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 644 | TheEntitytainerEntity* bucket_new = bucket_list_new->bucket_data + bucket_offset_new; 645 | ENTITYTAINER_memset( bucket_new, 0, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 646 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list->bucket_size * sizeof( TheEntitytainerEntity ) ); 647 | 648 | *bucket = (TheEntitytainerEntity)bucket_list->first_free_bucket; 649 | bucket_list->first_free_bucket = bucket_index; 650 | 651 | bucket_list_new->used_buckets++; 652 | bucket_list->used_buckets--; 653 | 654 | bucket = bucket_new; 655 | bucket_list = bucket_list_new; 656 | bucket_index = bucket_index_new; 657 | bucket_list_index += 1; 658 | 659 | // Update lookup 660 | int bucket_list_index_new = ( bucket_list_index ) << ENTITYTAINER_BucketListOffset; 661 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 662 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 663 | entitytainer->entry_lookup[parent] = lookup_new; 664 | } 665 | 666 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 667 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 668 | #endif 669 | 670 | // Update count and insert child into bucket 671 | ENTITYTAINER_assert( bucket[index + 1] == ENTITYTAINER_InvalidEntity ); 672 | TheEntitytainerEntity count = bucket[0] + (TheEntitytainerEntity)1; 673 | bucket[0] = count; 674 | bucket[index + 1] = child; 675 | 676 | ENTITYTAINER_assert( entitytainer->entry_parent_lookup[child] == ENTITYTAINER_InvalidEntity, 677 | "Entitytainer[%s] Tried to add " ENTITYTAINER_EntityFormat 678 | " as child to " ENTITYTAINER_EntityFormat 679 | " but it was already parented to " ENTITYTAINER_EntityFormat, 680 | "", 681 | child, 682 | parent, 683 | entitytainer->entry_parent_lookup[child] ); 684 | entitytainer->entry_parent_lookup[child] = parent; 685 | } 686 | 687 | ENTITYTAINER_API void 688 | entitytainer_remove_child_no_holes( TheEntitytainer* entitytainer, 689 | TheEntitytainerEntity parent, 690 | TheEntitytainerEntity child ) { 691 | ASSERT( !entitytainer->config.remove_with_holes ); 692 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 693 | ENTITYTAINER_assert( lookup != 0 ); 694 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 695 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 696 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 697 | int bucket_offset = bucket_index * bucket_list->bucket_size; 698 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 699 | 700 | // Remove child from bucket, move children after forward one step. 701 | int num_children = bucket[0]; 702 | TheEntitytainerEntity* child_to_move = &bucket[1]; 703 | int count = 0; 704 | while ( *child_to_move != child && count < num_children ) { 705 | ++count; 706 | ++child_to_move; 707 | } 708 | 709 | ENTITYTAINER_assert( count < num_children ); 710 | 711 | for ( ; count < num_children - 1; ++count ) { 712 | *child_to_move = *( child_to_move + 1 ); 713 | ++child_to_move; 714 | } 715 | 716 | // Lower child count, clear entry 717 | bucket[0]--; 718 | entitytainer->entry_parent_lookup[child] = 0; 719 | 720 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 721 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 722 | #endif 723 | 724 | #if ENTITYTAINER_DEFENSIVE_CHECKS 725 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 726 | entitytainer_remove_child_no_holes( entitytainer, parent, child ); 727 | } 728 | #endif 729 | 730 | if ( entitytainer->keep_capacity_on_remove ) { 731 | return; 732 | } 733 | 734 | TheEntitytainerBucketList* bucket_list_prev = 735 | bucket_list_index > 0 ? ( entitytainer->bucket_lists + bucket_list_index - 1 ) : NULL; 736 | if ( bucket_list_prev != NULL && bucket[0] + 1 == bucket_list_prev->bucket_size ) { 737 | TheEntitytainerBucketList* bucket_list_new = bucket_list_prev; 738 | int bucket_index_new = bucket_list_new->used_buckets; 739 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 740 | // There's a freed bucket available 741 | bucket_index_new = bucket_list_new->first_free_bucket; 742 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 743 | bucket_list_new->first_free_bucket = bucket_list_new->bucket_data[bucket_offset_new]; 744 | } 745 | 746 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 747 | TheEntitytainerEntity* bucket_new = bucket_list_new->bucket_data + bucket_offset_new; 748 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 749 | 750 | bucket_list_new->used_buckets++; 751 | bucket_list->used_buckets--; 752 | 753 | // Update lookup 754 | int bucket_list_index_new = ( bucket_list_index - 1 ) << ENTITYTAINER_BucketListOffset; 755 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 756 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 757 | entitytainer->entry_lookup[parent] = lookup_new; 758 | 759 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 760 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 761 | #endif 762 | 763 | #if ENTITYTAINER_DEFENSIVE_CHECKS 764 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 765 | entitytainer_remove_child_no_holes( entitytainer, parent, child ); 766 | } 767 | #endif 768 | } 769 | } 770 | 771 | ENTITYTAINER_API void 772 | entitytainer_remove_child_with_holes( TheEntitytainer* entitytainer, 773 | TheEntitytainerEntity parent, 774 | TheEntitytainerEntity child ) { 775 | ENTITYTAINER_assert( entitytainer->remove_with_holes ); 776 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 777 | ENTITYTAINER_assert( lookup != 0 ); 778 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 779 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 780 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 781 | int bucket_offset = bucket_index * bucket_list->bucket_size; 782 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 783 | 784 | // Remove child from bucket, move children after forward one step. 785 | int capacity = bucket_list->bucket_size; 786 | int last_child_index = 0; 787 | int child_to_move_index = 0; 788 | for ( int i = 1; i < capacity; i++ ) { 789 | if ( bucket[i] == child ) { 790 | child_to_move_index = i; 791 | } 792 | else if ( bucket[i] != ENTITYTAINER_InvalidEntity ) { 793 | last_child_index = i; 794 | } 795 | } 796 | 797 | ENTITYTAINER_assert( child_to_move_index != 0 ); 798 | bucket[child_to_move_index] = ENTITYTAINER_InvalidEntity; 799 | 800 | // Lower child count, clear entry 801 | bucket[0]--; 802 | entitytainer->entry_parent_lookup[child] = 0; 803 | 804 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 805 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ), 806 | "Entitytainer[%s] Removed child " ENTITYTAINER_EntityFormat 807 | " from parent " ENTITYTAINER_EntityFormat ", but it was still its child.", 808 | "", 809 | child, 810 | parent ); 811 | #endif 812 | 813 | #if ENTITYTAINER_DEFENSIVE_CHECKS 814 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 815 | entitytainer_remove_child_with_holes( entitytainer, parent, child ); 816 | } 817 | #endif 818 | 819 | if ( entitytainer->keep_capacity_on_remove ) { 820 | return; 821 | } 822 | 823 | TheEntitytainerBucketList* bucket_list_prev = 824 | bucket_list_index > 0 ? ( entitytainer->bucket_lists + bucket_list_index - 1 ) : NULL; 825 | if ( bucket_list_prev != NULL && last_child_index + ENTITYTAINER_ShrinkMargin < bucket_list_prev->bucket_size ) { 826 | // We've shrunk enough to fit in the previous bucket, move. 827 | TheEntitytainerBucketList* bucket_list_new = bucket_list_prev; 828 | int bucket_index_new = bucket_list_new->used_buckets; 829 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 830 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 831 | // There's a freed bucket available 832 | bucket_index_new = bucket_list_new->first_free_bucket; 833 | bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 834 | bucket_list_new->first_free_bucket = bucket_list_new->bucket_data[bucket_offset_new]; 835 | } 836 | 837 | TheEntitytainerEntity* bucket_new = bucket_list_new->bucket_data + bucket_offset_new; 838 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 839 | 840 | bucket_list_new->used_buckets++; 841 | bucket_list->used_buckets--; 842 | 843 | // Update lookup 844 | int bucket_list_index_new = ( bucket_list_index - 1 ) << ENTITYTAINER_BucketListOffset; 845 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 846 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 847 | entitytainer->entry_lookup[parent] = lookup_new; 848 | 849 | #if ENTITYTAINER_DEFENSIVE_ASSERTS 850 | ENTITYTAINER_assert( !entitytainer__child_in_bucket( bucket, bucket_list, child ) ); 851 | #endif 852 | 853 | #if ENTITYTAINER_DEFENSIVE_CHECKS 854 | if ( entitytainer__child_in_bucket( bucket, bucket_list, child ) ) { 855 | entitytainer_remove_child_with_holes( entitytainer, parent, child ); 856 | } 857 | #endif 858 | } 859 | } 860 | 861 | ENTITYTAINER_API void 862 | entitytainer_get_children( TheEntitytainer* entitytainer, 863 | TheEntitytainerEntity parent, 864 | TheEntitytainerEntity** children, 865 | int* num_children, 866 | int* capacity ) { 867 | 868 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 869 | ENTITYTAINER_assert( lookup != 0 ); 870 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 871 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 872 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 873 | int bucket_offset = bucket_index * bucket_list->bucket_size; 874 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 875 | *num_children = (int)bucket[0]; 876 | *children = bucket + 1; 877 | *capacity = bucket_list->bucket_size - 1; 878 | } 879 | 880 | ENTITYTAINER_API int 881 | entitytainer_num_children( TheEntitytainer* entitytainer, TheEntitytainerEntity parent ) { 882 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 883 | ENTITYTAINER_assert( lookup != 0 ); 884 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 885 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 886 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 887 | int bucket_offset = bucket_index * bucket_list->bucket_size; 888 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 889 | return (int)bucket[0]; 890 | } 891 | 892 | ENTITYTAINER_API int 893 | entitytainer_get_child_index( TheEntitytainer* entitytainer, 894 | TheEntitytainerEntity parent, 895 | TheEntitytainerEntity child ) { 896 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 897 | ENTITYTAINER_assert( lookup != 0 ); 898 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 899 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 900 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 901 | int bucket_offset = bucket_index * bucket_list->bucket_size; 902 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 903 | int num_children = (int)bucket[0]; 904 | for ( int i = 0; i < num_children; ++i ) { 905 | if ( bucket[1 + i] == child ) { 906 | return i; 907 | } 908 | } 909 | 910 | return -1; 911 | } 912 | 913 | ENTITYTAINER_API TheEntitytainerEntity 914 | entitytainer_get_parent( TheEntitytainer* entitytainer, TheEntitytainerEntity child ) { 915 | TheEntitytainerEntity parent = entitytainer->entry_parent_lookup[child]; 916 | return parent; 917 | } 918 | 919 | ENTITYTAINER_API bool 920 | entitytainer_is_added( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 921 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[entity]; 922 | return lookup != 0; 923 | } 924 | 925 | ENTITYTAINER_API void 926 | entitytainer_remove_holes( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 927 | // TODO 928 | ENTITYTAINER_assert( false ); 929 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[entity]; 930 | ENTITYTAINER_assert( lookup != 0 ); 931 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 932 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 933 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 934 | int bucket_offset = bucket_index * bucket_list->bucket_size; 935 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->bucket_data + bucket_offset ); 936 | int first_free_index = 1; 937 | for ( int i = 1; i < bucket_list->bucket_size; ++i ) { 938 | TheEntitytainerEntity child = bucket[i]; 939 | if ( child != ENTITYTAINER_InvalidEntity ) { 940 | for ( int i_free = first_free_index; i_free < i; ++i_free ) { 941 | if ( bucket[i_free] == ENTITYTAINER_InvalidEntity ) { 942 | first_free_index = i_free + 1; 943 | bucket[i_free] = child; 944 | bucket[i] = ENTITYTAINER_InvalidEntity; 945 | break; 946 | } 947 | } 948 | } 949 | } 950 | } 951 | 952 | ENTITYTAINER_API int 953 | entitytainer_save( TheEntitytainer* entitytainer, unsigned char* buffer, int buffer_size ) { 954 | 955 | TheEntitytainerBucketList* last = &entitytainer->bucket_lists[entitytainer->num_bucket_lists - 1]; 956 | TheEntitytainerEntity* entity_end = last->bucket_data + last->bucket_size * last->total_buckets; 957 | unsigned char* begin = (unsigned char*)entitytainer; 958 | unsigned char* end = (unsigned char*)entity_end; 959 | int size = (int)( end - begin ); 960 | if ( size > buffer_size ) { 961 | return size; 962 | } 963 | 964 | ENTITYTAINER_memcpy( buffer, entitytainer, size ); 965 | return size; 966 | } 967 | 968 | ENTITYTAINER_API TheEntitytainer* 969 | entitytainer_load( unsigned char* buffer, int buffer_size ) { 970 | ENTITYTAINER_assert( entitytainer__ptr_to_aligned_ptr( buffer, (int)ENTITYTAINER_alignof( TheEntitytainer ) ) == 971 | buffer ); 972 | 973 | // Fix pointers 974 | TheEntitytainer* entitytainer = (TheEntitytainer*)buffer; 975 | buffer += sizeof( TheEntitytainer ); 976 | entitytainer->entry_lookup = (TheEntitytainerEntry*)buffer; 977 | buffer += sizeof( TheEntitytainerEntry ) * entitytainer->entry_lookup_size; 978 | entitytainer->entry_parent_lookup = (TheEntitytainerEntity*)buffer; 979 | buffer += sizeof( TheEntitytainerEntity ) * entitytainer->entry_lookup_size; 980 | 981 | buffer = (unsigned char*)entitytainer__ptr_to_aligned_ptr( buffer, 982 | (int)ENTITYTAINER_alignof( TheEntitytainerBucketList ) ); 983 | entitytainer->bucket_lists = (TheEntitytainerBucketList*)buffer; 984 | 985 | unsigned char* bucket_list_end = buffer + sizeof( TheEntitytainerBucketList ) * entitytainer->num_bucket_lists; 986 | TheEntitytainerEntity* bucket_data_start = (TheEntitytainerEntity*)bucket_list_end; 987 | TheEntitytainerEntity* bucket_data = bucket_data_start; 988 | for ( int i = 0; i < entitytainer->num_bucket_lists; ++i ) { 989 | TheEntitytainerBucketList* list = (TheEntitytainerBucketList*)buffer; 990 | list->bucket_data = bucket_data; 991 | buffer += sizeof( TheEntitytainerBucketList ); 992 | bucket_data += list->bucket_size * list->total_buckets; 993 | } 994 | 995 | (void)buffer_size; 996 | ENTITYTAINER_assert( (unsigned char*)bucket_data <= buffer + buffer_size ); 997 | return entitytainer; 998 | } 999 | 1000 | ENTITYTAINER_API void 1001 | entitytainer_load_into( TheEntitytainer* entitytainer_dst, const TheEntitytainer* entitytainer_src ) { 1002 | // if ( ENTITYTAINER_memcmp( &entitytainer_dst->config, &entitytainer_src->config, sizeof( TheEntitytainerConfig 1003 | // ) ) 1004 | // == 1005 | // 0 ) { 1006 | // ENTITYTAINER_memcpy( entitytainer_dst, entitytainer_src, sizeof( TheEntitytainerConfig ) ); 1007 | // return; 1008 | // } 1009 | 1010 | // Only allow grow for now 1011 | ENTITYTAINER_assert( entitytainer_src->config.num_bucket_lists == entitytainer_dst->config.num_bucket_lists ); 1012 | for ( int i_bl = 0; i_bl < entitytainer_src->config.num_bucket_lists; ++i_bl ) { 1013 | ENTITYTAINER_assert( entitytainer_src->config.bucket_sizes[i_bl] <= 1014 | entitytainer_dst->config.bucket_sizes[i_bl] ); 1015 | ENTITYTAINER_assert( entitytainer_src->config.bucket_list_sizes[i_bl] <= 1016 | entitytainer_dst->config.bucket_list_sizes[i_bl] ); 1017 | 1018 | if ( entitytainer_src->config.bucket_sizes[i_bl] == entitytainer_dst->config.bucket_sizes[i_bl] ) { 1019 | int bucket_list_size = sizeof( TheEntitytainerEntity ) * entitytainer_src->config.bucket_list_sizes[i_bl] * 1020 | entitytainer_src->config.bucket_sizes[i_bl]; 1021 | ENTITYTAINER_memcpy( entitytainer_dst->bucket_lists[i_bl].bucket_data, 1022 | entitytainer_src->bucket_lists[i_bl].bucket_data, 1023 | bucket_list_size ); 1024 | } 1025 | else { 1026 | TheEntitytainerBucketList* bucket_list_src = &entitytainer_src->bucket_lists[i_bl]; 1027 | int bucket_size_src = entitytainer_src->config.bucket_sizes[i_bl]; 1028 | TheEntitytainerBucketList* bucket_list_dst = &entitytainer_dst->bucket_lists[i_bl]; 1029 | int bucket_size_dst = entitytainer_dst->config.bucket_sizes[i_bl]; 1030 | for ( int i_bucket = 0; i_bucket < entitytainer_src->config.bucket_list_sizes[i_bl]; ++i_bucket ) { 1031 | int bucket_offset_src = i_bucket * bucket_size_src; 1032 | TheEntitytainerEntity* bucket_src = bucket_list_src->bucket_data + bucket_offset_src; 1033 | int bucket_offset_dst = i_bucket * bucket_size_dst; 1034 | TheEntitytainerEntity* bucket_dst = bucket_list_dst->bucket_data + bucket_offset_dst; 1035 | ENTITYTAINER_memcpy( bucket_dst, bucket_src, bucket_size_src * sizeof( TheEntitytainerEntity ) ); 1036 | } 1037 | } 1038 | entitytainer_dst->bucket_lists[i_bl].first_free_bucket = entitytainer_src->bucket_lists[i_bl].first_free_bucket; 1039 | entitytainer_dst->bucket_lists[i_bl].used_buckets = entitytainer_src->bucket_lists[i_bl].used_buckets; 1040 | } 1041 | 1042 | ENTITYTAINER_memcpy( entitytainer_dst->entry_lookup, 1043 | entitytainer_src->entry_lookup, 1044 | sizeof( TheEntitytainerEntry ) * entitytainer_src->entry_lookup_size ); 1045 | ENTITYTAINER_memcpy( entitytainer_dst->entry_parent_lookup, 1046 | entitytainer_src->entry_parent_lookup, 1047 | sizeof( TheEntitytainerEntity ) * entitytainer_src->entry_lookup_size ); 1048 | 1049 | #if ENTITYTAINER_DEFENSIVE_CHECKS 1050 | for ( TheEntitytainerEntity entity = 0; entity < entitytainer_dst->config.num_entries; ++entity ) { 1051 | TheEntitytainerEntry lookup = entitytainer_dst->entry_lookup[entity]; 1052 | if ( lookup == 0 ) { 1053 | continue; 1054 | } 1055 | 1056 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 1057 | TheEntitytainerBucketList* bucket_list = entitytainer_dst->bucket_lists + bucket_list_index; 1058 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 1059 | int bucket_offset = bucket_index * bucket_list->bucket_size; 1060 | TheEntitytainerEntity* bucket = bucket_list->bucket_data + bucket_offset; 1061 | 1062 | TheEntitytainerEntity count = bucket[0]; 1063 | TheEntitytainerEntity found_count = 0; 1064 | for ( int i_child1 = 1; i_child1 < bucket_list->bucket_size; ++i_child1 ) { 1065 | if ( found_count == count ) { 1066 | break; 1067 | } 1068 | 1069 | if ( bucket[i_child1] == ENTITYTAINER_InvalidEntity ) { 1070 | continue; 1071 | } 1072 | 1073 | ++found_count; 1074 | for ( int i_child2 = i_child1 + 1; i_child2 < bucket_list->bucket_size; ++i_child2 ) { 1075 | if ( bucket[i_child1] == bucket[i_child2] ) { 1076 | if ( entitytainer_dst->remove_with_holes ) { 1077 | ENTITYTAINER_assert( entitytainer_dst->keep_capacity_on_remove, "untested" ); 1078 | bucket[0]--; 1079 | bucket[i_child2] = 0; 1080 | // entitytainer_remove_child_with_holes( entitytainer_dst, entity, bucket[i_child1] ); 1081 | } 1082 | else { 1083 | ENTITYTAINER_assert( false, "untested" ); 1084 | } 1085 | } 1086 | } 1087 | } 1088 | } 1089 | #endif 1090 | } 1091 | 1092 | static void* 1093 | entitytainer__ptr_to_aligned_ptr( void* ptr, int align ) { 1094 | if ( align == 0 ) { 1095 | return ptr; 1096 | } 1097 | 1098 | long long int ptr_address = (long long int)ptr; 1099 | int offset = ( ~(int)ptr_address + 1 ) & ( align - 1 ); 1100 | void* aligned_ptr = (char*)ptr + offset; 1101 | return aligned_ptr; 1102 | } 1103 | 1104 | static bool 1105 | entitytainer__child_in_bucket( TheEntitytainerEntity* bucket, 1106 | TheEntitytainerBucketList* bucket_list, 1107 | TheEntitytainerEntity child ) { 1108 | 1109 | TheEntitytainerEntity count = bucket[0]; 1110 | int found = 0; 1111 | for ( int i = 1; i < bucket_list->bucket_size; ++i ) { 1112 | if ( found == count ) { 1113 | break; 1114 | } 1115 | 1116 | if ( bucket[i] == ENTITYTAINER_InvalidEntity ) { 1117 | continue; 1118 | } 1119 | 1120 | if ( bucket[i] == child ) { 1121 | return true; 1122 | } 1123 | 1124 | ++found; 1125 | } 1126 | 1127 | return false; 1128 | } 1129 | 1130 | #endif // ENTITYTAINER_IMPLEMENTATION 1131 | 1132 | #ifdef __cplusplus 1133 | } 1134 | #endif 1135 | 1136 | #ifndef ENTITYTAINER_ENABLE_WARNINGS 1137 | #ifdef _MSC_VER 1138 | #pragma warning( pop ) 1139 | #endif 1140 | 1141 | #ifdef __clang__ 1142 | #pragma clang diagnostic pop 1143 | #endif 1144 | #endif // ENTITYTAINER_ENABLE_WARNINGS 1145 | 1146 | #endif // INCLUDE_THE_ENTITYTAINER_H 1147 | 1148 | /* 1149 | ------------------------------------------------------------------------------ 1150 | This software is available under 2 licenses -- choose whichever you prefer. 1151 | ------------------------------------------------------------------------------ 1152 | ALTERNATIVE A - MIT License 1153 | Copyright (c) 2020 Anders Elfgren 1154 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1155 | this software and associated documentation files (the "Software"), to deal in 1156 | the Software without restriction, including without limitation the rights to 1157 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1158 | of the Software, and to permit persons to whom the Software is furnished to do 1159 | so, subject to the following conditions: 1160 | The above copyright notice and this permission notice shall be included in all 1161 | copies or substantial portions of the Software. 1162 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1163 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1164 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1165 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1166 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1167 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1168 | SOFTWARE. 1169 | ------------------------------------------------------------------------------ 1170 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1171 | This is free and unencumbered software released into the public domain. 1172 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1173 | software, either in source code form or as a compiled binary, for any purpose, 1174 | commercial or non-commercial, and by any means. 1175 | In jurisdictions that recognize copyright laws, the author or authors of this 1176 | software dedicate any and all copyright interest in the software to the public 1177 | domain. We make this dedication for the benefit of the public at large and to 1178 | the detriment of our heirs and successors. We intend this dedication to be an 1179 | overt act of relinquishment in perpetuity of all present and future rights to 1180 | this software under copyright law. 1181 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1182 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1183 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1184 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1185 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1186 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1187 | ------------------------------------------------------------------------------ 1188 | */ 1189 | 1190 | /* clang-format on */ 1191 | -------------------------------------------------------------------------------- /the_entitytainer_2.h: -------------------------------------------------------------------------------- 1 | // /* clang-format off */ 2 | 3 | /* 4 | the_entitytainer_2.h - v0.01 - public domain - Anders Elfgren @srekel, 2017 5 | 6 | # THE ENTITYTAINER 7 | 8 | A single header library for managing entity hierarchies. 9 | 10 | Basically a multimap (not really) implementation in C, aimed at game development. 11 | 12 | Its main purpose is to keep track of hierarchies of entities. This can be useful for: 13 | 14 | * Attachments (e.g. holding a weapon in the hand) i 15 | * Inventory (having a piece of cheese in a bag in the backpack on the back of a character) 16 | * A workplace hierarchy, keeping track of who's the boss of who, for example. 17 | 18 | See github for latest version and documentaton: https://github.com/Srekel/the-entitytainer 19 | 20 | ## Usage 21 | 22 | In *ONE* source file, put: 23 | 24 | ```C 25 | #define ENTITYTAINER_IMPLEMENTATION 26 | 27 | // Define any of these if you wish to override them. 28 | // (There are more. Find them in the beginning of the code.) 29 | #define ENTITYTAINER_assert 30 | #define ENTITYTAINER_memcpy 31 | 32 | #include "the_entitytainer.h" 33 | ``` 34 | 35 | Other source files should just include the_entitytainer.h 36 | 37 | I recommend looking at the unittest.c file for an example of how to use it, but basically: 38 | 39 | 40 | ```C 41 | int max_num_entries = 1024; 42 | int bucket_sizes[] = { 4, 16, 256 }; 43 | int bucket_list_sizes[] = { 4, 2, 2 }; 44 | int needed_memory_size = entitytainer_needed_size( max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 45 | void* memory = malloc( needed_memory_size ); 46 | TheEntitytainer* entitytainer = 47 | entitytainer_create( memory, needed_memory_size, max_num_entries, bucket_sizes, bucket_list_sizes, 3 ); 48 | 49 | entitytainer_add_entity( entitytainer, 3 ); 50 | entitytainer_add_child( entitytainer, 3, 10 ); 51 | 52 | int num_children; 53 | TheEntitytainerEntity* children; 54 | entitytainer_get_children( entitytainer, 3, &children, &num_children ); 55 | ASSERT( num_children == 1 ); 56 | ASSERT( children[0] == 10 ); 57 | ``` 58 | 59 | ## Notes 60 | 61 | See the accompanying unit test projects for references on how to use it. 62 | Or the documentation on the github page. 63 | 64 | ## References and related stuff 65 | 66 | * https://github.com/nothings/stb 67 | * https://github.com/incrediblejr/ijhandlealloc 68 | 69 | ## License 70 | 71 | Public Domain / MIT. 72 | See end of file for license information. 73 | 74 | */ 75 | 76 | #ifndef INCLUDE_THE_ENTITYTAINER_H 77 | #define INCLUDE_THE_ENTITYTAINER_H 78 | 79 | #ifdef __cplusplus 80 | extern "C" { 81 | #endif 82 | 83 | #ifdef ENTITYTAINER_IMPLEMENTATION 84 | 85 | #ifndef ENTITYTAINER_assert 86 | #include 87 | #define ENTITYTAINER_assert assert; 88 | #endif 89 | 90 | #ifndef ENTITYTAINER_memcpy 91 | #include 92 | #define ENTITYTAINER_memcpy memcpy 93 | #endif 94 | 95 | #ifndef ENTITYTAINER_memmove 96 | #include 97 | #define ENTITYTAINER_memmove memmove 98 | #endif 99 | 100 | #ifndef ENTITYTAINER_memset 101 | #include 102 | #define ENTITYTAINER_memset memset 103 | #endif 104 | 105 | #ifndef ENTITYTAINER_Entity 106 | typedef short TheEntitytainerEntity; 107 | #endif 108 | 109 | #ifndef ENTITYTAINER_Entry 110 | typedef unsigned short TheEntitytainerEntry; 111 | #define ENTITYTAINER_BucketMask 0x3f 112 | #endif 113 | 114 | #define ENTITYTAINER_BucketListOffset ( sizeof( TheEntitytainerEntry ) * 8 - 2 ) 115 | #define ENTITYTAINER_NoFreeBucket -1 116 | 117 | #if defined( ENTITYTAINER_STATIC ) 118 | #define ENTITYTAINER_API static 119 | #else 120 | #define ENTITYTAINER_API extern 121 | #endif 122 | 123 | typedef struct TheEntitytainerBucket { 124 | TheEntitytainerEntity size; 125 | TheEntitytainerEntity entities[1]; // Will generally be > 1 126 | } TheEntitytainerBlock; 127 | 128 | typedef struct TheEntitytainerBlock { 129 | TheEntitytainerBucket buckets[1]; // Will generally be > 1 130 | } TheEntitytainerBlock; 131 | 132 | typedef struct { 133 | TheEntitytainerEntry* bucket_lookup; 134 | TheEntitytainerEntity* parent_lookup; 135 | TheEntitytainerBlock* blocks; 136 | int* block_sizes; 137 | int num_blocks; 138 | int entry_lookup_size; 139 | } TheEntitytainer; 140 | 141 | ENTITYTAINER_API void 142 | entitytainer_remove_child( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, TheEntitytainerEntity child ); 143 | 144 | ENTITYTAINER_API int 145 | entitytainer_needed_size( int num_entries, int* bucket_sizes, int* bucket_list_sizes, int num_bucket_lists ) { 146 | int size_needed = sizeof( TheEntitytainer ); 147 | size_needed += num_entries * sizeof( TheEntitytainerEntry ); // Lookup 148 | size_needed += num_entries * sizeof( TheEntitytainerEntity ); // Reverse lookup 149 | size_needed += num_bucket_lists * sizeof( TheEntitytainerBucketList ); // List structs 150 | 151 | // Buckte lists 152 | for ( int i = 0; i < num_bucket_lists; ++i ) { 153 | size_needed += bucket_list_sizes[i] * bucket_sizes[i] * sizeof( TheEntitytainerEntity ); 154 | } 155 | 156 | return size_needed; 157 | } 158 | 159 | ENTITYTAINER_API TheEntitytainer* 160 | entitytainer_create( void* memory, 161 | int memory_size, 162 | int num_entries, 163 | int* bucket_sizes, 164 | int* bucket_list_sizes, 165 | int num_bucket_lists ) { 166 | 167 | char* buffer_start = (char*)memory; 168 | char* buffer = buffer_start; 169 | ENTITYTAINER_memset( buffer, 0, memory_size ); 170 | 171 | TheEntitytainer* entitytainer = (TheEntitytainer*)buffer; 172 | buffer += sizeof( TheEntitytainer ); 173 | entitytainer->entry_lookup = (TheEntitytainerEntry*)buffer; 174 | buffer += sizeof( TheEntitytainerEntry ) * num_entries; 175 | entitytainer->entry_reverse_lookup = (TheEntitytainerEntity*)buffer; 176 | buffer += sizeof( TheEntitytainerEntity ) * num_entries; 177 | entitytainer->bucket_lists = (TheEntitytainerBucketList*)buffer; 178 | 179 | char* bucket_list_end = buffer + sizeof( TheEntitytainerBucketList ) * num_bucket_lists; 180 | TheEntitytainerEntity* bucket_data_start = (TheEntitytainerEntity*)bucket_list_end; 181 | TheEntitytainerEntity* bucket_data = bucket_data_start; 182 | for ( int i = 0; i < num_bucket_lists; ++i ) { 183 | // Just making sure that we don't go into the bucket data area 184 | ENTITYTAINER_assert( buffer + sizeof( TheEntitytainerBucketList ) <= bucket_list_end ); 185 | 186 | // We need to do this because first_free_bucket is stored as an int. 187 | ENTITYTAINER_assert( bucket_sizes[i] * sizeof( TheEntitytainerEntity ) >= sizeof( int ) ); 188 | 189 | TheEntitytainerBucketList* list = (TheEntitytainerBucketList*)buffer; 190 | list->buckets = bucket_data; 191 | list->bucket_size = bucket_sizes[i]; 192 | list->total_buckets = bucket_list_sizes[i]; 193 | list->first_free_bucket = ENTITYTAINER_NoFreeBucket; 194 | list->used_buckets = 0; 195 | 196 | if ( i == 0 ) { 197 | // We need this in order to ensure that we can use 0 as the default "invalid" entry. 198 | list->used_buckets = 1; 199 | } 200 | 201 | buffer += sizeof( TheEntitytainerBucketList ); 202 | bucket_data += list->bucket_size * list->total_buckets; 203 | } 204 | 205 | ENTITYTAINER_assert( *bucket_data_start == 0 ); 206 | ENTITYTAINER_assert( (char*)bucket_data == buffer_start + memory_size ); 207 | return entitytainer; 208 | } 209 | 210 | ENTITYTAINER_API TheEntitytainer* 211 | entitytainer_realloc( TheEntitytainer* entitytainer_old, void* memory, int memory_size, float growth ) { 212 | ENTITYTAINER_assert( false ); // Not yet implemented 213 | (void)memory_size; 214 | 215 | int num_entries = entitytainer_old->entry_lookup_size; // * growth; 216 | int size_needed = sizeof( TheEntitytainer ); 217 | size_needed += (int)( num_entries * sizeof( TheEntitytainerEntry ) ); 218 | size_needed += (int)( num_entries * sizeof( TheEntitytainerEntity ) ); 219 | size_needed += entitytainer_old->num_bucket_lists * sizeof( TheEntitytainerBucketList ); 220 | 221 | for ( int i = 0; i < entitytainer_old->num_bucket_lists; ++i ) { 222 | TheEntitytainerBucketList* bucket_list = &entitytainer_old->bucket_lists[i]; 223 | int old_bucket_size = bucket_list->total_buckets * bucket_list->bucket_size * sizeof( TheEntitytainerEntity ); 224 | size_needed += (int)( old_bucket_size * growth ); 225 | } 226 | 227 | char* buffer = (char*)memory; 228 | 229 | TheEntitytainer* entitytainer = (TheEntitytainer*)buffer; 230 | *entitytainer = *entitytainer_old; 231 | entitytainer->entry_lookup_size = num_entries; 232 | buffer += sizeof( TheEntitytainer ); 233 | entitytainer->entry_lookup = (TheEntitytainerEntry*)buffer; 234 | buffer += sizeof( TheEntitytainerEntry ) * num_entries; 235 | entitytainer->entry_reverse_lookup = (TheEntitytainerEntity*)buffer; 236 | buffer += sizeof( TheEntitytainerEntity ) * num_entries; 237 | 238 | // char* bucket_data = buffer + sizeof( TheEntitytainerBucketList ) * entitytainer_old->num_bucket_lists; 239 | // for ( int i = 0; i < entitytainer_old->num_bucket_lists; ++i ) { 240 | // // ENTITYTAINER_assert( bucket_data - buffer > bucket_sizes[i] * bucket_list_sizes[i] ); // >= ? 241 | // TheEntitytainerBucketList* list = (TheEntitytainerBucketList*)buffer; 242 | // list->buckets = bucket_data; 243 | // list->bucket_size = entitytainer_old->bucket_lists[i].bucket_size; 244 | // list->total_buckets = entitytainer_old->bucket_lists[i].total_buckets; 245 | // list->first_free_bucket = entitytainer_old->bucket_lists[i].first_free_bucket; 246 | // list->used_buckets = entitytainer_old->bucket_lists[i].used_buckets; 247 | 248 | // int old_buffer_size = entitytainer_old->bucket_lists[i].total_buckets * sizeof( TheEntitytainerEntry ); 249 | // ENTITYTAINER_memcpy( list->buckets, entitytainer_old->bucket_lists[i].buckets, old_buffer_size ); 250 | // buffer += sizeof( TheEntitytainerBucketList ); 251 | // bucket_data += list->bucket_size * list->total_buckets; 252 | // } 253 | 254 | // ENTITYTAINER_assert( bucket_data == buffer + buffer_size ); 255 | return entitytainer; 256 | } 257 | 258 | ENTITYTAINER_API bool 259 | entitytainer_needs_realloc( TheEntitytainer* entitytainer, float percent_free, int num_free_buckets ) { 260 | for ( int i = 0; i < entitytainer->num_bucket_lists; ++i ) { 261 | // ENTITYTAINER_assert( bucket_data - buffer > bucket_sizes[i] * bucket_list_sizes[i] ); // >= ? 262 | TheEntitytainerBucketList* list = &entitytainer->bucket_lists[i]; 263 | if ( percent_free >= 0 ) { 264 | num_free_buckets = (int)( list->total_buckets * percent_free ); 265 | } 266 | 267 | if ( list->total_buckets - list->used_buckets <= num_free_buckets ) { 268 | return true; 269 | } 270 | } 271 | 272 | return false; 273 | } 274 | 275 | ENTITYTAINER_API void 276 | entitytainer_add_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 277 | ENTITYTAINER_assert( entitytainer->entry_lookup[entity] == 0 ); 278 | 279 | TheEntitytainerBucketList* bucket_list = &entitytainer->bucket_lists[0]; 280 | int bucket_index = bucket_list->used_buckets; 281 | if ( bucket_list->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 282 | // There's a freed bucket available 283 | bucket_index = bucket_list->first_free_bucket; 284 | bucket_list->first_free_bucket = bucket_list->buckets[bucket_list->first_free_bucket]; 285 | } 286 | 287 | ++bucket_list->used_buckets; 288 | 289 | TheEntitytainerEntry* lookup = &entitytainer->entry_lookup[entity]; 290 | ENTITYTAINER_assert( *lookup == 0 ); 291 | *lookup = (TheEntitytainerEntry)bucket_index; // bucket list index is 0 292 | 293 | // Ensure the count is 0. 294 | bucket_list->buckets[bucket_index * bucket_list->bucket_size] = 0; 295 | } 296 | 297 | ENTITYTAINER_API void 298 | entitytainer_remove_entity( TheEntitytainer* entitytainer, TheEntitytainerEntity entity ) { 299 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[entity]; 300 | 301 | if ( entitytainer->entry_reverse_lookup[entity] != 0 ) { 302 | entitytainer_remove_child( entitytainer, entitytainer->entry_reverse_lookup[entity], entity ); 303 | lookup = entitytainer->entry_lookup[entity]; 304 | } 305 | 306 | if ( lookup == 0 ) { 307 | // lookup is 0 for entities that don't have children (or haven't been added by _add_entity) 308 | return; 309 | } 310 | 311 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 312 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 313 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 314 | int bucket_offset = bucket_index * bucket_list->bucket_size; 315 | int* bucket = (int*)( bucket_list->buckets + bucket_offset ); 316 | *bucket = bucket_list->first_free_bucket; 317 | bucket_list->first_free_bucket = bucket_index; 318 | 319 | entitytainer->entry_lookup[entity] = 0; 320 | --bucket_list->used_buckets; 321 | } 322 | 323 | ENTITYTAINER_API void 324 | entitytainer_add_child( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, TheEntitytainerEntity child ) { 325 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 326 | ENTITYTAINER_assert( lookup != 0 ); 327 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 328 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 329 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 330 | int bucket_offset = bucket_index * bucket_list->bucket_size; 331 | TheEntitytainerEntity* bucket = bucket_list->buckets + bucket_offset; 332 | if ( bucket[0] + 1 == bucket_list->bucket_size ) { 333 | ASSERT( bucket_list_index != 3 ); 334 | TheEntitytainerBucketList* bucket_list_new = bucket_list + 1; 335 | int bucket_index_new = bucket_list_new->used_buckets; 336 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 337 | // There's a freed bucket available 338 | bucket_index_new = bucket_list_new->first_free_bucket; 339 | bucket_list_new->first_free_bucket = bucket_list_new->buckets[bucket_list_new->first_free_bucket]; 340 | } 341 | 342 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 343 | TheEntitytainerEntity* bucket_new = bucket_list_new->buckets + bucket_offset_new; 344 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list->bucket_size * sizeof( TheEntitytainerEntity ) ); 345 | 346 | bucket_list_new->used_buckets++; 347 | bucket_list->used_buckets--; 348 | 349 | bucket = bucket_new; 350 | 351 | // Update lookup 352 | int bucket_list_index_new = ( bucket_list_index + 1 ) << ENTITYTAINER_BucketListOffset; 353 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 354 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 355 | entitytainer->entry_lookup[parent] = lookup_new; 356 | } 357 | 358 | // Update count and insert child into bucket 359 | bucket[0]++; 360 | bucket[bucket[0]] = child; 361 | 362 | entitytainer->entry_reverse_lookup[child] = parent; 363 | } 364 | 365 | ENTITYTAINER_API void 366 | entitytainer_remove_child( TheEntitytainer* entitytainer, TheEntitytainerEntity parent, TheEntitytainerEntity child ) { 367 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 368 | ENTITYTAINER_assert( lookup != 0 ); 369 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 370 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 371 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 372 | int bucket_offset = bucket_index * bucket_list->bucket_size; 373 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->buckets + bucket_offset ); 374 | 375 | // Remove child from bucket, move children after forward one step. 376 | int num_children = bucket[0]; 377 | TheEntitytainerEntity* child_temp = &bucket[1]; 378 | int i = 0; 379 | while ( *child_temp != child && i < num_children ) { 380 | ++i; 381 | ++child; 382 | } 383 | 384 | ASSERT( i < num_children ); 385 | int bytes_to_move = ( num_children - i - 1 ) * sizeof( TheEntitytainerEntity ); 386 | ENTITYTAINER_memmove( child_temp, child_temp + 1, bytes_to_move ); 387 | 388 | // Lower count 389 | bucket[0]--; 390 | 391 | TheEntitytainerBucketList* bucket_list_prev = 392 | bucket_list_index > 0 ? ( entitytainer->bucket_lists + bucket_list_index - 1 ) : NULL; 393 | if ( bucket_list_prev != NULL && bucket[0] + 1 == bucket_list_prev->bucket_size ) { 394 | TheEntitytainerBucketList* bucket_list_new = bucket_list_prev; 395 | int bucket_index_new = bucket_list_new->used_buckets; 396 | if ( bucket_list_new->first_free_bucket != ENTITYTAINER_NoFreeBucket ) { 397 | // There's a freed bucket available 398 | bucket_index_new = bucket_list_new->first_free_bucket; 399 | bucket_list_new->first_free_bucket = bucket_list_new->buckets[bucket_list_new->first_free_bucket]; 400 | } 401 | 402 | int bucket_offset_new = bucket_index_new * bucket_list_new->bucket_size; 403 | TheEntitytainerEntity* bucket_new = bucket_list_new->buckets + bucket_offset_new; 404 | ENTITYTAINER_memcpy( bucket_new, bucket, bucket_list_new->bucket_size * sizeof( TheEntitytainerEntity ) ); 405 | 406 | bucket_list_new->used_buckets++; 407 | bucket_list->used_buckets--; 408 | 409 | // Update lookup 410 | int bucket_list_index_new = ( bucket_list_index - 1 ) << ENTITYTAINER_BucketListOffset; 411 | TheEntitytainerEntry lookup_new = (TheEntitytainerEntry)bucket_list_index_new; 412 | lookup_new = lookup_new | (TheEntitytainerEntry)bucket_index_new; 413 | entitytainer->entry_lookup[parent] = lookup_new; 414 | } 415 | 416 | entitytainer->entry_reverse_lookup[child] = 0; 417 | } 418 | 419 | ENTITYTAINER_API void 420 | entitytainer_get_children( TheEntitytainer* entitytainer, 421 | TheEntitytainerEntity parent, 422 | TheEntitytainerEntity** children, 423 | int* num_children ) { 424 | 425 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 426 | ENTITYTAINER_assert( lookup != 0 ); 427 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 428 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 429 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 430 | int bucket_offset = bucket_index * bucket_list->bucket_size; 431 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->buckets + bucket_offset ); 432 | *num_children = (int)bucket[0]; 433 | *children = bucket + 1; 434 | } 435 | 436 | ENTITYTAINER_API int 437 | entitytainer_num_children( TheEntitytainer* entitytainer, TheEntitytainerEntity parent ) { 438 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 439 | ENTITYTAINER_assert( lookup != 0 ); 440 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 441 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 442 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 443 | int bucket_offset = bucket_index * bucket_list->bucket_size; 444 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->buckets + bucket_offset ); 445 | return (int)bucket[0]; 446 | } 447 | 448 | ENTITYTAINER_API int 449 | entitytainer_get_child_index( TheEntitytainer* entitytainer, 450 | TheEntitytainerEntity parent, 451 | TheEntitytainerEntity child ) { 452 | TheEntitytainerEntry lookup = entitytainer->entry_lookup[parent]; 453 | ENTITYTAINER_assert( lookup != 0 ); 454 | int bucket_list_index = lookup >> ENTITYTAINER_BucketListOffset; 455 | TheEntitytainerBucketList* bucket_list = entitytainer->bucket_lists + bucket_list_index; 456 | int bucket_index = lookup & ENTITYTAINER_BucketMask; 457 | int bucket_offset = bucket_index * bucket_list->bucket_size; 458 | TheEntitytainerEntity* bucket = (TheEntitytainerEntity*)( bucket_list->buckets + bucket_offset ); 459 | int num_children = (int)bucket[0]; 460 | for ( int i = 0; i < num_children; ++i ) { 461 | if ( bucket[1 + i] == child ) { 462 | return i; 463 | } 464 | } 465 | 466 | return -1; 467 | } 468 | 469 | ENTITYTAINER_API TheEntitytainerEntity 470 | entitytainer_get_parent( TheEntitytainer* entitytainer, TheEntitytainerEntity child ) { 471 | TheEntitytainerEntity parent = entitytainer->entry_reverse_lookup[child]; 472 | return parent; 473 | } 474 | 475 | #ifdef __cplusplus 476 | } 477 | #endif 478 | 479 | #endif // ENTITYTAINER_IMPLEMENTATION 480 | #endif // INCLUDE_THE_ENTITYTAINER_H 481 | 482 | /* 483 | ------------------------------------------------------------------------------ 484 | This software is available under 2 licenses -- choose whichever you prefer. 485 | ------------------------------------------------------------------------------ 486 | ALTERNATIVE A - MIT License 487 | Copyright (c) 2017 Anders Elfgren 488 | Permission is hereby granted, free of charge, to any person obtaining a copy of 489 | this software and associated documentation files (the "Software"), to deal in 490 | the Software without restriction, including without limitation the rights to 491 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 492 | of the Software, and to permit persons to whom the Software is furnished to do 493 | so, subject to the following conditions: 494 | The above copyright notice and this permission notice shall be included in all 495 | copies or substantial portions of the Software. 496 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 497 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 498 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 499 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 500 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 501 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 502 | SOFTWARE. 503 | ------------------------------------------------------------------------------ 504 | ALTERNATIVE B - Public Domain (www.unlicense.org) 505 | This is free and unencumbered software released into the public domain. 506 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 507 | software, either in source code form or as a compiled binary, for any purpose, 508 | commercial or non-commercial, and by any means. 509 | In jurisdictions that recognize copyright laws, the author or authors of this 510 | software dedicate any and all copyright interest in the software to the public 511 | domain. We make this dedication for the benefit of the public at large and to 512 | the detriment of our heirs and successors. We intend this dedication to be an 513 | overt act of relinquishment in perpetuity of all present and future rights to 514 | this software under copyright law. 515 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 516 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 517 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 518 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 519 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 520 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 521 | ------------------------------------------------------------------------------ 522 | */ 523 | 524 | /* clang-format on */ 525 | --------------------------------------------------------------------------------