From 7f8b5196a54eba148c09a0c0242f56ce979f6543 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 21 Sep 2025 18:17:39 +0200 Subject: [PATCH] new tree implementation this about halves the time spent on level_order_it and drastically reduces the time spent in children_it --- lib/ugui.c3l/src/mtree.c3 | 284 ++++++++++++++++++++++ lib/ugui.c3l/src/ugui_core.c3 | 18 +- lib/ugui.c3l/src/ugui_layout.c3 | 32 +-- lib/ugui.c3l/src/widgets/ugui_div.c3 | 2 +- test/test_mtree.c3 | 342 +++++++++++++++++++++++++++ 5 files changed, 649 insertions(+), 29 deletions(-) create mode 100644 lib/ugui.c3l/src/mtree.c3 create mode 100644 test/test_mtree.c3 diff --git a/lib/ugui.c3l/src/mtree.c3 b/lib/ugui.c3l/src/mtree.c3 new file mode 100644 index 0000000..ee4ba38 --- /dev/null +++ b/lib/ugui.c3l/src/mtree.c3 @@ -0,0 +1,284 @@ +module mtree{Type}; + +import std::core::mem; +import std::core::mem::allocator; +import std::io; +import std::bits; +import std::collections::list; + + +alias Bitmap = ulong; +const BITS = Bitmap.sizeof*8; + +alias IdxList = List{int}; + +// more: if positive it contains the index of the next node that contains the children information +struct Node { + int more; + int parent; + Bitmap children; +} + +struct MTree { + usz elements; + Allocator allocator; + IdxList queue; + Bitmap[] used; + Type[] elem_mat; // element matrix + Node[] refs_mat; // relationship matrix +} + + +fn void MTree.init(&tree, usz size, Allocator allocator = mem) +{ + // round size to the nearest multiple of BITS + size = size + size%BITS; + + tree.elements = 0; + tree.allocator = allocator; + tree.queue.init(tree.allocator, size); + tree.used = allocator::new_array(tree.allocator, Bitmap, size/BITS); + tree.elem_mat = allocator::new_array(tree.allocator, Type, size); + tree.refs_mat = allocator::new_array(tree.allocator, Node, size); + + foreach (&r: tree.refs_mat) { + r.more = -1; + } +} + + +fn void MTree.free(&tree) +{ + tree.elements = 0; + tree.queue.free(); + (void)allocator::free(tree.allocator, tree.used); + (void)allocator::free(tree.allocator, tree.elem_mat); + (void)allocator::free(tree.allocator, tree.refs_mat); +} + + +fn int MTree.get_free_spot(&tree) +{ + foreach (idx, d: tree.used) { + if (d != $typeof(d).max) { + int spot = (int)idx*BITS + BITS-(int)d.clz(); + return spot; + } + } + unreachable("no free spots left"); +} + +<* @require idx >= 0 *> +fn void MTree.set_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + tree.used[q] |= (1l << r); +} + +<* @require idx >= 0 *> +fn void MTree.unset_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + tree.used[q] &= ~(1l << r); +} + +<* @require idx >= 0 *> +fn bool MTree.is_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + return !!(tree.used[q] & (1l << r)); +} + + +// get the last node in the "more" chain +<* @require tree.is_used(parent) == true *> +fn int MTree.last_node(&tree, int parent) +{ + while(tree.refs_mat[parent].more >= 0) { + parent = tree.refs_mat[parent].more; + } + return parent; +} + + +<* @require tree.elements == 0 || tree.is_used(parent) == true *> +fn int MTree.add(&tree, int parent, Type t) +{ + int idx = tree.get_free_spot(); + int subtree = idx / BITS; + + tree.set_used(idx); + tree.elem_mat[idx] = t; + tree.refs_mat[idx] = (Node){ + .parent = parent, + .more = -1, + }; + + tree.elements++; + // root element, has no parent + if (tree.elements == 1) { + tree.refs_mat[idx].parent = -1; + return idx; + } + + + // if the parent already has a node in the same subtree as the child then update that node's + // children bitmap + bool done; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + int ps = p/BITS; + if (ps == subtree) { + tree.refs_mat[p].children |= (1l << (idx%BITS)); + done = true; + break; + } + } + // on fail we need to create another parent node + if (!done) { + int new_more = 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_more/BITS != subtree) { + unreachable("cannot allocate new child for parent"); + } + tree.set_used(new_more); + tree.elements++; + // update the "more" chain + int last_link = tree.last_node(parent); + tree.refs_mat[last_link].more = new_more; + tree.refs_mat[new_more].more = -1; + tree.refs_mat[new_more].children |= (long)(1 << (idx%BITS)); + tree.refs_mat[new_more].parent = last_link; + // FIXME: the elem_mat is not updated, do we need to? + } + + return idx; +} + + +// 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++) { ... } +fn int MTree.children_it(&tree, int parent, int n) +{ + int tot_children; + int child; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + int cn = (int)tree.refs_mat[p].children.popcount(); + tot_children += cn; + + // we are in the right subtree + if (tot_children > n) { + child = (p/BITS) * BITS; // start at the parent's subtree index + int j = cn - (tot_children - n); // we need the j-th children of this node + Bitmap u = tree.refs_mat[p].children; + + child += j; // add the children number + do { + child += (int)u.ctz(); // increment by the skipped zeroes + u >>= u.ctz() + 1; + j--; + } while (j >= 0); + + return child; + } + } + return -1; +} + +fn int MTree.children_num(&tree, int parent) +{ + int n; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + n += (int)tree.refs_mat[p].children.popcount(); + } + return n; +} + +fn int MTree.subtree_size(&tree, int parent) +{ + int x = tree.children_num(parent); + int c; + for (int n; (c = tree.children_it(parent, n)) >= 0; n++) { + x += tree.subtree_size(c); + } + return x; +} + + + +fn int MTree.level_order_it(&tree, int parent, int i) +{ + if (i == 0) { + tree.queue.clear(); + tree.queue.push(parent); + } + + if (tree.queue.len() == 0) return -1; + + int p = tree.queue.pop_first()!!; + int c; + for (int n; (c = tree.children_it(p, n)) >= 0; n++) { + tree.queue.push(c); + } + return p; +} + +fn void MTree.prune(&tree, int parent) +{ + int c; + for (int i = 0; (c = tree.children_it(parent, i)) >= 0; i++) { + tree.prune(c); // prune the subtree + + // delete all children including their more chain + for (int p = c; p >= 0;) { + int next = tree.refs_mat[p].more; + tree.unset_used(p); + tree.refs_mat[p] = {.more = -1}; + p = next; + } + + } + + // finally delete the parent + for (int p = parent; p >= 0;) { + int next = tree.refs_mat[p].more; + tree.unset_used(p); + tree.elements--; + tree.refs_mat[p] = {.more = -1}; + p = next; + } + +} + +<* @require tree.is_used(ref) *> +fn Type MTree.get(&tree, int ref) => tree.elem_mat[ref]; + +<* @require tree.is_used(ref) *> +fn Type MTree.parentof(&tree, int ref) => tree.refs_mat[ref].parent; + +fn void MTree.nuke(&tree) +{ + foreach (idx, &b: tree.used) { + *b = 0; + tree.refs_mat[idx] = {.more = -1}; + } + tree.elements = 0; +} + + +macro bool MTree.is_root(&t, int i) => t.refs_mat[i].parent == -1; + +fn void MTree.print(&tree) +{ + foreach (idx, c: tree.elem_mat) { + if (tree.is_used((int)idx)) { + io::printfn("[%d](%s) parent:%d more:%d children:%b", + idx, c, tree.refs_mat[idx].parent, tree.refs_mat[idx].more, + tree.refs_mat[idx].children + ); + } + } +} diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 04d8e06..f7d4c49 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -1,6 +1,6 @@ module ugui; -import vtree; +import mtree; import cache; import fifo; @@ -51,7 +51,7 @@ bitstruct ElemEvents : uint { // element structure struct Elem { Id id; - isz tree_idx; + int tree_idx; ElemFlags flags; ElemEvents events; Rect bounds; @@ -69,7 +69,7 @@ struct Elem { // relationships between elements are stored in a tree, it stores just the ids -alias IdTree = vtree::VTree{Id}; +alias IdTree = mtree::MTree{Id}; // elements themselves are kept in a cache const uint MAX_ELEMENTS = 256; @@ -122,13 +122,13 @@ struct Ctx { Id focus_id; Rect div_scissor; // the current div bounds used for scissor test - isz active_div; // tree node indicating the current active div + int active_div; // tree node indicating the current active div } // return a pointer to the parent of the current active div fn Elem*? Ctx.get_parent(&ctx) { - Id parent_id = ctx.tree.get(ctx.active_div)!; + Id parent_id = ctx.tree.get(ctx.active_div); Elem*? parent = ctx.cache.search(parent_id); if (catch parent) return parent; if (parent.type != ETYPE_DIV) return WRONG_ELEMENT_TYPE?; @@ -144,7 +144,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9; fn Id? Ctx.gen_id(&ctx, Id id2) { // FIXME: this is SHIT - Id id1 = ctx.tree.get(ctx.active_div)!; + Id id1 = ctx.tree.get(ctx.active_div); // Mix the two IDs non-linearly Id mixed = id1 ^ id2.rotate_left(13); mixed ^= id1.rotate_left(7); @@ -178,7 +178,7 @@ fn Elem*? Ctx.get_elem(&ctx, Id id, ElemType type) } else { elem.type = type; } - elem.tree_idx = ctx.tree.add(id, ctx.active_div)!; + elem.tree_idx = ctx.tree.add(ctx.active_div, id); return elem; } @@ -196,13 +196,13 @@ macro Elem* Ctx.find_elem(&ctx, Id id) fn Elem*? Ctx.get_active_div(&ctx) { - Id id = ctx.tree.get(ctx.active_div)!; + Id id = ctx.tree.get(ctx.active_div); return ctx.cache.search(id); } fn void? Ctx.init(&ctx, Allocator allocator) { - ctx.tree.init(MAX_ELEMENTS, allocator)!; + ctx.tree.init(MAX_ELEMENTS, allocator); defer catch { (void)ctx.tree.free(); } ctx.cache.init(allocator)!; diff --git a/lib/ugui.c3l/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 index afdf0c5..c852267 100644 --- a/lib/ugui.c3l/src/ugui_layout.c3 +++ b/lib/ugui.c3l/src/ugui_layout.c3 @@ -316,18 +316,16 @@ fn void resolve_placement(Elem* c, Elem* p) fn void Ctx.layout_element_tree(&ctx) { - isz cursor = -1; - isz current = ctx.tree.level_order_it(0, &cursor)!!; - for (; current >= 0; current = ctx.tree.level_order_it(0, &cursor)!!) { - Elem* p = ctx.find_elem(ctx.tree.get(current))!!; + int current; + for (int n; (current = ctx.tree.level_order_it(0, n)) >= 0; n++) { + Elem* p = ctx.find_elem(ctx.tree.get(current)); //if (ctx.tree.is_root(current)!!) p = &&{}; + int ch; // RESOLVE KNOWN DIMENSIONS - isz ch_cur = 0; - isz ch = ctx.tree.children_it(current, &ch_cur)!!; - for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { - Elem* c = ctx.find_elem(ctx.tree.get(ch))!!; - if (ctx.tree.is_root(ch)!!) { + for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) { + Elem* c = ctx.find_elem(ctx.tree.get(ch)); + if (ctx.tree.is_root(ch)) { resolve_dimensions(p, &&{}); } else { resolve_dimensions(c, p); @@ -335,11 +333,9 @@ fn void Ctx.layout_element_tree(&ctx) } // RESOLVE GROW CHILDREN - ch_cur = 0; - ch = ctx.tree.children_it(current, &ch_cur)!!; - for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { - Elem* c = ctx.find_elem(ctx.tree.get(ch))!!; - if (ctx.tree.is_root(ch)!!) { + for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) { + Elem* c = ctx.find_elem(ctx.tree.get(ch)); + if (ctx.tree.is_root(ch)) { resolve_grow_elements(p, &&{}); } else { resolve_grow_elements(c, p); @@ -347,11 +343,9 @@ fn void Ctx.layout_element_tree(&ctx) } // RESOLVE CHILDREN PLACEMENT - ch_cur = 0; - ch = ctx.tree.children_it(current, &ch_cur)!!; - for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { - Elem* c = ctx.find_elem(ctx.tree.get(ch))!!; - if (ctx.tree.is_root(ch)!!) { + for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) { + Elem* c = ctx.find_elem(ctx.tree.get(ch)); + if (ctx.tree.is_root(ch)) { resolve_placement(p, &&{}); update_children_bounds(p, &&{}); } else { diff --git a/lib/ugui.c3l/src/widgets/ugui_div.c3 b/lib/ugui.c3l/src/widgets/ugui_div.c3 index 611e3d7..50de082 100644 --- a/lib/ugui.c3l/src/widgets/ugui_div.c3 +++ b/lib/ugui.c3l/src/widgets/ugui_div.c3 @@ -135,7 +135,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.div.z_index)!; diff --git a/test/test_mtree.c3 b/test/test_mtree.c3 new file mode 100644 index 0000000..f5df756 --- /dev/null +++ b/test/test_mtree.c3 @@ -0,0 +1,342 @@ +module mtree{Type}; + +import std::core::mem; +import std::core::mem::allocator; +import std::io; +import std::bits; +import std::collections::list; + + +alias Bitmap = ulong; +const BITS = Bitmap.sizeof*8; + +alias IdxList = list::List{int}; + +// more: if positive it contains the index of the next node that contains the children information +struct Node { + int more; + int parent; + Bitmap children; +} + +struct MTree { + usz elements; + Allocator allocator; + IdxList queue; + Bitmap[] used; + Type[] elem_mat; // element matrix + Node[] refs_mat; // relationship matrix +} + + +fn void MTree.init(&tree, usz size, Allocator allocator = mem) +{ + // round size to the nearest multiple of BITS + size = size + size%BITS; + + tree.elements = 0; + tree.allocator = allocator; + tree.queue.init(allocator, size); + tree.used = allocator::new_array(tree.allocator, Bitmap, size/BITS); + tree.elem_mat = allocator::new_array(tree.allocator, Type, size); + tree.refs_mat = allocator::new_array(tree.allocator, Node, size); + + foreach (&r: tree.refs_mat) { + r.more = -1; + } +} + + +fn void MTree.free(&tree) +{ + tree.elements = 0; + tree.queue.free(); + (void)allocator::free(tree.allocator, tree.used); + (void)allocator::free(tree.allocator, tree.elem_mat); + (void)allocator::free(tree.allocator, tree.refs_mat); +} + + +fn int MTree.get_free_spot(&tree) +{ + foreach (idx, d: tree.used) { + if (d != $typeof(d).max) { + int spot = (int)idx*BITS + BITS-(int)d.clz(); + return spot; + } + } + unreachable("no free spots left"); +} + +<* @require idx >= 0 *> +fn void MTree.set_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + tree.used[q] |= (1l << r); +} + +<* @require idx >= 0 *> +fn void MTree.unset_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + tree.used[q] &= ~(1l << r); +} + +<* @require idx >= 0 *> +fn bool MTree.is_used(&tree, int idx) +{ + int r = idx % BITS; + int q = idx / BITS; + return !!(tree.used[q] & (1l << r)); +} + + +// get the last node in the "more" chain +<* @require tree.is_used(parent) == true *> +fn int MTree.last_node(&tree, int parent) +{ + while(tree.refs_mat[parent].more >= 0) { + parent = tree.refs_mat[parent].more; + } + return parent; +} + + +<* @require tree.elements == 0 || tree.is_used(parent) == true *> +fn int MTree.add(&tree, int parent, Type t) +{ + int idx = tree.get_free_spot(); + int subtree = idx / BITS; + + tree.set_used(idx); + tree.elem_mat[idx] = t; + tree.refs_mat[idx] = (Node){ + .parent = parent, + .more = -1, + }; + + tree.elements++; + // root element, has no parent + if (tree.elements == 1) { + tree.refs_mat[idx].parent = -1; + return idx; + } + + + // if the parent already has a node in the same subtree as the child then update that node's + // children bitmap + bool done; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + int ps = p/BITS; + if (ps == subtree) { + tree.refs_mat[p].children |= (1l << (idx%BITS)); + done = true; + break; + } + } + // on fail we need to create another parent node + if (!done) { + int new_more = 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_more/BITS != subtree) { + unreachable("cannot allocate new child for parent"); + } + tree.set_used(new_more); + tree.elements++; + // update the "more" chain + int last_link = tree.last_node(parent); + tree.refs_mat[last_link].more = new_more; + tree.refs_mat[new_more].more = -1; + tree.refs_mat[new_more].children |= (long)(1 << (idx%BITS)); + tree.refs_mat[new_more].parent = last_link; + // FIXME: the elem_mat is not updated, do we need to? + } + + return idx; +} + + +// 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++) { ... } +fn int MTree.children_it(&tree, int parent, int n) +{ + int tot_children; + int child; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + int cn = (int)tree.refs_mat[p].children.popcount(); + tot_children += cn; + + // we are in the right subtree + if (tot_children > n) { + child = (p/BITS) * BITS; // start at the parent's subtree index + int j = cn - (tot_children - n); // we need the j-th children of this node + Bitmap u = tree.refs_mat[p].children; + + child += j; // add the children number + do { + child += (int)u.ctz(); // increment by the skipped zeroes + u >>= u.ctz() + 1; + j--; + } while (j >= 0); + + return child; + } + } + return -1; +} + +fn int MTree.children_num(&tree, int parent) +{ + int n; + for (int p = parent; p >= 0; p = tree.refs_mat[p].more) { + n += (int)tree.refs_mat[p].children.popcount(); + } + return n; +} + +fn int MTree.subtree_size(&tree, int parent) +{ + int x = tree.children_num(parent); + int c; + for (int n; (c = tree.children_it(parent, n)) >= 0; n++) { + x += tree.subtree_size(c); + } + return x; +} + + +fn int MTree.level_order_it(&tree, int parent, int i) +{ + if (i == 0) { + tree.queue.clear(); + tree.queue.push(parent); + } + + if (tree.queue.len() == 0) return -1; + + int p = tree.queue.pop_first()!!; + int c; + for (int n; (c = tree.children_it(p, n)) >= 0; n++) { + tree.queue.push(c); + } + return p; +} + +fn void MTree.prune(&tree, int parent) +{ + int c; + for (int i = 0; (c = tree.children_it(parent, i)) >= 0; i++) { + tree.prune(c); // prune the subtree + + // delete all children including their more chain + for (int p = c; p >= 0;) { + int next = tree.refs_mat[p].more; + tree.unset_used(p); + tree.refs_mat[p] = {.more = -1}; + p = next; + } + + } + + // finally delete the parent + for (int p = parent; p >= 0;) { + int next = tree.refs_mat[p].more; + tree.unset_used(p); + tree.elements--; + tree.refs_mat[p] = {.more = -1}; + p = next; + } + +} + + +macro bool MTree.is_root(&t, int i) => t.refs_mat[i].parent == -1; + +fn void MTree.print(&tree) +{ + foreach (idx, c: tree.elem_mat) { + if (tree.is_used((int)idx)) { + io::printfn("[%d](%s) parent:%d more:%d children:%b", + idx, c, tree.refs_mat[idx].parent, tree.refs_mat[idx].more, + tree.refs_mat[idx].children + ); + } + } +} + + +module foo; + +import std::io; +import mtree; + +alias Tree = mtree::MTree{int}; + +fn int main() +{ + Tree t; + t.init(256); + defer t.free(); +/* + int root = t.add(0, 0); + int c1 = t.add(root, 1); + int c2 = t.add(root, 2); + + int c11 = t.add(c1, 11); + int c12 = t.add(c1, 12); + + int c3 = t.add(root, 3); + + for (int x = 0; x < 70; x++) { + t.add(c2, x); + } + + int c31 = t.add(c3, 31); + int c32 = t.add(c3, 32); + + int c4 = t.add(root, 4); + + int c13 = t.add(c1, 13); + int c14 = t.add(c1, 14); + int c15 = t.add(c1, 15); + + t.prune(c2); + + io::printn("printing tree"); + t.print(); + + usz x; + foreach_r (u: t.used) { + x += u.popcount(); + io::printf("%b ", u); + } + io::printfn("TOT:%d/%d",x,t.elements); + + io::printn(t.subtree_size(root)); + + io::printn(); +*/ + + int root = t.add(0, 0); + int c1 = t.add(root, 1); + int c2 = t.add(root, 2); + int c3 = t.add(root, 3); + + int c11 = t.add(c1, 11); + int c12 = t.add(c1, 12); + + int c111 = t.add(c11, 111); + int c121 = t.add(c12, 121); + + int c31 = t.add(c3, 31); + + int c; + for (int i; (c = t.level_order_it(root, i)) >= 0; i++) { + io::printfn("%d-th: [%d](%d)", i, c, t.elem_mat[c]); + } + + return 0; +} \ No newline at end of file