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.
Children.next:Children.nextreturns the index of the next direct child, or null after the last one.Map.insert:insertadds nodeindexunder that node's id and returns true.Map.lookup:lookupreturns the index of the node whose id equalsid, or null.Map.reset:resetempties every slot and setscountto 0.admit:surveyis exported astree.admit.bind:bindrunsreadHeader, checks each of the ten table spans, and returns aView.childCount:childCountreturns the number of direct children of nodeindexby walking them.children:childrenreturns theChildreniterator for nodeindex.graft:spliceis exported astree.graft.readHeader:readHeadercopies the 152 byte header out of the buffer and reads no table.slotCount:slotCountreturns the smallest power of two at least twicenodes.subtree:subtreereturns nodeindexfollowed by all of its descendants, as one slice ofsubtree_count + 1nodes.
Types and contracts
Public types and contracts.
Accepted:Acceptedis the result of an accepted publish.BindErrorCapacity:Capacityis the layoutCapacity.derivecomputes from oneLimits.CapacityErrorChildren:Childreniterates over the direct children of one node, in preorder.Error:Erroris every rejection thatsurveyandStore.publishreturn.Limits:Limitsis the largest count of each table aStoreaccepts, plus the number of identity map slots.Map:Mapfinds a node index by node id through open addressing with linear probing.OutcomeReport:Reportis whatsurveyreturns for an accepted publish.Retained:Retaineddescribes the tree a publish is admitted against.Scratch:Scratchholds the work arrays for a splice, which writes a new preorder node array in the order of the flat tree.Spliced:Splicedcounts what the splice adds beyond the publish's own tables when it copies retained subtrees in.Store:Storeretains one accepted tree.View:Viewholds typed slices over one publish buffer.
Values and defaults
Public values and defaults.
arrays:arrayslists the ten tables in the order they sit in a block.map_absent:absentis 0, the value of an empty slot.prior_absentprior_dirtystorage_alignment:storage_alignmentis the byte alignment of aStore's allocation and of every bufferStore.publishaccepts.
Source
Source: lib/ui/src/tree/capacity.zig:5
pub const Error = error{CapacityOverflow};Source: lib/ui/src/tree/map.zig:14
/// `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
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
/// `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
/// `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
/// `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
/// `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
/// `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
pub const BindError = error{ BufferTooSmall, BufferLengthMismatch, MagicMismatch, AbiVersionMismatch, NodeSizeMismatch, SpanOutOfBounds, SpanMisaligned,};Source: lib/ui/src/tree/walk.zig:8
/// `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
/// `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
/// `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;}Source: lib/ui/src/tree/map.zig:9
/// `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
pub const prior_absent: u32 = 0x7fff_ffff;Source: lib/ui/src/tree/splice.zig:44
pub const prior_dirty: u32 = 0x8000_0000;Source: lib/ui/src/tree/splice.zig:78
/// `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);}Source: lib/ui/src/tree/store.zig:20
/// `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
/// `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
/// `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;}Source: lib/ui/src/tree/view.zig:135
/// `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), };}Source: lib/ui/src/tree/view.zig:121
/// `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;}Source: lib/ui/src/tree/walk.zig:48
/// `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;}Source: lib/ui/src/tree/walk.zig:31
/// `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 };}Source: lib/ui/src/tree/walk.zig:40
/// `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];}Source: lib/ui/src/root.zig:10
pub const tree = @import("tree/root.zig");Source: lib/ui/src/tree/root.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.
lib.ui.src.tree.survey.limitsBytes[function] — private source atlib/ui/src/tree/survey.zig:148in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyAssets[function] — private source atlib/ui/src/tree/survey.zig:135in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyAtoms[function] — private source atlib/ui/src/tree/survey.zig:335in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyMerge[function] — private source atlib/ui/src/tree/survey.zig:367in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyNodes[function] — private source atlib/ui/src/tree/survey.zig:166in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyQuotas[function] — private source atlib/ui/src/tree/survey.zig:153in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveyRelations[function] — private source atlib/ui/src/tree/survey.zig:342in nearest public ownerlib.ui.src.tree.surveylib.ui.src.tree.survey.surveySolved[function] — private source atlib/ui/src/tree/survey.zig:352in nearest public ownerlib.ui.src.tree.survey
Audit
| Definitions | 29 |
|---|---|
| Public names | 29 |
| Members | 36 |
| Version | 26.7.0 |
| Revision | daab053ee433 |