Skip to documentation
SLOP

tiny.ui.tree

Reference tiny.ui tree

Defined in tiny.ui.

Retained tree namespace whose Store retains one accepted tree in one of two envelope blocks.

API (32)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.uitree
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/ui/src/tree/capacity.zig:5

zig
pub const Error = error{CapacityOverflow};

Source: lib/ui/src/tree/map.zig:14

zig
/// `Map` finds a node index by node id through open addressing with linear probing./// It stores node indices, not ids. `lookup` and `insert` read the id through the node array/// passed to them, so every call must pass the array the map was filled from.pub const Map = struct {    /// `Map.slots` must have a power-of-two length, which `lookup` and `insert` assert.    slots: []u32 = &.{},    /// `Map.count` is the number of occupied slots.    count: u32 = 0,    /// `reset` empties every slot and sets `count` to 0.    pub fn reset(self: *Map) void {        @memset(self.slots, absent);        self.count = 0;    }    /// `lookup` returns the index of the node whose id equals `id`, or null.    /// A map with no slots answers null. Each call probes at most `slots.len` slots.    pub fn lookup(self: *const Map, nodes: []const Node, id: u64) ?u32 {        if (self.slots.len == 0) return null;        std.debug.assert(std.math.isPowerOfTwo(self.slots.len));        const mask = self.slots.len - 1;        var index = @as(usize, hash(id)) & mask;        var probe: usize = 0;        while (probe < self.slots.len) : (probe += 1) {            const slot = self.slots[index];            if (slot == absent) return null;            std.debug.assert(slot - 1 < nodes.len);            if (nodes[slot - 1].id == id) return slot - 1;            index = (index + 1) & mask;        }        return null;    }    /// `insert` adds node `index` under that node's id and returns true. When a node    /// with the same id is already present, it returns false and leaves the map unchanged.    /// It asserts that a free slot remains.    pub fn insert(self: *Map, nodes: []const Node, index: u32) bool {        std.debug.assert(self.slots.len > 0);        std.debug.assert(std.math.isPowerOfTwo(self.slots.len));        std.debug.assert(index < nodes.len);        const id = nodes[index].id;        if (self.lookup(nodes, id) != null) return false;        std.debug.assert(self.count < self.slots.len);        const mask = self.slots.len - 1;        var slot = @as(usize, hash(id)) & mask;        var probe: usize = 0;        while (probe < self.slots.len) : (probe += 1) {            if (self.slots[slot] == absent) {                self.slots[slot] = index + 1;                self.count += 1;                return true;            }            slot = (slot + 1) & mask;        }        unreachable;    }};

Source: lib/ui/src/tree/splice.zig:33

zig
pub const Outcome = struct {    /// `Outcome.bytes` is the length of the envelope written into the block. It is the publish    /// buffer's length when no node carries the `retained` flag, and the full block size otherwise.    bytes: u32 = 0,    /// `Outcome.dirty` is the number of indices written to the beginning of `Scratch.place`,    /// each naming a dirty node's position in the newly written preorder node array.    dirty: u32 = 0,};

Source: lib/ui/src/tree/splice.zig:26

zig
/// `Scratch` holds the work arrays for a splice, which writes a new preorder node array in the/// order of the flat tree. While the splice emits nodes, `Scratch.place` maps publish indices to/// written indices. After the emit and trailer phases, the splice reuses that slice for the/// compact dirty index list, which `Store.dirty` returns as new preorder indices. After the/// splice, `Scratch.dirty` holds one packed prior entry per written node: the low 31 bits name/// the node's index in the previously retained tree, the high bit says its revision changed or/// its id is new, and all low bits set means there was no retained node. The correspondence/// describes only the immediately preceding retained tree, and remains valid until the next/// publish modifies the scratch arrays.pub const Scratch = struct {    place: []u32,    atom_remap: []u32,    text_remap: []u32,    dirty: []u32,};

Source: lib/ui/src/tree/store.zig:25

zig
/// `Accepted` is the result of an accepted publish.pub const Accepted = struct {    /// `Accepted.dirty` is the number of nodes `Store.dirty` lists.    dirty: u32 = 0,    /// `Accepted.unchanged` is true when that number is 0.    unchanged: bool = false,};

Source: lib/ui/src/tree/survey.zig:89

zig
/// `Report` is what `survey` returns for an accepted publish.pub const Report = struct {    /// `Report.view` is the bound publish.    view: View,    /// `Report.reuses_retained` is true when at least one node carries the `retained` flag.    reuses_retained: bool = false,    /// `Report.spliced` holds the counts described for `Spliced`.    spliced: Spliced = .{},};

Source: lib/ui/src/tree/survey.zig:61

zig
/// `Retained` describes the tree a publish is admitted against.pub const Retained = struct {    /// `Retained.view` is that tree.    view: View = .{},    /// `Retained.map` is its identity map.    map: Map = .{},    /// `Retained.revision` is its header revision.    revision: u64 = 0,    /// `Retained.present` is false when no tree is retained, which is the default.    present: bool = false,    assets: ?*const asset.Registry = null,};

Source: lib/ui/src/tree/survey.zig:78

zig
/// `Spliced` counts what the splice adds beyond the publish's own tables when it/// copies retained subtrees in./// `Spliced.nodes` and `Spliced.classes` are exact. The other counts are upper bounds,/// because the survey counts absent atoms that the splice skips, shared atoms and texts/// that the splice copies once, and the whole retained string table for every placeholder.pub const Spliced = struct {    nodes: u64 = 0,    declarations: u64 = 0,    classes: u64 = 0,    texts: u64 = 0,    runs: u64 = 0,    atoms: u64 = 0,    strings: u64 = 0,};

Source: lib/ui/src/tree/view.zig:18

zig
pub const BindError = error{    BufferTooSmall,    BufferLengthMismatch,    MagicMismatch,    AbiVersionMismatch,    NodeSizeMismatch,    SpanOutOfBounds,    SpanMisaligned,};

Source: lib/ui/src/tree/walk.zig:8

zig
/// `Children` iterates over the direct children of one node, in preorder.pub const Children = struct {    nodes: []const Node,    /// `Children.last` is the index of the last descendant of the node whose children are walked.    last: u32,    /// `Children.cursor` is the index the next call examines.    cursor: u32,    /// `Children.next` returns the index of the next direct child, or null after the last one.    /// Each call jumps over that child's whole subtree through its `subtree_count`,    /// so a walk takes one step per child and never visits a grandchild.    pub fn next(self: *Children) ?u32 {        if (self.cursor > self.last) return null;        const found = self.cursor;        std.debug.assert(found < self.nodes.len);        const span = self.nodes[found].subtree_count;        std.debug.assert(found + span <= self.last);        self.cursor = found + 1 + span;        return found;    }};

Source: lib/ui/src/tree/capacity.zig:44

zig
/// `arrays` lists the ten tables in the order they sit in a block.pub const arrays = [_]Array{    .{ .name = "nodes", .limit = "nodes", .bytes = @sizeOf(abi.Node), .alignment = 8 },    .{ .name = "declarations", .limit = "declarations", .bytes = 16, .alignment = 4 },    .{ .name = "classes", .limit = "classes", .bytes = 4, .alignment = 4 },    .{ .name = "texts", .limit = "texts", .bytes = @sizeOf(abi.TextRecord), .alignment = 4 },    .{ .name = "runs", .limit = "runs", .bytes = @sizeOf(abi.TextRun), .alignment = 4 },    .{ .name = "relations", .limit = "relations", .bytes = @sizeOf(abi.Relation), .alignment = 4 },    .{ .name = "atoms", .limit = "atoms", .bytes = @sizeOf(abi.Atom), .alignment = 4 },    .{ .name = "strings", .limit = "string_bytes", .bytes = 1, .alignment = 1 },    .{        .name = "solved_roots",        .limit = "solved_roots",        .bytes = @sizeOf(abi.SolvedRoot),        .alignment = 4,    },    .{        .name = "solved_rects",        .limit = "solved_rects",        .bytes = @sizeOf(abi.Rect),        .alignment = 4,    },};

Source: lib/ui/src/tree/capacity.zig:114

zig
/// `slotCount` returns the smallest power of two at least twice `nodes`. It returns 0/// for zero nodes and when that power of two does not fit in 32 bits.pub fn slotCount(nodes: u32) u32 {    if (nodes == 0) return 0;    const wanted = std.math.mul(u32, nodes, 2) catch return 0;    return std.math.ceilPowerOfTwo(u32, wanted) catch 0;}
Called byCallsNo direct callsCapacityderivetest sourcelib.ui.src.tree.capacitytest: a slot count stays a power of t...treeslotCount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callerstree.Maplookupprivate sourcelib.ui.src.tree.maphashtree.Mapinsert
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstree.Mapinsertprivate sourcelib.ui.src.tree.maphashtree.Maplookup
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/ui/src/tree/map.zig:9

zig
/// `absent` is 0, the value of an empty slot./// An occupied slot stores its node index plus 1.pub const absent: u32 = 0;

Source: lib/ui/src/tree/splice.zig:45

zig
pub const prior_absent: u32 = 0x7fff_ffff;

Source: lib/ui/src/tree/splice.zig:44

zig
pub const prior_dirty: u32 = 0x8000_0000;

Source: lib/ui/src/tree/splice.zig:78

zig
/// `splice` is exported as `tree.graft`. It writes an accepted publish into `block` and leaves/// `block_map` indexing the written nodes. It takes the `Report` that `admit` returned for that/// publish and the same `Retained`. When no node carries the `retained` flag, it copies the/// publish bytes whole and keeps the map `admit` filled, so `block_map` must be that map. It marks/// dirty each node whose id the retained tree lacks or holds at another revision, and skips the/// descendants of a node whose revision matches. Otherwise it rebuilds the tree. It copies the/// publish tables, replaces each placeholder with the retained subtree of the same id, copies that/// subtree's declarations, classes, atoms, texts, and runs with their indices remapped, and/// rebuilds `block_map`. It marks dirty each other node whose id the retained tree lacks or holds/// at another revision, and never a node of a retained subtree. It returns no error, because/// `admit` already checked every bound.pub fn splice(    block: []align(8) u8,    block_map: *Map,    capacity: Capacity,    report: Report,    retained: Retained,    scratch: Scratch,) Outcome {    if (!report.reuses_retained) return bulk(block, block_map, report.view, retained, scratch);    var sink = Sink{        .block = block,        .capacity = capacity,        .atom_remap = scratch.atom_remap,        .text_remap = scratch.text_remap,    };    return rebuild(&sink, block_map, report.view, retained, scratch);}
Called byCallsNo direct callersprivate sourcelib.ui.src.tree.splicebulkprivate sourcelib.ui.src.tree.splicerebuildtreegraft
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/ui/src/tree/store.zig:20

zig
/// `storage_alignment` is the byte alignment of a `Store`'s allocation and of every buffer/// `Store.publish` accepts.pub const storage_alignment: usize = 8;

Source: lib/ui/src/tree/survey.zig:16

zig
/// `Error` is every rejection that `survey` and `Store.publish` return./// It includes every `BindError`.pub const Error = view_mod.BindError || error{    PublishTooLarge,    NodeLimitExceeded,    DeclarationLimitExceeded,    ClassLimitExceeded,    TextLimitExceeded,    RunLimitExceeded,    RelationLimitExceeded,    AtomLimitExceeded,    StringLimitExceeded,    SolvedRootLimitExceeded,    SolvedRectLimitExceeded,    SubtreeExtentOutOfBounds,    ParentNotAncestor,    RootParentMismatch,    RootSubtreeIncomplete,    DepthExceeded,    DuplicateNodeId,    StaleRetainedSubtree,    RetainedSubtreeNotEmpty,    AtomIndexOutOfBounds,    AtomTextOutOfBounds,    DeclarationIndexOutOfBounds,    ClassIndexOutOfBounds,    TextIndexOutOfBounds,    RunIndexOutOfBounds,    TextRunEmpty,    TextRunsUnordered,    TextRunsOverlap,    TextRunOutOfBounds,    TextRunSplitsScalar,    TextRunPropertyNotAllowed,    TextNotUtf8,    RelationsUnsorted,    RelationNodeOutOfBounds,    SolvedRootNotSolved,    SolvedRootNodeOutOfBounds,    SolvedRootsUnordered,    SolvedRectsOutOfBounds,    SolvedNodeWithoutRoot,    RetainedSolvedSubtree,    StaleAsset,};

Source: lib/ui/src/tree/survey.zig:115

zig
/// `survey` is exported as `tree.admit`. It checks one publish buffer against `limits` and/// `retained` and returns a `Report` or the first rejection. It checks the buffer size, the header,/// and the spans, then the table quotas. `survey` checks the declaration table for asset handles/// after quota checks and before it resets scratch or walks nodes. It rejects a stale generation/// with `error.StaleAsset`. It then walks the node table once, then loops over the/// atoms, the relations, and the solved roots. When a node reuses a retained subtree, it last/// checks the quotas again with those subtrees counted. `survey` reads the buffer and writes only/// `scratch`. `survey` calls `scratch.reset()` only after the size check, `bind`, and/// `surveyQuotas` have passed, and the node walk then inserts one node at a time. A rejection/// can leave `scratch` untouched, partly filled, or wholly filled, and only an accepted publish/// leaves every node of the publish in it./// `scratch` needs a power-of-two slot count at least as large as the publish's node count.pub fn survey(    bytes: []align(8) const u8,    limits: Limits,    retained: Retained,    scratch: *Map,) Error!Report {    if (bytes.len > limitsBytes(limits)) return error.PublishTooLarge;    const view = try view_mod.bind(bytes);    try surveyQuotas(view, limits);    try surveyAssets(view, retained.assets);    scratch.reset();    var report = Report{ .view = view };    const solved_nodes = try surveyNodes(view, retained, scratch, &report);    try surveyAtoms(view);    try surveyRelations(view);    try surveySolved(view, solved_nodes);    if (report.reuses_retained) try surveyMerge(view, limits, report);    return report;}
Called byCallsprivate sourcelib.ui.src.tree.surveyadmittest sourcelib.ui.src.tree.surveytest: a buffer too short to hold a he...test sourcelib.ui.src.tree.surveytest: a publish larger than the decla...test sourcelib.ui.src.tree.surveytest: a publish over a declared span ...test sourcelib.ui.src.tree.surveytest: every declared span quota rejec...private sourcelib.ui.src.tree.surveylimitsBytesprivate sourcelib.ui.src.tree.surveysurveyAssetsprivate sourcelib.ui.src.tree.surveysurveyAtomsprivate sourcelib.ui.src.tree.surveysurveyMergeprivate sourcelib.ui.src.tree.surveysurveyNodes+3 moretreeadmit
Static calls · unresolved targets: 1 · external targets: 2.

Source: lib/ui/src/tree/view.zig:135

zig
/// `bind` runs `readHeader`, checks each of the ten table spans, and returns a `View`./// It does not check the node tree, the indices inside records, or atom ranges. `admit` checks/// those. The `View` accessors assert those bounds rather than return errors, so they belong on/// a buffer `admit` accepted, such as the block `Store.retained` binds.pub fn bind(bytes: []align(8) const u8) BindError!View {    const header = try readHeader(bytes);    return .{        .bytes = bytes,        .header = header,        .nodes = try sliceOf(Node, bytes, header.nodes),        .declarations = try sliceOf(Declaration, bytes, header.declarations),        .classes = try sliceOf(u32, bytes, header.classes),        .texts = try sliceOf(TextRecord, bytes, header.texts),        .runs = try sliceOf(TextRun, bytes, header.runs),        .relations = try sliceOf(Relation, bytes, header.relations),        .atoms = try sliceOf(Atom, bytes, header.atoms),        .strings = try sliceOf(u8, bytes, header.strings),        .solved_roots = try sliceOf(SolvedRoot, bytes, header.solved_roots),        .solved_rects = try sliceOf(Rect, bytes, header.solved_rects),    };}
Called byCallstest sourcelib.ui.src.tree.viewtest: a buffer shorter than one heade...test sourcelib.ui.src.tree.viewtest: a header whose magic, version, ...test sourcelib.ui.src.tree.viewtest: a span reaching past the buffer...test sourcelib.ui.src.tree.viewtest: a span whose element alignment ...treereadHeaderprivate sourcelib.ui.src.tree.viewsliceOftreebind
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/ui/src/tree/view.zig:121

zig
/// `readHeader` copies the 152 byte header out of the buffer and reads no table./// It checks, in order, that the buffer holds a whole header (`error.BufferTooSmall`), then/// `magic` (`error.MagicMismatch`), `abi_version` (`error.AbiVersionMismatch`), `node_bytes`/// (`error.NodeSizeMismatch`), and `buffer_bytes` (`error.BufferLengthMismatch`).pub fn readHeader(bytes: []align(8) const u8) BindError!Header {    if (bytes.len < abi.header_bytes) return error.BufferTooSmall;    const header = std.mem.bytesToValue(Header, bytes[0..abi.header_bytes]);    if (header.magic != abi.magic) return error.MagicMismatch;    if (header.abi_version != abi.abi_version) return error.AbiVersionMismatch;    if (header.node_bytes != @sizeOf(Node)) return error.NodeSizeMismatch;    if (header.buffer_bytes != bytes.len) return error.BufferLengthMismatch;    return header;}
Called byCallsNo direct callstreebindtreereadHeader
Static calls · unresolved targets: 1 · external targets: 0.

Source: lib/ui/src/tree/walk.zig:48

zig
/// `childCount` returns the number of direct children of node `index` by walking them.pub fn childCount(nodes: []const Node, index: u32) u32 {    var walk = children(nodes, index);    var count: u32 = 0;    while (walk.next()) |_| count += 1;    return count;}
Called byCallstest sourcelib.ui.src.tree.walktest: a leaf enumerates no children a...test sourcelib.ui.src.tree.walktest: a preorder child walk names chi...test sourcelib.ui.src.tree.walktest: every child of a wide node is e...treechildrentreechildCount
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/ui/src/tree/walk.zig:31

zig
/// `children` returns the `Children` iterator for node `index`./// It asserts that `index` and that node's subtree lie inside `nodes`.pub fn children(nodes: []const Node, index: u32) Children {    std.debug.assert(index < nodes.len);    const span = nodes[index].subtree_count;    std.debug.assert(index + span < nodes.len);    return .{ .nodes = nodes, .last = index + span, .cursor = index + 1 };}
Called byCallsNo direct callsViewchildrentreechildCounttest sourcelib.ui.src.tree.walktest: a leaf enumerates no children a...test sourcelib.ui.src.tree.walktest: a preorder child walk names chi...test sourcelib.ui.src.tree.walktest: every child of a wide node is e...treechildren
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/ui/src/tree/walk.zig:40

zig
/// `subtree` returns node `index` followed by all of its descendants,/// as one slice of `subtree_count + 1` nodes.pub fn subtree(nodes: []const Node, index: u32) []const Node {    std.debug.assert(index < nodes.len);    const span = nodes[index].subtree_count;    std.debug.assert(index + span < nodes.len);    return nodes[index .. index + span + 1];}
Called byCallsNo direct callsViewsubtreetest sourcelib.ui.src.tree.walktest: a leaf enumerates no children a...test sourcelib.ui.src.tree.walktest: a preorder child walk names chi...treesubtree
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/ui/src/root.zig:10

zig
pub const tree = @import("tree/root.zig");

Source: lib/ui/src/tree/root.zig

zig
//! Retained tree namespace whose `Store` retains one accepted tree in one of two envelope blocks.//!//! `admit` is the admission survey rejecting before any retained byte changes.//! `bind` returns a `View` over the buffer whose accessors copy no table data.//! `readHeader` copies only the 152 byte header.const capacity = @import("capacity.zig");const map = @import("map.zig");const splice = @import("splice.zig");const store = @import("store.zig");const survey = @import("survey.zig");const view = @import("view.zig");const walk = @import("walk.zig");pub const Capacity = capacity.Capacity;pub const Limits = capacity.Limits;pub const CapacityError = capacity.Error;pub const arrays = capacity.arrays;pub const slotCount = capacity.slotCount;pub const Map = map.Map;pub const map_absent = map.absent;pub const View = view.View;pub const prior_dirty = splice.prior_dirty;pub const prior_absent = splice.prior_absent;pub const BindError = view.BindError;pub const bind = view.bind;pub const readHeader = view.readHeader;pub const Error = survey.Error;pub const Report = survey.Report;pub const Retained = survey.Retained;pub const Spliced = survey.Spliced;pub const admit = survey.survey;pub const Scratch = splice.Scratch;pub const Outcome = splice.Outcome;pub const graft = splice.splice;pub const Accepted = store.Accepted;pub const Store = store.Store;pub const storage_alignment = store.storage_alignment;pub const Children = walk.Children;pub const children = walk.children;pub const subtree = walk.subtree;pub const childCount = walk.childCount;

Complete call list for tree.admit

8 direct calls.

Audit

Definitions29
Public names29
Members36
Version26.7.0
Revisiondaab053ee433