diff --git a/lib/ugui.c3l/src/core.c3 b/lib/ugui.c3l/src/core.c3 index 6e45928..f21af75 100644 --- a/lib/ugui.c3l/src/core.c3 +++ b/lib/ugui.c3l/src/core.c3 @@ -186,7 +186,7 @@ fn PElemTuple? Ctx.get_elem(&ctx, Id id, ElemType type) elem.type = type; } elem.z_index = parent.z_index; - elem.tree_idx = ctx.tree.add(ctx.active_div, id); + elem.tree_idx = ctx.tree.add(ctx.active_div, id)!; return {elem, parent}; } diff --git a/lib/ugui.c3l/src/mtree.c3 b/lib/ugui.c3l/src/mtree.c3 index db71cb9..0481048 100644 --- a/lib/ugui.c3l/src/mtree.c3 +++ b/lib/ugui.c3l/src/mtree.c3 @@ -88,6 +88,7 @@ struct MTree { } +<* @param [&inout] tree *> fn void MTree.init(&tree, usz size, Allocator allocator = mem) { // round size to the nearest multiple of BITS @@ -106,6 +107,7 @@ fn void MTree.init(&tree, usz size, Allocator allocator = mem) } +<* @param [&inout] tree *> fn void MTree.free(&tree) { tree.elements = 0; @@ -116,7 +118,8 @@ fn void MTree.free(&tree) } -fn int MTree.get_free_spot(&tree) +<* @param [&inout] tree *> +fn int? MTree.get_free_spot(&tree) { foreach (idx, d: tree.used) { if (d != $typeof(d).max) { @@ -124,7 +127,7 @@ fn int MTree.get_free_spot(&tree) return spot; } } - unreachable("no free spots left"); + return CAPACITY_EXCEEDED?; } <* @require idx >= 0 *> @@ -163,10 +166,13 @@ fn int MTree.last_node(&tree, int parent) } -<* @require tree.elements == 0 || tree.is_used(parent) == true *> -fn int MTree.add(&tree, int parent, Type t) +<* +@require tree.elements == 0 || tree.is_used(parent) == true +@param [&inout] tree +*> +fn int? MTree.add(&tree, int parent, Type t) { - int idx = tree.get_free_spot(); + int idx = tree.get_free_spot()!; int subtree = idx / BITS; tree.set_used(idx); @@ -197,11 +203,11 @@ fn int MTree.add(&tree, int parent, Type t) } // on fail we need to create another parent node if (!done) { - int new_next = tree.get_free_spot(); + int new_next = tree.get_free_spot()!; // if the new node does not land in the same subtree as the child we cannot do // anything since the references are immutable if (new_next/BITS != subtree) { - unreachable("cannot allocate new child for parent"); + return CAPACITY_EXCEEDED?; } tree.set_used(new_next); tree.elements++; @@ -220,6 +226,7 @@ fn int MTree.add(&tree, int parent, Type t) // get the index of the n-th children of parent, -1 otherwise // usage: for (int i, c; (c = tree.children_it(parent, i)) >= 0; i++) { ... } +<* @param [&in] tree *> fn int MTree.children_it(&tree, int parent, int n) { int tot_children; @@ -247,6 +254,7 @@ fn int MTree.children_it(&tree, int parent, int n) return -1; } +<* @param [&in] tree *> fn int MTree.children_num(&tree, int parent) { int n; @@ -256,6 +264,7 @@ fn int MTree.children_num(&tree, int parent) return n; } +<* @param [&in] tree *> fn int MTree.subtree_size(&tree, int parent) { int x = tree.children_num(parent); @@ -266,8 +275,7 @@ fn int MTree.subtree_size(&tree, int parent) return x; } - - +<* @param [&inout] tree *> fn int MTree.level_order_it(&tree, int parent, int i) { if (i == 0) { @@ -285,8 +293,11 @@ fn int MTree.level_order_it(&tree, int parent, int i) return p; } +<* @param [&inout] tree *> fn void MTree.prune(&tree, int parent) { + if (!tree.is_used(parent)) return; + int c; for (int i = 0; (c = tree.children_it(parent, i)) >= 0; i++) { tree.prune(c); // prune the subtree @@ -312,16 +323,24 @@ fn void MTree.prune(&tree, int parent) } -<* @require ref >= 0 , ref < tree.elem_vec.len *> +<* +@require ref >= 0 , ref < tree.elem_vec.len +@param [&inout] tree +*> fn Type? MTree.get(&tree, int ref) @operator([]) { if (tree.is_used(ref)) return tree.elem_vec[ref]; return NOT_FOUND?; } -<* @require tree.is_used(ref) *> -fn Type MTree.parentof(&tree, int ref) => tree.refs_vec[ref].parent; +<* @param [&in] tree *> +fn Type? MTree.parentof(&tree, int ref) +{ + if (!tree.is_used(ref)) return NOT_FOUND?; + return tree.refs_vec[ref].parent; +} +<* @param [&inout] tree *> fn void MTree.nuke(&tree) { foreach (idx, &b: tree.used) { @@ -331,9 +350,10 @@ fn void MTree.nuke(&tree) tree.elements = 0; } +<* @param [&in] t *> +macro bool MTree.is_root(&t, int i) => t.is_used(i) && t.refs_vec[i].parent == -1; -macro bool MTree.is_root(&t, int i) => t.refs_vec[i].parent == -1; - +<* @param [&in] tree *> fn void MTree.print(&tree) { foreach (idx, c: tree.elem_vec) { diff --git a/lib/ugui.c3l/src/vtree.c3 b/lib/ugui.c3l/src/vtree.c3 deleted file mode 100644 index fdc94eb..0000000 --- a/lib/ugui.c3l/src/vtree.c3 +++ /dev/null @@ -1,352 +0,0 @@ -module vtree::faults; -faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT; - -module vtree{ElemType}; - -import std::core::mem; -import std::core::mem::allocator; -import std::io; - -struct VTree { - Allocator allocator; - usz elements; - ElemType[] vector; // vector of element ids - isz[] refs, ordered_refs; -} - - -macro VTree.ref_is_valid(&tree, isz ref) => (ref >= 0 && ref < tree.refs.len); -macro VTree.ref_is_present(&tree, isz ref) => tree.refs[ref] >= 0; -macro VTree.size(&tree) => tree.refs.len; - -// macro to zero an element -macro @zero() -{ - $if @assignable_to(0, ElemType): - return 0; - $endif - - $if @assignable_to(null, ElemType): - return null; - $endif - - $if @assignable_to({}, ElemType): - return {}; - $endif - - //$assert true == false : ElemType.nameof +++ " is not assignable to zero or equivalent"; -} - -fn void? VTree.init(&tree, usz size, Allocator allocator) -{ - tree.allocator = allocator; - - tree.vector = allocator::new_array(tree.allocator, ElemType, size); - defer catch { (void)allocator::free(tree.allocator, tree.vector); } - - tree.refs = allocator::new_array(tree.allocator, isz, size); - defer catch { (void)allocator::free(tree.allocator, tree.refs); } - - tree.ordered_refs = allocator::new_array(tree.allocator, isz, size); - defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); } - - // set all refs to -1, meaning invalid (free) element - tree.refs[..] = -1; - - tree.elements = 0; -} - -fn void VTree.free(&tree) -{ - (void)allocator::free(tree.allocator, tree.vector); - (void)allocator::free(tree.allocator, tree.refs); - (void)allocator::free(tree.allocator, tree.ordered_refs); -} - -fn void VTree.pack(&tree) -{ - // TODO: add a PACKED flag to skip this - - isz free_spot = -1; - for (usz 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) { - isz old_ref = i; - - // move the item - tree.vector[free_spot] = tree.vector[i]; - tree.refs[free_spot] = tree.refs[i]; - - tree.vector[i] = {}; - tree.refs[i] = -1; - - // and move all references - for (usz j = 0; j < tree.size(); j++) { - if (tree.refs[j] == old_ref) { - tree.refs[j] = free_spot; - } - } - - // mark the free spot as used - free_spot = -1; - } - } -} - -fn void? VTree.resize(&tree, usz newsize) -{ - // return error when shrinking with too many elements - if (newsize < tree.elements) { - return vtree::faults::CANNOT_SHRINK?; - } - - // pack the vector when shrinking to avoid data loss - if ((int)newsize < tree.size()) { - // FIXME: packing destroys all references to elements of vec - // so shrinking may cause dangling pointers - return vtree::faults::CANNOT_SHRINK?; - } - - usz old_size = tree.size(); - - tree.vector = ((ElemType*)allocator::realloc(tree.allocator, tree.vector, newsize*ElemType.sizeof))[:newsize]; - defer catch { (void)allocator::free(tree.allocator, tree.vector); } - - tree.refs = ((isz*)allocator::realloc(tree.allocator, tree.refs, newsize*isz.sizeof))[:newsize]; - defer catch { (void)allocator::free(tree.allocator, tree.refs); } - - tree.ordered_refs = ((isz*)allocator::realloc(tree.allocator, tree.ordered_refs, newsize*isz.sizeof))[:newsize]; - defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); } - - if (newsize > tree.size()) { - tree.vector[old_size..newsize-1] = @zero(); - tree.refs[old_size..newsize-1] = -1; - } -} - -// add an element to the tree, return it's ref -fn isz? VTree.add(&tree, ElemType elem, isz parent) -{ - // invalid parent - if (!tree.ref_is_valid(parent)) { - return vtree::faults::INVALID_REFERENCE?; - } - - // no space left - if (tree.elements >= tree.size()) { - return vtree::faults::TREE_FULL?; - } - - // check if the parent exists - // if there are no elements in the tree the first add will set the root - if (!tree.ref_is_present(parent) && tree.elements != 0) { - return vtree::faults::REFERENCE_NOT_PRESENT?; - } - - // get the first free spot - isz free_spot = -1; - for (usz i = 0; i < tree.size(); i++) { - if (tree.refs[i] == -1) { - free_spot = i; - break; - } - } - if (free_spot < 0) { - return vtree::faults::TREE_FULL?; - } - - // 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 -fn usz? VTree.prune(&tree, isz ref) -{ - if (!tree.ref_is_valid(ref)) { - return vtree::faults::INVALID_REFERENCE?; - } - - if (!tree.ref_is_present(ref)) { - return 0; - } - - tree.vector[ref] = @zero(); - tree.refs[ref] = -1; - tree.elements--; - - usz count = 1; - for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) { - if (tree.refs[i] == ref) { - count += tree.prune(i)!; - } - } - - return count; -} - -fn usz VTree.nuke(&tree) -{ - tree.vector[0..] = @zero(); - tree.refs[0..] = -1; - usz x = tree.elements; - tree.elements = 0; - return x; -} - -// find the size of the subtree starting from ref -fn usz? VTree.subtree_size(&tree, isz ref) -{ - if (!tree.ref_is_valid(ref)) { - return vtree::faults::INVALID_REFERENCE?; - } - - if (!tree.ref_is_present(ref)) { - return 0; - } - - usz count = 1; - for (usz i = 0; i < tree.size(); i++) { - // only root has the reference to itself - if (tree.refs[i] == ref && ref != i) { - count += tree.subtree_size(i)!; - } - } - - return count; -} - -fn bool? VTree.is_root(&tree, isz node) => node == tree.parentof(node)!; - -// iterate through the first level children, use a cursor like strtok_r -fn isz? VTree.children_it(&tree, isz parent, isz *cursor) -{ - if (cursor == null) { - return vtree::faults::INVALID_ARGUMENT?; - } - - // if the cursor is out of bounds then we are done for sure - if (!tree.ref_is_valid(*cursor)) { - return vtree::faults::INVALID_REFERENCE?; - } - - // same for the parent, if it's invalid it can't have children - if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { - return vtree::faults::INVALID_REFERENCE?; - } - - // find the first child, update the cursor and return the ref - for (isz 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] - */ -fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor) -{ - if (cursor == null) { - return vtree::faults::INVALID_ARGUMENT?; - } - - isz[] 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 (*cursor == -1) { - *cursor = 0; - queue[..] = -1; - - // iterate through the queue appending found children - isz pos, off; - do { - // printf ("ref=%d\n", ref); - for (isz 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 (tree.ref_is_valid(ref)); - // This line is why tree.ordered_refs has to be size+1 - queue[off + 1] = -1; - } - - // 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 (tree.ref_is_valid(*cursor)) { - return queue[(*cursor)++]; - } - - return -1; -} - -fn isz? VTree.parentof(&tree, isz ref) -{ - if (!tree.ref_is_valid(ref)) { - return vtree::faults::INVALID_REFERENCE?; - } - - if (!tree.ref_is_present(ref)) { - return vtree::faults::REFERENCE_NOT_PRESENT?; - } - - return tree.refs[ref]; -} - -fn ElemType? VTree.get(&tree, isz ref) -{ - if (!tree.ref_is_valid(ref)) { - return vtree::faults::INVALID_REFERENCE?; - } - - if (!tree.ref_is_present(ref)) { - return vtree::faults::REFERENCE_NOT_PRESENT?; - } - - return tree.vector[ref]; -} - -fn void VTree.print(&tree) -{ - for (isz i = 0; i < tree.size(); i++) { - if (tree.refs[i] == -1) { - continue; - } - io::printf("[%d] {parent=%d, data=", i, tree.refs[i]); - io::print(tree.vector[i]); - io::printn("}"); - } -} diff --git a/lib/ugui.c3l/src/widgets/div.c3 b/lib/ugui.c3l/src/widgets/div.c3 index a8a185c..a4d78c2 100644 --- a/lib/ugui.c3l/src/widgets/div.c3 +++ b/lib/ugui.c3l/src/widgets/div.c3 @@ -156,7 +156,7 @@ fn Id? Ctx.div_end(&ctx) } // the active_div returns to the parent of the current one - ctx.active_div = ctx.tree.parentof(ctx.active_div); + ctx.active_div = ctx.tree.parentof(ctx.active_div)!; Elem* parent = ctx.get_parent()!; ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset); ctx.reset_scissor(elem.z_index)!; diff --git a/src/main.c3 b/src/main.c3 index ddc79b6..a17ed3c 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -1,5 +1,4 @@ import std::io; -import vtree; import cache; import ugui; import std::time;