new tree implementation

this about halves the time spent on level_order_it and drastically reduces the
time spent in children_it
This commit is contained in:
Alessandro Mauri 2025-09-21 18:17:39 +02:00
parent c046c6af52
commit 7f8b5196a5
5 changed files with 649 additions and 29 deletions

284
lib/ugui.c3l/src/mtree.c3 Normal file
View File

@ -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
);
}
}
}

View File

@ -1,6 +1,6 @@
module ugui; module ugui;
import vtree; import mtree;
import cache; import cache;
import fifo; import fifo;
@ -51,7 +51,7 @@ bitstruct ElemEvents : uint {
// element structure // element structure
struct Elem { struct Elem {
Id id; Id id;
isz tree_idx; int tree_idx;
ElemFlags flags; ElemFlags flags;
ElemEvents events; ElemEvents events;
Rect bounds; Rect bounds;
@ -69,7 +69,7 @@ struct Elem {
// relationships between elements are stored in a tree, it stores just the ids // 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 // elements themselves are kept in a cache
const uint MAX_ELEMENTS = 256; const uint MAX_ELEMENTS = 256;
@ -122,13 +122,13 @@ struct Ctx {
Id focus_id; Id focus_id;
Rect div_scissor; // the current div bounds used for scissor test 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 // return a pointer to the parent of the current active div
fn Elem*? Ctx.get_parent(&ctx) 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); Elem*? parent = ctx.cache.search(parent_id);
if (catch parent) return parent; if (catch parent) return parent;
if (parent.type != ETYPE_DIV) return WRONG_ELEMENT_TYPE?; 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) fn Id? Ctx.gen_id(&ctx, Id id2)
{ {
// FIXME: this is SHIT // 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 // Mix the two IDs non-linearly
Id mixed = id1 ^ id2.rotate_left(13); Id mixed = id1 ^ id2.rotate_left(13);
mixed ^= id1.rotate_left(7); mixed ^= id1.rotate_left(7);
@ -178,7 +178,7 @@ fn Elem*? Ctx.get_elem(&ctx, Id id, ElemType type)
} else { } else {
elem.type = type; 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; return elem;
} }
@ -196,13 +196,13 @@ macro Elem* Ctx.find_elem(&ctx, Id id)
fn Elem*? Ctx.get_active_div(&ctx) 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); return ctx.cache.search(id);
} }
fn void? Ctx.init(&ctx, Allocator allocator) 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(); } defer catch { (void)ctx.tree.free(); }
ctx.cache.init(allocator)!; ctx.cache.init(allocator)!;

View File

@ -316,18 +316,16 @@ fn void resolve_placement(Elem* c, Elem* p)
fn void Ctx.layout_element_tree(&ctx) fn void Ctx.layout_element_tree(&ctx)
{ {
isz cursor = -1; int current;
isz current = ctx.tree.level_order_it(0, &cursor)!!; for (int n; (current = ctx.tree.level_order_it(0, n)) >= 0; n++) {
for (; current >= 0; current = ctx.tree.level_order_it(0, &cursor)!!) { Elem* p = ctx.find_elem(ctx.tree.get(current));
Elem* p = ctx.find_elem(ctx.tree.get(current))!!;
//if (ctx.tree.is_root(current)!!) p = &&{}; //if (ctx.tree.is_root(current)!!) p = &&{};
int ch;
// RESOLVE KNOWN DIMENSIONS // RESOLVE KNOWN DIMENSIONS
isz ch_cur = 0; for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
isz ch = ctx.tree.children_it(current, &ch_cur)!!; Elem* c = ctx.find_elem(ctx.tree.get(ch));
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { if (ctx.tree.is_root(ch)) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_dimensions(p, &&{}); resolve_dimensions(p, &&{});
} else { } else {
resolve_dimensions(c, p); resolve_dimensions(c, p);
@ -335,11 +333,9 @@ fn void Ctx.layout_element_tree(&ctx)
} }
// RESOLVE GROW CHILDREN // RESOLVE GROW CHILDREN
ch_cur = 0; for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
ch = ctx.tree.children_it(current, &ch_cur)!!; Elem* c = ctx.find_elem(ctx.tree.get(ch));
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { if (ctx.tree.is_root(ch)) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_grow_elements(p, &&{}); resolve_grow_elements(p, &&{});
} else { } else {
resolve_grow_elements(c, p); resolve_grow_elements(c, p);
@ -347,11 +343,9 @@ fn void Ctx.layout_element_tree(&ctx)
} }
// RESOLVE CHILDREN PLACEMENT // RESOLVE CHILDREN PLACEMENT
ch_cur = 0; for (int i; (ch = ctx.tree.children_it(current, i)) >= 0; i++) {
ch = ctx.tree.children_it(current, &ch_cur)!!; Elem* c = ctx.find_elem(ctx.tree.get(ch));
for (; ch >= 0; ch = ctx.tree.children_it(current, &ch_cur)!!) { if (ctx.tree.is_root(ch)) {
Elem* c = ctx.find_elem(ctx.tree.get(ch))!!;
if (ctx.tree.is_root(ch)!!) {
resolve_placement(p, &&{}); resolve_placement(p, &&{});
update_children_bounds(p, &&{}); update_children_bounds(p, &&{});
} else { } else {

View File

@ -135,7 +135,7 @@ fn Id? Ctx.div_end(&ctx)
*/ */
// the active_div returns to the parent of the current one // 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()!; Elem* parent = ctx.get_parent()!;
ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset); ctx.div_scissor = parent.bounds.pad(parent.layout.content_offset);
ctx.reset_scissor(elem.div.z_index)!; ctx.reset_scissor(elem.div.z_index)!;

342
test/test_mtree.c3 Normal file
View File

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