From 94837ed410a93e3f5f55a192a7bd0d8c6dd6f032 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 29 Dec 2023 12:44:53 +0100 Subject: [PATCH] things --- .clang-format | 41 +++ .gitignore | 2 + ARCHITECTURE.md | 108 +++++++ Makefile | 14 + cache.c | 219 ++++++++++++++ compile_flags.txt | 5 + generic_hash.h => stuff/generic_hash.h | 0 generic_stack.h => stuff/generic_stack.h | 0 main.c => stuff/main.c | 0 stuff/vectree.h | 348 +++++++++++++++++++++++ ugui.c | 61 ++++ ugui.h | 63 ++++ vectree | Bin 20752 -> 0 bytes vectree.c | 335 ++++++++++++++++++++++ 14 files changed, 1196 insertions(+) create mode 100644 .clang-format create mode 100644 ARCHITECTURE.md create mode 100644 Makefile create mode 100644 cache.c create mode 100644 compile_flags.txt rename generic_hash.h => stuff/generic_hash.h (100%) rename generic_stack.h => stuff/generic_stack.h (100%) rename main.c => stuff/main.c (100%) create mode 100644 stuff/vectree.h create mode 100644 ugui.c create mode 100644 ugui.h delete mode 100755 vectree create mode 100644 vectree.c 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 154e744d2bdb3ed04eed55376bd986de24ec5328..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20752 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 +#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; +}