Skip to documentation
SLOP

tiny.ui.Store

Reference tiny.ui Store

Defined in tiny.ui.

Store retains one accepted tree.

API (26)

Actions

Public operations.

Types and contracts

Public types and contracts.

Fields and members

Public fields and members.

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

Source

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

zig
/// `Store` retains one accepted tree. `init` makes its only allocation, sized by/// `Capacity.derive`, which holds two envelope blocks, one identity map per block, and the/// splice scratch. The retained tree occupies one block. An accepted publish with a new revision/// is written into the other block, which then becomes the retained one.pub const Store = struct {    pub const Limits = LimitsType;    pub const Capacity = CapacityType;    /// `Store.claim` is the static capacity claim `ui.publish_store`. It declares that the store's    /// retained bytes are exactly two blocks, two identity maps, and the scratch arrays, all    /// derived from `Limits`, and it names six witness tests.    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "ui.publish_store",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "double_buffered_retained_envelope_blocks",                        .lifetime = .steady,                        .detail = "the store's two envelope blocks, each Capacity.buffer_bytes " ++                            "long and aligned to storage_alignment, with one block holding the " ++                            "retained tree and the splice writing the next tree into the other",                    },                    .{                        .id = "open_addressed_node_identity_maps",                        .lifetime = .steady,                        .detail = "one identity map slot array of Limits.map_slots words for " ++                            "each envelope block",                    },                    .{                        .id = "splice_scratch_arrays",                        .lifetime = .steady,                        .detail = "the dirty node list and the placement map of Limits.nodes " ++                            "words each, and the atom and text remap arrays of Limits.atoms " ++                            "and Limits.texts words",                    },                },                .excluded = &.{                    "the publish buffer, which the caller owns and passes to Store.publish, " ++                        "where the survey reads it in place and the store keeps only the " ++                        "bytes the splice copies into its own block",                    "storage for computed style, layout, and facts derived from the retained " ++                        "tree, which belongs to the packages that compute them, with Store " ++                        "having no field for any of it",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(LimitsType, "nodes", "nodes"),                    alloc_phase.capacity.bindInput(LimitsType, "declarations", "declarations"),                    alloc_phase.capacity.bindInput(LimitsType, "classes", "classes"),                    alloc_phase.capacity.bindInput(LimitsType, "texts", "texts"),                    alloc_phase.capacity.bindInput(LimitsType, "runs", "runs"),                    alloc_phase.capacity.bindInput(LimitsType, "relations", "relations"),                    alloc_phase.capacity.bindInput(LimitsType, "atoms", "atoms"),                    alloc_phase.capacity.bindInput(LimitsType, "string_bytes", "string_bytes"),                    alloc_phase.capacity.bindInput(LimitsType, "solved_roots", "solved_roots"),                    alloc_phase.capacity.bindInput(LimitsType, "solved_rects", "solved_rects"),                    alloc_phase.capacity.bindInput(LimitsType, "map_slots", "map_slots"),                },                .type_selectors = &.{                    alloc_phase.capacity.bindType(abi.Node, "node"),                    alloc_phase.capacity.bindType(abi.Declaration, "declaration"),                    alloc_phase.capacity.bindType(u32, "u32"),                    alloc_phase.capacity.bindType(abi.TextRecord, "text_record"),                    alloc_phase.capacity.bindType(abi.TextRun, "text_run"),                    alloc_phase.capacity.bindType(abi.Relation, "relation"),                    alloc_phase.capacity.bindType(abi.Atom, "atom"),                    alloc_phase.capacity.bindType(u8, "u8"),                    alloc_phase.capacity.bindType(abi.SolvedRoot, "solved_root"),                    alloc_phase.capacity.bindType(abi.Rect, "rect"),                },                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .input = 1 },                    .{ .scale = .{ .node = 2, .coefficient = .{ .size_of_concrete_type = 1 } } },                    .{ .input = 2 },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .input = 3 },                    .{ .scale = .{ .node = 6, .coefficient = .{ .size_of_concrete_type = 3 } } },                    .{ .input = 4 },                    .{ .scale = .{ .node = 8, .coefficient = .{ .size_of_concrete_type = 4 } } },                    .{ .input = 5 },                    .{ .scale = .{ .node = 10, .coefficient = .{ .size_of_concrete_type = 5 } } },                    .{ .input = 6 },                    .{ .scale = .{ .node = 12, .coefficient = .{ .size_of_concrete_type = 6 } } },                    .{ .input = 7 },                    .{ .scale = .{ .node = 14, .coefficient = .{ .size_of_concrete_type = 7 } } },                    .{ .input = 8 },                    .{ .scale = .{ .node = 16, .coefficient = .{ .size_of_concrete_type = 8 } } },                    .{ .input = 9 },                    .{ .scale = .{ .node = 18, .coefficient = .{ .size_of_concrete_type = 9 } } },                    .{ .input = 10 },                    .{ .scale = .{ .node = 20, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .constant = 152 },                    .{ .alignment = .{ .node = 22, .alignment = .{ .concrete_type = 0 } } },                    .{ .add = .{ .left = 23, .right = 1 } },                    .{ .alignment = .{ .node = 24, .alignment = .{ .concrete_type = 1 } } },                    .{ .add = .{ .left = 25, .right = 3 } },                    .{ .alignment = .{ .node = 26, .alignment = .{ .concrete_type = 2 } } },                    .{ .add = .{ .left = 27, .right = 5 } },                    .{ .alignment = .{ .node = 28, .alignment = .{ .concrete_type = 3 } } },                    .{ .add = .{ .left = 29, .right = 7 } },                    .{ .alignment = .{ .node = 30, .alignment = .{ .concrete_type = 4 } } },                    .{ .add = .{ .left = 31, .right = 9 } },                    .{ .alignment = .{ .node = 32, .alignment = .{ .concrete_type = 5 } } },                    .{ .add = .{ .left = 33, .right = 11 } },                    .{ .alignment = .{ .node = 34, .alignment = .{ .concrete_type = 6 } } },                    .{ .add = .{ .left = 35, .right = 13 } },                    .{ .alignment = .{ .node = 36, .alignment = .{ .concrete_type = 7 } } },                    .{ .add = .{ .left = 37, .right = 15 } },                    .{ .alignment = .{ .node = 38, .alignment = .{ .concrete_type = 8 } } },                    .{ .add = .{ .left = 39, .right = 17 } },                    .{ .alignment = .{ .node = 40, .alignment = .{ .concrete_type = 9 } } },                    .{ .add = .{ .left = 41, .right = 19 } },                    .{ .alignment = .{ .node = 42, .alignment = .{ .literal = 8 } } },                    .{ .add = .{ .left = 43, .right = 43 } },                    .{ .add = .{ .left = 21, .right = 21 } },                    .{ .add = .{ .left = 44, .right = 45 } },                    .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .add = .{ .left = 46, .right = 47 } },                    .{ .add = .{ .left = 48, .right = 47 } },                    .{ .scale = .{ .node = 12, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .add = .{ .left = 49, .right = 50 } },                    .{ .scale = .{ .node = 6, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .add = .{ .left = 51, .right = 52 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 53,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "Store.publish runs the admission survey over the caller's buffer " ++                    "before the splice, and the survey rejects every quota, encoding, and " ++                    "retained reference fault, so a rejected publish writes no retained byte",            },            .risks = .{                .transitive = .{                    .status = .open,                    .detail = "the survey and the splice take no allocator, and six witness " ++                        "tests cover the store, but no machine-checked certificate of their " ++                        "call graphs proves them allocation free",                },                .foreign = .{                    .status = .excluded,                    .detail = "admission and splicing transform one caller buffer inside the " ++                        "process and make no operating-system call",                },            },            .obligations = &.{                .{ .key = "ui_publish_capacity", .role = .capacity_model },                .{ .key = "ui_publish_acquisition", .role = .acquisition },                .{ .key = "ui_publish_survey", .role = .overload },                .{ .key = "ui_publish_oom", .role = .initialization_failure },                .{ .key = "ui_publish_retained", .role = .integration },                .{ .key = "ui_publish_teardown", .role = .teardown },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },        },    };    phase: alloc_phase.capacity.Phase,    limits: LimitsType,    capacity: CapacityType,    bytes: []align(storage_alignment) u8,    blocks: [2][]align(storage_alignment) u8,    maps: [2]Map,    scratch: splice_mod.Scratch,    live: u1 = 0,    live_bytes: u32 = 0,    present: bool = false,    dirty_count: u32 = 0,    assets: ?*const asset.Registry = null,    asset_epoch: u64 = 0,    /// `init` derives the capacity from `limits`, makes the one allocation, and lays out the    /// blocks, maps, and scratch inside it. It returns `error.CapacityOverflow` or the    /// allocator's error.    pub fn init(allocator: Allocator, limits: LimitsType) !Store {        const capacity = try CapacityType.derive(limits);        const bytes = try allocator.alignedAlloc(            u8,            .fromByteUnits(storage_alignment),            capacity.total_bytes,        );        var store = Store{            .phase = .initialization,            .limits = limits,            .capacity = capacity,            .bytes = bytes,            .blocks = undefined,            .maps = undefined,            .scratch = undefined,        };        store.place();        return store;    }    /// `activate` empties both identity maps and moves the store into its steady phase. `admits`,    /// `publish`, and `forget` assert that phase.    pub fn activate(self: *Store) void {        std.debug.assert(self.phase == .initialization);        self.assertStorage();        self.maps[0].reset();        self.maps[1].reset();        self.phase = .steady;    }    pub fn setAssets(self: *Store, assets: *const asset.Registry) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(assets.phase == .steady);        self.assets = assets;        self.asset_epoch = assets.epoch;    }    /// `admits` returns true when every field of `limits` is at most the limit this store was    /// sized with.    pub fn admits(self: *const Store, limits: LimitsType) bool {        std.debug.assert(self.phase == .steady);        return limits.fits(self.limits);    }    /// `publish` accepts one envelope buffer or returns the first rejection. It reads the header    /// first. `Store.publish` returns after the header when the revision equals the retained    /// revision and the registry epoch equals the epoch at the last accepted publish. The    /// registry epoch changes whenever a font or image registers or releases, so when the epoch    /// differs, admission checks asset handles again even if the tree revision is the same.    /// Otherwise `admit` checks the whole buffer and the    /// splice writes it into the other block. A rejected publish leaves `retained`, `dirty`, and    /// `identity` as they were.    pub fn publish(self: *Store, buffer: []align(storage_alignment) const u8) Error!Accepted {        std.debug.assert(self.phase == .steady);        const head = try view_mod.readHeader(buffer);        const current_asset_epoch = if (self.assets) |assets| assets.epoch else 0;        if (self.present and head.revision == self.retained().header.revision and current_asset_epoch == self.asset_epoch) {            self.dirty_count = 0;            return .{ .dirty = 0, .unchanged = true };        }        const next: u1 = self.live ^ 1;        const report = try survey_mod.survey(buffer, self.limits, self.state(), &self.maps[next]);        const outcome = splice_mod.splice(            self.blocks[next],            &self.maps[next],            self.capacity,            report,            self.state(),            self.scratch,        );        self.live = next;        self.live_bytes = outcome.bytes;        self.present = true;        self.dirty_count = outcome.dirty;        self.asset_epoch = current_asset_epoch;        return .{ .dirty = outcome.dirty, .unchanged = outcome.dirty == 0 };    }    /// `forget` drops the retained tree and empties both identity maps, so the next publish is    /// surveyed with no retained tree. A node carrying the `retained` flag is rejected with    /// `error.StaleRetainedSubtree`, and an accepted publish marks every one of its nodes dirty.    pub fn forget(self: *Store) void {        std.debug.assert(self.phase == .steady);        self.maps[0].reset();        self.maps[1].reset();        self.present = false;        self.live_bytes = 0;        self.dirty_count = 0;        self.asset_epoch = if (self.assets) |assets| assets.epoch else 0;    }    /// `retained` returns a `View` of the retained tree, or an empty `View` when no tree is    /// retained. The view points into the store's block. After an accepted publish with a new    /// revision, a view taken earlier no longer shows the retained tree.    pub fn retained(self: *const Store) View {        if (!self.present) return .{};        var view = view_mod.bind(self.blocks[self.live][0..self.live_bytes]) catch unreachable;        view.identity = &self.maps[self.live];        view.prior = self.scratch.dirty[0..view.nodes.len];        return view;    }    /// `dirty` returns, in ascending order, the indices in `retained().nodes` of the nodes the    /// last accepted publish marked dirty. It is empty after a publish at the retained revision    /// and after `forget`.    pub fn dirty(self: *const Store) []const u32 {        return self.scratch.place[0..self.dirty_count];    }    /// `identity` returns the identity map of the retained tree. Pass `retained().nodes` to its    /// `lookup`.    pub fn identity(self: *const Store) *const Map {        return &self.maps[self.live];    }    /// `deinit` frees the allocation and moves the store into its teardown phase. After it,    /// `retained` returns an empty `View`.    pub fn deinit(self: *Store, allocator: Allocator) void {        std.debug.assert(self.phase != .teardown);        self.assertStorage();        self.phase = .teardown;        allocator.free(self.bytes);        self.bytes = &.{};        self.blocks = .{ &.{}, &.{} };        self.maps = .{ .{}, .{} };        self.scratch = .{ .place = &.{}, .atom_remap = &.{}, .text_remap = &.{}, .dirty = &.{} };        self.present = false;        self.assets = null;        self.asset_epoch = 0;    }    fn state(self: *const Store) survey_mod.Retained {        if (!self.present) return .{ .assets = self.assets };        const view = self.retained();        return .{            .view = view,            .map = self.maps[self.live],            .revision = view.header.revision,            .present = true,            .assets = self.assets,        };    }    fn place(self: *Store) void {        const block = self.capacity.buffer_bytes;        var cursor: usize = 0;        for (&self.blocks) |*slot| {            slot.* = @alignCast(self.bytes[cursor..][0..block]);            cursor += block;        }        for (&self.maps) |*slot| {            slot.* = .{ .slots = words(self.bytes, &cursor, self.limits.map_slots) };        }        self.scratch = .{            .dirty = words(self.bytes, &cursor, self.limits.nodes),            .place = words(self.bytes, &cursor, self.limits.nodes),            .atom_remap = words(self.bytes, &cursor, self.limits.atoms),            .text_remap = words(self.bytes, &cursor, self.limits.texts),        };        std.debug.assert(cursor == self.capacity.total_bytes);    }    fn assertStorage(self: *const Store) void {        const expected = CapacityType.derive(self.limits) catch unreachable;        std.debug.assert(std.meta.eql(expected, self.capacity));        std.debug.assert(self.bytes.len == self.capacity.total_bytes);        std.debug.assert(self.blocks[0].len == self.capacity.buffer_bytes);        std.debug.assert(self.blocks[1].len == self.capacity.buffer_bytes);        std.debug.assert(self.maps[0].slots.len == self.limits.map_slots);        std.debug.assert(self.scratch.dirty.len == self.limits.nodes);    }};

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

zig
pub const Store = tree.Store;
Called byCallsprivate sourcelib.ui.src.tree.store.Harnessinitprivate sourcelib.ui.src.tree.storeopenAndClosetest sourcelib.ui.src.tree.storetest: ui asset generation stale handl...private sourcelib.ui.src.tree.store.StoreassertStorageStoreactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.ui.src.tree.store.Harnessdeinitprivate sourcelib.ui.src.tree.store.Harnessinitprivate sourcelib.ui.src.tree.storeopenAndClosetest sourcelib.ui.src.tree.storetest: ui asset generation stale handl...private sourcelib.ui.src.tree.store.StoreassertStorageStoredeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate sourcelib.ui.src.tree.store.Harnessinitprivate sourcelib.ui.src.tree.storeopenAndClosetest sourcelib.ui.src.tree.storetest: the publish store reports a ref...test sourcelib.ui.src.tree.storetest: ui asset generation stale handl...Storeinit
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsprivate sourcelib.ui.src.tree.store.Harnesssendtest sourcelib.ui.src.tree.storetest: ui asset generation stale handl...Storeretainedprivate sourcelib.ui.src.tree.store.StorestateStorepublish
Static calls · unresolved targets: 1 · external targets: 3.
Called byCallsNo direct callsStorepublishprivate sourcelib.ui.src.tree.store.Storestatetest sourcelib.ui.src.tree.storetest: ui asset generation stale handl...Storeretained
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.ui.src.tree.storetest: ui asset generation stale handl...StoresetAssets
Static calls · unresolved targets: 0 · external targets: 0.

Also reachable as

tree.Store.

Audit

Definitions14
Public names28
Members13
Version26.7.0
Revisiondaab053ee433