rewrite2
Alessandro Mauri 4 months ago
parent 4aefe8b42d
commit 94837ed410
  1. 41
      .clang-format
  2. 2
      .gitignore
  3. 108
      ARCHITECTURE.md
  4. 14
      Makefile
  5. 219
      cache.c
  6. 5
      compile_flags.txt
  7. 0
      stuff/generic_hash.h
  8. 0
      stuff/generic_stack.h
  9. 0
      stuff/main.c
  10. 348
      stuff/vectree.h
  11. 61
      ugui.c
  12. 63
      ugui.h
  13. BIN
      vectree
  14. 335
      vectree.c

@ -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

2
.gitignore vendored

@ -1,6 +1,8 @@
microgui
ugui
*.o
test/test
**/compile_commands.json
**/.cache
test
raylib/*

@ -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
}
```

@ -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

@ -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 <stdlib.h>
#include <string.h>
#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);
}

@ -0,0 +1,5 @@
-Iraylib/src
-Wall
-Wextra
-pedantic
-std=c11

@ -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

@ -0,0 +1,61 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#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;
}

@ -0,0 +1,63 @@
#ifndef _UGUI_H
#define _UGUI_H
#include <stdint.h>
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

Binary file not shown.

@ -0,0 +1,335 @@
#include <stdlib.h>
#include <string.h>
#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;
}
Loading…
Cancel
Save