tiny.ui.Store
Defined in tiny.ui.
Store retains one accepted tree.
API (26)
Actions
Public operations.
activate:activateempties both identity maps and moves the store into its steady phase.admits:admitsreturns true when every field oflimitsis at most the limit this store was sized with.deinit:deinitfrees the allocation and moves the store into its teardown phase.dirty:dirtyreturns, in ascending order, the indices inretained().nodesof the nodes the last accepted publish marked dirty.forget:forgetdrops the retained tree and empties both identity maps, so the next publish is surveyed with no retained tree.identity:identityreturns the identity map of the retained tree.init:initderives the capacity fromlimits, makes the one allocation, and lays out the blocks, maps, and scratch inside it.publish:publishaccepts one envelope buffer or returns the first rejection.retained:retainedreturns aViewof the retained tree, or an emptyViewwhen no tree is retained.setAssets
Types and contracts
Public types and contracts.
Fields and members
Public fields and members.
asset_epochassetsblocksbytescapacitydirty_countlimitslivelive_bytesmapsphasepresentscratch
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;Also reachable as
Audit
| Definitions | 14 |
|---|---|
| Public names | 28 |
| Members | 13 |
| Version | 26.7.0 |
| Revision | daab053ee433 |