diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b6d0dcb --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +# linux kernel style formatting +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: AlignWithSpaces + +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false +ColumnLimit: 85 + +InsertBraces: true +SortIncludes: Never +BinPackParameters: false +BinPackArguments: false +Cpp11BracedListStyle: true +SpaceBeforeCpp11BracedList: true +SeparateDefinitionBlocks: Always +AlignAfterOpenBracket: BlockIndent +InsertNewlineAtEOF: true + +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: false + +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true diff --git a/.gitignore b/.gitignore index 6320a1c..15fae14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ microgui +ugui *.o test/test **/compile_commands.json **/.cache test +raylib/* diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4ecdfd0 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,108 @@ +## High level overview + +Under the hood every element has an id, this id allows the library to store state +between frames. +Elements are also cached such that when the ui tree is rebuilt at the beginning of +every frame the element data structure doesn't have to be rebuilt. + +Elements are arranged in a tree, nodes are container elements that can contain other +elements, leafs are elements that cannot contain other elements. + +Every element has a size and a position, containers also have to keep track of their +layout information and some other state. + +Elements can push commands into the draw stack, which is a structure that contains +all the draw commands that the user application has to perform do display the ui +correctly, such commands include drawing lines, rectangles, sprites, text, etc. + + +```text + +-----------+ + | ug_init() | + +-----+-----+ + | + | + | + +---------v----------+ + |ug_input_keyboard() | + |ug_input_mouse() <----+ + |ug_input_clipboard()| | + | ... | | + +---------+----------+ | + | | + | | + +-------v--------+ | + |ug_frame_begin()| | + +-------+--------+ | + | | + | | + +---------v----------+ | + |ug_window_start() | | + +---->ug_container_start()| | + | |ug_div_start() | | + | | ... | | + | +---------+----------+ | + | | | + | | | +multiple +--------v---------+ | + times |ug_layout_row() | | + | |ug_layout_column()| | + | |ug_layout_float() | | + | | ... | | + | +--------+---------+ | + | | | + | | | + | +------v------+ | + | |ug_button() | | + | |ug_text_box()| | + | |ug_slider() | | + | | ... | | + | +------+------+ | + | | | + +--------------+ | + | | + +--------v---------+ | + |ug_window_end() | | + |ug_container_end()| | + |ug_div_end() | | + | ... | | + +--------+---------+ | + | | + | | + | | + +------v-------+ | + |ug_frame_end()| | + +------+-------+ | + | | + | | + | | + +------v-------+ | + |user draws the| | + | ui +-------+ + +------+-------+ + | + |quit + | + +------v-------+ + | ug_destroy() | + +--------------+ +``` + +### Notes + +How elements determine if they have focus or not + +```C +// in begin_{container} code +calculate focus +set has_focus property + + + +// in the element code +if(PARENT_HAS_FOCUS()) { + update stuff +} else { + fast path to return +} +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8f92d90 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/src +CC = gcc +LDFLAGS = -Lraylib/src -lm + +all: ugui + +raylib/src/libraylib.a: raylib/src/Makefile + cd raylib/src; $(MAKE) PLATFORM=PLATFORM_DESKTOP + +ugui: ugui.o vectree.o raylib/src/libraylib.a + +ugui.o: ugui.c ugui.h + +vectree.o: vectree.c ugui.h diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..593bebc --- /dev/null +++ b/cache.c @@ -0,0 +1,219 @@ +// LRU cache: +/* + * The cache uses a pool (array) containing all the elements and a hash table + * associating the position in the pool with the element id + */ + +#include +#include + +#include "ugui.h" + +// To have less collisions TABLE_SIZE has to be larger than the cache size +#define CACHE_SIZE 265 +#define TABLE_SIZE (CACHE_SIZE * 1.5f) +#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3) +#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6) +#define CACHE_BRESET(b) \ + for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \ + ; +#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63) +#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63))) + +/* Hash Table Implementation ----------------------------------------------------- */ + +#define HASH_MAXSIZE 4096 + +// hash table (id -> index) +typedef struct { + uint32_t id; + uint32_t index; +} IdElem; + +typedef struct { + uint32_t items, size, exp; + IdElem bucket[]; +} IdTable; + +IdTable *table_create(uint32_t size) +{ + if (!size || size > HASH_MAXSIZE) { + return NULL; + } + /* round to the greater power of two */ + /* FIXME: check for intger overflow here */ + uint32_t exp = 32 - __builtin_clz(size - 1); + size = 1 << (exp); + /* FIXME: check for intger overflow here */ + IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size); + if (ht) { + ht->items = 0; + ht->size = size; + memset(ht->bucket, 0, sizeof(IdTable) * size); + } + return ht; +} + +void table_destroy(IdTable *ht) +{ + if (ht) { + free(ht); + } +} + +// Find and return the element by pointer +IdElem *table_search(IdTable *ht, uint32_t id) +{ + if (!ht) { + return NULL; + } + + // In this case id is the hash + uint32_t idx = id % ht->size; + + for (uint32_t x = 0, i; x < ht->size; x++) { + i = (idx + x) % ht->size; + if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) { + return &(ht->bucket[i]); + } + } + return NULL; +} + +// FIXME: this simply overrides the found item +IdElem *table_insert(IdTable *ht, IdElem entry) +{ + IdElem *r = table_search(ht, entry.id); + if (r != NULL) { + if (r->id != 0) { + ht->items++; + } + *r = entry; + } + return r; +} + +IdElem *table_remove(IdTable *ht, uint32_t id) +{ + if (!ht) { + return NULL; + } + IdElem *r = table_search(ht, id); + if (r) { + r->id = 0; + } + return r; +} + +/* Cache Implementation ---------------------------------------------------------- */ + +// Every CACHE_CYCLES operations mark not-present the unused elements +#define CACHE_CYCLE(c) \ + do { \ + if (++(c->cycles) > CACHE_NCYCLES) { \ + for (int i = 0; i < CACHE_BSIZE; i++) { \ + c->present[i] &= c->used[i]; \ + c->used[i] = 0; \ + } \ + c->cycles = 0; \ + } \ + } while (0) + +typedef struct { + IdTable *table; + UgElem *array; + uint64_t *present, *used; + int cycles; +} UgElemCache; + +/* FIXME: check for allocation errors */ +UgElemCache ug_cache_init(void) +{ + IdTable *t = table_create(TABLE_SIZE); + UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE); + uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE); + uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE); + CACHE_BRESET(p); + CACHE_BRESET(u); + return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0}; +} + +void ug_cache_free(UgElemCache *cache) +{ + if (cache) { + table_destroy(cache->table); + free(cache->array); + free(cache->present); + free(cache->used); + } +} + +const UgElem *ug_cache_search(UgElemCache *cache, uint32_t id) +{ + if (!cache) { + return NULL; + } + IdElem *r; + r = table_search(cache->table, id); + /* MISS */ + if (!r || id != r->id) { + return NULL; + } + /* MISS, the data is not valid (not present) */ + if (!CACHE_BTEST(cache->present, r->index)) { + return NULL; + } + /* HIT, set as recently used */ + CACHE_BSET(cache->used, r->index); + return (&cache->array[r->index]); +} + +/* Look for a free spot in the present bitmap and return its index */ +/* If there is no free space left then just return the first position */ +int ug_cache_get_free_spot(UgElemCache *cache) +{ + if (!cache) { + return -1; + } + int x = 0; + for (int b = 0; b < CACHE_BSIZE; b++) { + if (cache->present[b] == 0) { + x = 64; + } else { + x = __builtin_clzll(cache->present[b]); + } + x = 64 - x; + if (!CACHE_BTEST(cache->present, x + 64 * b)) { + return x + 64 * b; + } + } + return 0; +} + +const UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) +{ + if (!cache) { + return NULL; + } + UgElem *spot = NULL; + + /* Set used and present */ + CACHE_BSET(cache->present, index); + CACHE_BSET(cache->used, index); + CACHE_CYCLE(cache); + spot = &(cache->array[index]); + *spot = *g; + IdElem e = {.id = g->id, .index = index}; + if (!table_insert(cache->table, e)) { + return NULL; + } + return spot; +} + +// Insert an element in the cache +const UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, int32_t *index) +{ + *index = ug_cache_get_free_spot(cache); + return ug_cache_insert_at(cache, g, *index); +} + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..13a23a6 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,5 @@ +-Iraylib/src +-Wall +-Wextra +-pedantic +-std=c11 diff --git a/generic_hash.h b/stuff/generic_hash.h similarity index 100% rename from generic_hash.h rename to stuff/generic_hash.h diff --git a/generic_stack.h b/stuff/generic_stack.h similarity index 100% rename from generic_stack.h rename to stuff/generic_stack.h diff --git a/main.c b/stuff/main.c similarity index 100% rename from main.c rename to stuff/main.c diff --git a/stuff/vectree.h b/stuff/vectree.h new file mode 100644 index 0000000..949bee1 --- /dev/null +++ b/stuff/vectree.h @@ -0,0 +1,348 @@ +#ifndef _VECTREE_H +#define _VECTREE_H + +#ifdef VTREE_DTYPE + +typedef struct { + int size, elements; + VTREE_DTYPE *vector; + int *refs; +} Vtree; + +int vtree_init(Vtree *tree, unsigned int size); +int vtree_pack(Vtree *tree); +int vtree_resize(Vtree *tree, unsigned int newsize); +int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent); +int vtree_prune(Vtree *tree, int ref); +int vtree_subtree_size(Vtree *tree, int ref); +int vtree_children_it(Vtree *tree, int parent, int *cursor); +int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor); +int vtree_destroy(Vtree *tree); + +#ifdef VTREE_IMPL + +#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) +#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) + +int vtree_init(Vtree *tree, unsigned int size) +{ + if (tree == NULL) { + return -1; + } + + tree->vector = malloc(sizeof(VTREE_DTYPE) * size); + if (tree->vector == NULL) { + return -1; + } + + tree->refs = malloc(sizeof(int) * size); + if (tree->refs == NULL) { + free(tree->vector); + return -1; + } + + // set all refs to -1, meaning invalid (free) element + for (unsigned int i = 0; i < size; i++) { + tree->refs[i] = -1; + } + + // fill vector with zeroes + memset(tree->vector, 0, size * sizeof(VTREE_DTYPE)); + + tree->size = size; + tree->elements = 0; + + return 0; +} + +int vtree_destroy(Vtree *tree) +{ + if (tree == NULL) { + return -1; + } + + free(tree->vector); + free(tree->refs); + + return 0; +} + +int vtree_pack(Vtree *tree) +{ + if (tree == NULL) { + return -1; + } + + // TODO: add a PACKED flag to skip this + + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + continue; + } + + // find a item that can be packed + if (free_spot >= 0 && tree->refs[i] >= 0) { + int old_ref = i; + + // move the item + tree->vector[free_spot] = tree->vector[i]; + tree->refs[free_spot] = tree->refs[i]; + + tree->vector[i] = (VTREE_DTYPE){0}; + tree->refs[i] = -1; + + // and move all references + for (int x = 0; x < tree->size; x++) { + if (tree->refs[x] == old_ref) { + tree->refs[x] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } + + return 0; +} + +int vtree_resize(Vtree *tree, unsigned int newsize) +{ + if (tree == NULL) { + return -1; + } + + // return error when shrinking with too many elements + if ((int)newsize < tree->elements) { + return -1; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree->size) { + //if (vtree_pack(tree) < 0) { + // return -1; + //} + // TODO: allow shrinking, since packing destroys all references + return -1; + } + + VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE)); + if (newvec == NULL) { + return -1; + } + + int *newrefs = realloc(tree->refs, newsize * sizeof(int)); + if (newrefs == NULL) { + return -1; + } + + tree->vector = newvec; + tree->refs = newrefs; + if ((int)newsize > tree->size) { + for (int i = tree->size; i < (int)newsize; i++) { + tree->vector[i] = (VTREE_DTYPE){0}; + tree->refs[i] = -1; + } + } + + tree->size = newsize; + + return 0; +} + +// add an element to the tree, return it's ref +int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent) +{ + if (tree == NULL) { + return -1; + } + + // invalid parent + if (!IS_VALID_REF(tree, parent)) { + return -1; + } + + // no space left + if (tree->elements >= tree->size) { + return -1; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { + return -1; + } + + // get the first free spot + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return -1; + } + + // finally add the element + tree->vector[free_spot] = elem; + tree->refs[free_spot] = parent; + tree->elements++; + + return free_spot; +} + +// prune the tree starting from the ref +// returns the number of pruned elements +int vtree_prune(Vtree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + tree->vector[ref] = (VTREE_DTYPE){0}; + tree->refs[ref] = -1; + + int count = 1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + count += vtree_prune(tree, i); + } + } + + return count; +} + +// find the size of the subtree starting from ref +int vtree_subtree_size(Vtree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + int count = 1; + for (int i = 0; i < tree->size; i++) { + // only root has the reference to itself + if (tree->refs[i] == ref && ref != i) { + count += vtree_subtree_size(tree, i); + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +int vtree_children_it(Vtree *tree, int parent, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + // if the cursor is out of bounds then we are done for sure + if (!IS_VALID_REF(tree, *cursor)) { + return -1; + } + + // same for the parent, if it's invalid it can't have children + if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { + return -1; + } + + // find the first child, update the cursor and return the ref + for (int i = *cursor; i < tree->size; i++) { + if (tree->refs[i] == parent) { + *cursor = i + 1; + return i; + } + } + + // if no children are found return -1 + *cursor = -1; + return -1; +} + +/* iterates trough every leaf of the subtree in the following manner + * node [x], x: visit order + * [0] + * / | \ + * / [2] [3] + * [1] | + * / \ [6] + * [4] [5] + */ +int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor) +{ + if (tree == NULL || queue_p == NULL || cursor == NULL) { + return -1; + } + + int *queue = *queue_p; + + // TODO: this could also be done when adding or removing elements + // first call, create a ref array ordered like we desire + if (queue == NULL) { + *cursor = 0; + // create a queue of invalid refs, size is the worst case + queue = malloc(sizeof(int) * tree->size); + if (queue == NULL) { + return -1; + } + for (int i = 0; i < tree->size; i++) { + queue[i] = -1; + } + *queue_p = queue; + + // iterate through the queue appending found children + int pos = 0, off = 0; + do { + //printf ("ref=%d\n", ref); + + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + queue[pos++] = i; + } + } + + for (;ref == queue[off] && off < tree->size; off++); + ref = queue[off]; + + } while (IS_VALID_REF(tree, ref)); + } + + //PRINT_ARR(queue, tree->size); + //return -1; + + // on successive calls just iterate through the queue until we find an + // invalid ref + int ret = queue[(*cursor)++]; + if (!IS_VALID_REF(tree, ret)) { + free(queue); + } + + return ret; +} +#endif // VTREE_IMPL + +#endif // VTREE_DTYPE + +#endif + diff --git a/ugui.c b/ugui.c new file mode 100644 index 0000000..a22e092 --- /dev/null +++ b/ugui.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +#include "raylib.h" +#include "ugui.h" + +int main(void) +{ + UgCtx ctx; + ug_init(&ctx); + + InitWindow(800, 450, "Ugui Test"); + + // Main loop + while (!WindowShouldClose()) { + ug_begin_frame(&ctx); + + // drawing + BeginDrawing(); + ClearBackground(BLACK); + DrawText( + "Congrats! You created your first window!", + 190, + 200, + 20, + LIGHTGRAY + ); + EndDrawing(); + + ug_end_frame(&ctx); + } + + CloseWindow(); + + ug_destroy(&ctx); + return 0; +} + +int ug_init(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + ug_tree_init(&ctx->tree, 10); + + return 0; +} + +int ug_destroy(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + ug_tree_destroy(&ctx->tree); + + return 0; +} diff --git a/ugui.h b/ugui.h new file mode 100644 index 0000000..08dabb6 --- /dev/null +++ b/ugui.h @@ -0,0 +1,63 @@ +#ifndef _UGUI_H +#define _UGUI_H + +#include + +typedef struct { + int32_t x, y, w, h; +} UgRect; + +typedef struct { + int32_t x, y; +} UgPoint; + +typedef struct { + uint8_t r, g, b, a; +} UgColor; + +typedef enum { + ETYPE_NONE = 0, + ETYPE_BUTTON, + ETYPE_TEXT, + ETYPE_SCROLL, + ETYPE_SLIDER, +} UgElemType; + +typedef struct { + uint64_t id; + UgRect rec; + union { + uint32_t type_int; + UgElemType type; + }; +} UgElem; + +// TODO: add a packed flag +// TODO: add a fill index to skip some searching for free spots + +typedef struct { + int size, elements; + uint32_t *vector; // vector of element ids + int *refs, *ordered_refs; +} UgTree; + +typedef struct { + UgTree tree; +} UgCtx; + +// tree implementation +int ug_tree_init(UgTree *tree, unsigned int size); +int ug_tree_pack(UgTree *tree); +int ug_tree_resize(UgTree *tree, unsigned int newsize); +int ug_tree_add(UgTree *tree, uint32_t elem, int parent); +int ug_tree_prune(UgTree *tree, int ref); +int ug_tree_subtree_size(UgTree *tree, int ref); +int ug_tree_children_it(UgTree *tree, int parent, int *cursor); +int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); +int ug_tree_destroy(UgTree *tree); + +int ug_init(UgCtx *ctx); +int ug_destroy(UgCtx *ctx); + +#endif // _UGUI_H + diff --git a/vectree b/vectree deleted file mode 100755 index 154e744..0000000 Binary files a/vectree and /dev/null differ diff --git a/vectree.c b/vectree.c new file mode 100644 index 0000000..e9e80f8 --- /dev/null +++ b/vectree.c @@ -0,0 +1,335 @@ +#include +#include + +#include "ugui.h" + +#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) +#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) + +int ug_tree_init(UgTree *tree, unsigned int size) +{ + if (tree == NULL) { + return -1; + } + + tree->vector = malloc(sizeof(UgElem) * size); + if (tree->vector == NULL) { + return -1; + } + + tree->refs = malloc(sizeof(int) * size); + if (tree->refs == NULL) { + free(tree->vector); + return -1; + } + + // ordered refs are used in the iterators + tree->ordered_refs = malloc(sizeof(int) * size); + if (tree->ordered_refs == NULL) { + free(tree->vector); + free(tree->refs); + return -1; + } + + // set all refs to -1, meaning invalid (free) element + for (unsigned int i = 0; i < size; i++) { + tree->refs[i] = -1; + } + + // fill vector with zeroes + memset(tree->vector, 0, size * sizeof(UgElem)); + + tree->size = size; + tree->elements = 0; + + return 0; +} + +int ug_tree_destroy(UgTree *tree) +{ + if (tree == NULL) { + return -1; + } + + free(tree->vector); + free(tree->refs); + + return 0; +} + +int ug_tree_pack(UgTree *tree) +{ + if (tree == NULL) { + return -1; + } + + // TODO: add a PACKED flag to skip this + + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + continue; + } + + // find a item that can be packed + if (free_spot >= 0 && tree->refs[i] >= 0) { + int old_ref = i; + + // move the item + tree->vector[free_spot] = tree->vector[i]; + tree->refs[free_spot] = tree->refs[i]; + + tree->vector[i] = 0; + tree->refs[i] = -1; + + // and move all references + for (int x = 0; x < tree->size; x++) { + if (tree->refs[x] == old_ref) { + tree->refs[x] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } + + return 0; +} + +int ug_tree_resize(UgTree *tree, unsigned int newsize) +{ + if (tree == NULL) { + return -1; + } + + // return error when shrinking with too many elements + if ((int)newsize < tree->elements) { + return -1; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree->size) { + // if (ug_tree_pack(tree) < 0) { + // return -1; + // } + // TODO: allow shrinking, since packing destroys all references + return -1; + } + + uint32_t *newvec = realloc(tree->vector, newsize * sizeof(uint32_t)); + if (newvec == NULL) { + return -1; + } + + int *newrefs = realloc(tree->refs, newsize * sizeof(int)); + if (newrefs == NULL) { + return -1; + } + + int *neworrefs = realloc(tree->ordered_refs, newsize * sizeof(int)); + if (neworrefs == NULL) { + return -1; + } + + tree->vector = newvec; + tree->refs = newrefs; + tree->ordered_refs = neworrefs; + if ((int)newsize > tree->size) { + for (int i = tree->size; i < (int)newsize; i++) { + tree->vector[i] = 0; + tree->refs[i] = -1; + } + } + + tree->size = newsize; + + return 0; +} + +// add an element to the tree, return it's ref +int ug_tree_add(UgTree *tree, uint32_t elem, int parent) +{ + if (tree == NULL) { + return -1; + } + + // invalid parent + if (!IS_VALID_REF(tree, parent)) { + return -1; + } + + // no space left + if (tree->elements >= tree->size) { + return -1; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { + return -1; + } + + // get the first free spot + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return -1; + } + + // finally add the element + tree->vector[free_spot] = elem; + tree->refs[free_spot] = parent; + tree->elements++; + + return free_spot; +} + +// prune the tree starting from the ref +// returns the number of pruned elements +int ug_tree_prune(UgTree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + tree->vector[ref] = 0; + tree->refs[ref] = -1; + + int count = 1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + count += ug_tree_prune(tree, i); + } + } + + return count; +} + +// find the size of the subtree starting from ref +int ug_tree_subtree_size(UgTree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + int count = 1; + for (int i = 0; i < tree->size; i++) { + // only root has the reference to itself + if (tree->refs[i] == ref && ref != i) { + count += ug_tree_subtree_size(tree, i); + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +int ug_tree_children_it(UgTree *tree, int parent, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + // if the cursor is out of bounds then we are done for sure + if (!IS_VALID_REF(tree, *cursor)) { + return -1; + } + + // same for the parent, if it's invalid it can't have children + if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { + return -1; + } + + // find the first child, update the cursor and return the ref + for (int i = *cursor; i < tree->size; i++) { + if (tree->refs[i] == parent) { + *cursor = i + 1; + return i; + } + } + + // if no children are found return -1 + *cursor = -1; + return -1; +} + +/* iterates trough every leaf of the subtree in the following manner + * node [x], x: visit order + * [0] + * / | \ + * / [2] [3] + * [1] | + * / \ [6] + * [4] [5] + */ +int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + int *queue = tree->ordered_refs; + + // TODO: this could also be done when adding or removing elements + // first call, create a ref array ordered like we desire + if (queue == NULL) { + *cursor = 0; + for (int i = 0; i < tree->size; i++) { + queue[i] = -1; + } + + // iterate through the queue appending found children + int pos = 0, off = 0; + do { + // printf ("ref=%d\n", ref); + + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + queue[pos++] = i; + } + } + + for (; ref == queue[off] && off < tree->size; off++) + ; + ref = queue[off]; + + } while (IS_VALID_REF(tree, ref)); + } + + // PRINT_ARR(queue, tree->size); + // return -1; + + // on successive calls just iterate through the queue until we find an + // invalid ref, if the user set the cursor to -1 it means it has found what + // he needed, so free + if (*cursor < 0) { + return -1; + } else if (IS_VALID_REF(tree, *cursor)) { + return queue[(*cursor)++]; + } + + return -1; +}