Skip to documentation
SLOP

tiny.ui.style.generation

Reference tiny.ui style generation

Defined in style.

API (25)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callstest sourcelib.ui.src.style.generationtest: ui style capacity derives the c...style.generation.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.ui.src.style.generationopenAndClosetest sourcelib.ui.src.style.generationtest: incremental cascade agrees with...test sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: style ids share within an epoch...test sourcelib.ui.src.style.generationtest: stylesheet generation visits on...+7 morestyle.Engineactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.ui.src.style.testtest: style lowering resolves a regis...style.Enginerecordstyle.EngineauthoredFallbacks
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersstyle.EnginecascadeViewstyle.Enginecascade
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsstyle.Enginecascadetest sourcelib.ui.src.style.generationtest: incremental cascade agrees with...test sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: style ids share within an epoch...test sourcelib.ui.src.style.generationtest: stylesheet generation visits on...+5 moreprivate sourcelib.ui.src.style.generation.EngineclassBloomprivate sourcelib.ui.src.style.generation.EnginecountCandidatesprivate sourcelib.ui.src.style.generation.EnginefastCascadeprivate sourcelib.ui.src.style.generation.EnginehoverAncestorKeyHitprivate sourcelib.ui.src.style.generation.EnginemarkBucketKeys+8 morestyle.EnginecascadeView
Static calls · unresolved targets: 4 · external targets: 17.
Called byCallsNo direct callsprivate sourcelib.ui.src.style.generationopenAndClosetest sourcelib.ui.src.style.generationtest: incremental cascade agrees with...test sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: style ids share within an epoch...test sourcelib.ui.src.style.generationtest: stylesheet generation visits on...+7 morestyle.Enginedeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate sourcelib.ui.src.style.generationopenAndClosetest sourcelib.ui.src.style.generationtest: incremental cascade agrees with...test sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: style ids share within an epoch...test sourcelib.ui.src.style.generationtest: stylesheet generation visits on...+8 morestyle.Engineinit
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallsNo direct callsstyle.EngineauthoredFallbackstest sourcelib.ui.src.style.generationtest: incremental cascade agrees with...test sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: stylesheet generation visits on...test sourcelib.ui.src.style.testtest: style lowering reads an inline ...+3 morestyle.Enginerecord
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.ui.src.style.generationtest: ui style font chain rejects a f...test sourcelib.ui.src.style.testtest: style lowering reads an inline ...test sourcelib.ui.src.style.testtest: style lowering resolves a regis...test sourcelib.ui.src.style.testtest: stylesheet replacement resolves...style.EnginesetFonts
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.ui.src.style.generationtest: sparse quota failure preserves ...test sourcelib.ui.src.style.generationtest: style ids share within an epoch...style.EnginestyleEpoch
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.ui.src.style.generationtest: style ids share within an epoch...style.EnginestyleId
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/ui/src/style/generation.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const css = @import("css");const abi = @import("../abi/root.zig");const tree = @import("../tree/root.zig");const asset = @import("../asset/root.zig");const computed = @import("computed.zig");const element = @import("element.zig");const share = @import("share.zig");const Allocator = std.mem.Allocator;pub const Limits = struct {    nodes: u32 = 16_384,    distinct_styles: u32 = 224,    selectors: u32 = 512,    font_faces: u32 = 32,    bytes: usize = 2_097_152,};pub const Capacity = struct {    required_bytes: usize,    total_bytes: usize,    storage_bytes: usize,    pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {        const nodes = std.math.mul(usize, limits.nodes, 2 * @sizeOf(NodeState)) catch return error.CapacityOverflow;        const records = std.math.mul(usize, limits.distinct_styles, 2 * @sizeOf(computed.Record)) catch return error.CapacityOverflow;        const chain_slots = std.math.mul(usize, limits.distinct_styles, limits.font_faces) catch return error.CapacityOverflow;        if (chain_slots > std.math.maxInt(u32)) return error.CapacityOverflow;        const chains = std.math.mul(usize, chain_slots, 2 * @sizeOf(asset.AssetHandle)) catch return error.CapacityOverflow;        const slots = std.math.mul(usize, std.math.ceilPowerOfTwo(usize, @as(usize, limits.distinct_styles) * 2) catch return error.CapacityOverflow, 2 * @sizeOf(u32)) catch return error.CapacityOverflow;        const key_slots = std.math.mul(usize, std.math.ceilPowerOfTwo(usize, limits.distinct_styles) catch return error.CapacityOverflow, 2 * @sizeOf(share.KeySlot)) catch return error.CapacityOverflow;        const entries = std.math.mul(usize, limits.selectors, @sizeOf(css.bucket.Entry) + @sizeOf(css.match.Match)) catch return error.CapacityOverflow;        const flags = @as(usize, limits.nodes) + @as(usize, limits.distinct_styles) * @sizeOf(u32);        const word_count = std.math.divCeil(usize, limits.nodes, 64) catch return error.CapacityOverflow;        const key_words = std.math.mul(usize, word_count, (2 * key_columns + 1) * @sizeOf(u64)) catch return error.CapacityOverflow;        const node_keys = std.math.mul(usize, limits.nodes, 2 * @sizeOf(u16)) catch return error.CapacityOverflow;        const sum = nodes;        const sum2 = std.math.add(usize, sum, records) catch return error.CapacityOverflow;        const sum3 = std.math.add(usize, sum2, slots) catch return error.CapacityOverflow;        const sum4 = std.math.add(usize, sum3, entries) catch return error.CapacityOverflow;        const sum5 = std.math.add(usize, sum4, key_slots) catch return error.CapacityOverflow;        const tail = std.math.add(usize, flags, key_words) catch return error.CapacityOverflow;        const tail_with_keys = std.math.add(usize, tail, node_keys) catch return error.CapacityOverflow;        const tail_with_chains = std.math.add(usize, tail_with_keys, chains) catch return error.CapacityOverflow;        const tail_with_alignment = std.math.add(usize, tail_with_chains, 80) catch return error.CapacityOverflow;        const required = std.math.add(usize, sum5, tail_with_alignment) catch return error.CapacityOverflow;        if (required > limits.bytes) return error.CapacityOverflow;        return .{ .required_bytes = required, .total_bytes = limits.bytes, .storage_bytes = limits.bytes };    }};const NodeState = extern struct {    id: u64 = 0,    ancestor_classes: u64 = 0,    style: u32 = 0,    computed_generation: u32 = 0,};const hover_sensitive: u32 = 0x8000_0000;const style_mask: u32 = ~hover_sensitive;const key_columns: usize = 14;const ChainSet = struct {    ids: [128]u64 = undefined,    occupied: [128]bool = @splat(false),    fn position(id: u64) usize {        const mixed = id *% 0x9e3779b97f4a7c15;        return @intCast((mixed >> 57) & 127);    }    fn insert(self: *ChainSet, id: u64) void {        var at = position(id);        for (0..self.ids.len) |_| {            if (!self.occupied[at]) {                self.ids[at] = id;                self.occupied[at] = true;                return;            }            if (self.ids[at] == id) return;            at = (at + 1) & 127;        }        unreachable;    }    fn contains(self: *const ChainSet, id: u64) bool {        var at = position(id);        for (0..self.ids.len) |_| {            if (!self.occupied[at]) return false;            if (self.ids[at] == id) return true;            at = (at + 1) & 127;        }        return false;    }};fn bucketBit(space: css.bucket.Space, name: []const u8) u16 {    const digest = css.atom.hash(name) ^ (@as(u32, @backingInt(space)) *% 0x9e3779b9);    return @as(u16, 1) << @intCast(digest % key_columns);}const SelectorKeys = struct { bits: u16, universal: bool };fn selectorKeys(index: css.bucket.Index) SelectorKeys {    var bits: u16 = 0;    var universal = false;    for (index.entries) |entry| {        const space: css.bucket.Space = @fromBackingInt(@intCast(entry.space));        if (space == .universal) {            universal = true;        } else {            bits |= bucketBit(space, index.source.name(entry.key));        }    }    return .{ .bits = bits, .universal = universal };}pub const Error = error{ DistinctStyleQuota, MatchedRuleQuota, SelectorQuota, CapacityOverflow } || Allocator.Error;pub const Exhaustion = error{ DistinctStyleQuota, MatchedRuleQuota, SelectorQuota, CapacityOverflow };const LimitsType = Limits;const CapacityType = Capacity;const ExhaustionType = Exhaustion;pub const Result = struct {    restyled: u32 = 0,    visited: u32 = 0,    carried: u32 = 0,    carried_bytes: u64 = 0,    shared: u32 = 0,    candidates: u64 = 0,    ancestor_walks: u64 = 0,    combinator_candidates: u64 = 0,};pub const Engine = struct {    pub const Limits = LimitsType;    pub const Capacity = CapacityType;    pub const Storage = []align(8) u8;    pub const storage_alignment: usize = 8;    pub const InitError = error{CapacityOverflow} || Allocator.Error;    pub const Exhaustion = ExhaustionType;    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 1,        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "ui.style_engine",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{ .id = "computed_style_pools", .lifetime = .steady, .detail = "two bounded record pools, value hash slots, and sixteen-byte sharing keys" },                    .{ .id = "node_style_generations", .lifetime = .steady, .detail = "two node-state arrays and invalidation occupancy words" },                    .{ .id = "rule_buckets_and_invalidation", .lifetime = .steady, .detail = "rule entries, match scratch, flags, and style remap" },                    .{ .id = "authored_font_chains", .lifetime = .steady, .detail = "two bounded chains with one font slot per configured face and distinct style" },                },                .excluded = &.{                    "the caller-owned stylesheet source and parsed sheet placement",                    "the retained publish envelope owned by ui.publish_store",                },            },            .capacity = .{                .inputs = &.{alloc_phase.capacity.bindInput(LimitsType, "bytes", "bytes")},                .type_selectors = &.{},                .nodes = &.{.{ .input = 0 }},                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 0,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "Capacity.derive rejects an undersized byte budget, font binding rejects a face quota above the chain slots, and a cascade above the distinct style quota leaves the live pool sealed",            },            .risks = .{                .transitive = .{ .status = .open, .detail = "the matcher and resolver accept no allocator, while a machine certificate of their call graphs remains open" },                .foreign = .{ .status = .excluded, .detail = "cascade and lowering call no foreign runtime" },            },            .obligations = &.{                .{ .key = "ui_style_capacity", .role = .capacity_model },                .{ .key = "ui_style_acquisition", .role = .acquisition },                .{ .key = "ui_style_sharing_quota", .role = .overload },                .{ .key = "ui_style_font_chain", .role = .overload },                .{ .key = "ui_style_oom", .role = .initialization_failure },                .{ .key = "ui_style_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,    capacity: CapacityType,    bytes: []align(8) u8,    limits: LimitsType,    states: [2][]NodeState,    key_index: [2][]u64,    key_bits: [2][]u16,    touched: []u64,    flags: []u8,    entries: []css.bucket.Entry,    matches: []css.match.Match,    pools: [2]share.Pool,    remap: []u32,    live: u1 = 0,    pool_live: u1 = 0,    style_epoch: u64 = 0,    count: u32 = 0,    node_buffer_addr: usize = 0,    view_revision: u64 = 0,    last_view: tree.View = .{},    selector_count: u32 = 0,    stylesheet_generation: u32 = 0,    sheet: ?*const css.StyleSheet = null,    summary: css.bucket.Summary = .{},    selector_key_bits: u16 = 0,    universal_selector: bool = false,    custom_sheet: bool = false,    direct_hover: bool = false,    hover_filter_enabled: bool = true,    interaction: element.Interaction = .{},    metrics: computed.Metrics = .{},    fonts: ?*const asset.Registry = null,    font_revision: u64 = 0,    applied_font_revision: u64 = 0,    applied_font_epoch: u64 = 0,    pub fn init(allocator: Allocator, limits: LimitsType) InitError!Engine {        const capacity = try CapacityType.derive(limits);        const bytes = try allocator.alignedAlloc(u8, .of(u64), capacity.total_bytes);        errdefer allocator.free(bytes);        var arena = std.heap.FixedBufferAllocator.init(bytes);        const a = arena.allocator();        const states_a = a.alloc(NodeState, limits.nodes) catch unreachable;        const states_b = a.alloc(NodeState, limits.nodes) catch unreachable;        const word_count = std.math.divCeil(usize, limits.nodes, 64) catch unreachable;        const key_index_a = a.alloc(u64, key_columns * word_count) catch unreachable;        const key_index_b = a.alloc(u64, key_columns * word_count) catch unreachable;        const key_bits_a = a.alloc(u16, limits.nodes) catch unreachable;        const key_bits_b = a.alloc(u16, limits.nodes) catch unreachable;        const touched = a.alloc(u64, word_count) catch unreachable;        const flags = a.alloc(u8, limits.nodes) catch unreachable;        const entries = a.alloc(css.bucket.Entry, limits.selectors) catch unreachable;        const matches = a.alloc(css.match.Match, limits.selectors) catch unreachable;        const records_a = a.alloc(computed.Record, limits.distinct_styles) catch unreachable;        const records_b = a.alloc(computed.Record, limits.distinct_styles) catch unreachable;        const chain_slots = @as(usize, limits.distinct_styles) * limits.font_faces;        const chains_a = a.alloc(asset.AssetHandle, chain_slots) catch unreachable;        const chains_b = a.alloc(asset.AssetHandle, chain_slots) catch unreachable;        const slot_count = std.math.ceilPowerOfTwo(usize, @as(usize, limits.distinct_styles) * 2) catch unreachable;        const slots_a = a.alloc(u32, slot_count) catch unreachable;        const slots_b = a.alloc(u32, slot_count) catch unreachable;        const key_count = std.math.ceilPowerOfTwo(usize, limits.distinct_styles) catch unreachable;        const keys_a = a.alloc(share.KeySlot, key_count) catch unreachable;        const keys_b = a.alloc(share.KeySlot, key_count) catch unreachable;        const remap = a.alloc(u32, limits.distinct_styles) catch unreachable;        @memset(slots_a, 0);        @memset(slots_b, 0);        return .{            .phase = .initialization,            .capacity = capacity,            .bytes = bytes,            .limits = limits,            .states = .{ states_a, states_b },            .key_index = .{ key_index_a, key_index_b },            .key_bits = .{ key_bits_a, key_bits_b },            .touched = touched,            .flags = flags,            .entries = entries,            .matches = matches,            .pools = .{                .{ .records = records_a, .slots = slots_a, .keys = keys_a, .chains = chains_a, .font_faces = limits.font_faces },                .{ .records = records_b, .slots = slots_b, .keys = keys_b, .chains = chains_b, .font_faces = limits.font_faces },            },            .remap = remap,        };    }    pub fn activate(self: *Engine) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.bytes.len == self.capacity.storage_bytes);        self.phase = .steady;    }    /// The registry outlives the engine. A changed registry epoch recomputes    /// every style that may hold a resolved face handle.    pub fn setFonts(self: *Engine, registry: ?*const asset.Registry) error{FontChainQuota}!void {        if (registry) |value| {            if (value.limits.fonts > self.limits.font_faces) return error.FontChainQuota;        }        self.fonts = registry;        self.font_revision +%= 1;    }    pub fn authoredFallbacks(self: *const Engine, node: u32) []const asset.AssetHandle {        const text = self.record(node).text;        return self.pools[self.pool_live].chains[text.fallback_first..][0..text.fallback_count];    }    pub fn deinit(self: *Engine, allocator: Allocator) void {        std.debug.assert(self.phase != .teardown);        self.phase = .teardown;        allocator.free(self.bytes);        self.bytes = &.{};    }    pub fn record(self: *const Engine, node: u32) *const computed.Record {        std.debug.assert(node < self.count);        return self.pools[self.pool_live].get(self.states[self.live][node].style & style_mask);    }    /// Returns the interned computed Record ID for an admitted node.    /// Two nodes sharing the same computed style have the same ID within one style epoch.    pub fn styleId(self: *const Engine, node: u32) u32 {        std.debug.assert(node < self.count);        return self.states[self.live][node].style & style_mask;    }    /// Returns a u64 counter that advances only when a full cascade flips the    /// computed-style pool and may renumber IDs: a fast cascade that appends in    /// the current pool keeps the epoch. A cache retained across cascades keys    /// by both epoch and ID, while a script fallback used only within one layout    /// pass can key by ID alone.    pub fn styleEpoch(self: *const Engine) u64 {        std.debug.assert(self.phase == .steady);        return self.style_epoch;    }    fn oldAt(self: *const Engine, id: u64) ?NodeState {        for (self.states[self.live][0..self.count]) |state| {            if (state.id == id) return state;        }        return null;    }    fn oldAtIndex(self: *const Engine, view: tree.View, at: u32) ?NodeState {        if (self.count == view.nodes.len and self.node_buffer_addr == @intFromPtr(view.nodes.ptr) and            self.view_revision == view.header.revision and self.states[self.live][at].id == view.nodes[at].id)        {            return self.states[self.live][at];        }        if (view.prior.len == view.nodes.len) {            const prior = view.prior[at] & ~tree.prior_dirty;            if (prior != tree.prior_absent and prior < self.count) return self.states[self.live][prior];            return null;        }        if (at < self.count and self.states[self.live][at].id == view.nodes[at].id) {            return self.states[self.live][at];        }        return self.oldAt(view.nodes[at].id);    }    fn classBloom(adapter: *const element.Adapter, index: u32) u64 {        var result: u64 = 0;        for (adapter.view.classesOf(index)) |atom| {            const token = adapter.sheet.token(adapter.view.text(atom));            result |= @as(u64, 1) << @truncate(token);        }        return result;    }    fn sameMatchInputs(before: tree.View, old: u32, after: tree.View, at: u32) bool {        const left = before.nodes[old];        const right = after.nodes[at];        if (left.kind != right.kind or left.role != right.role or left.state != right.state or            left.flags != right.flags or (old == 0) != (at == 0) or            (left.subtree_count == 0 and left.text == abi.text_absent) !=                (right.subtree_count == 0 and right.text == abi.text_absent)) return false;        if (old != 0 and before.nodes[left.parent].id != after.nodes[right.parent].id) return false;        if (!std.mem.eql(u8, before.text(left.identifier), after.text(right.identifier))) return false;        const old_classes = before.classesOf(old);        const new_classes = after.classesOf(at);        if (old_classes.len != new_classes.len) return false;        for (old_classes, new_classes) |left_class, right_class| {            if (!std.mem.eql(u8, before.text(left_class), after.text(right_class))) return false;        }        return std.mem.eql(u8, std.mem.sliceAsBytes(before.declarationsOf(old)), std.mem.sliceAsBytes(after.declarationsOf(at)));    }    fn tokenHit(word: u64, token: u32) bool {        return token != 0 and word & (@as(u64, 1) << @truncate(token)) != 0;    }    fn hoverAncestorKeyHit(self: *const Engine, callbacks: css.match.Element, index: u32, classes: u64) bool {        const summary = self.summary;        if (summary.ancestor_kinds == 0 and summary.ancestor_roles == 0 and            summary.ancestor_classes == 0 and summary.ancestor_ids == 0) return true;        return classes & summary.ancestor_classes != 0 or            tokenHit(summary.ancestor_kinds, callbacks.kind(callbacks.context, index)) or            tokenHit(summary.ancestor_roles, callbacks.role(callbacks.context, index)) or            tokenHit(summary.ancestor_ids, callbacks.identifier(callbacks.context, index));    }    fn nodeKeys(view: tree.View, at: u32) u16 {        const node = view.nodes[at];        const kind: abi.Kind = @fromBackingInt(@intCast(node.kind));        const role: abi.Role = @fromBackingInt(@intCast(node.role));        var bits = bucketBit(.kind, @tagName(kind)) | bucketBit(.role, @tagName(role));        if (node.identifier != 0) bits |= bucketBit(.identifier, view.text(node.identifier));        for (view.classesOf(at)) |class| bits |= bucketBit(.class, view.text(class));        return bits;    }    fn putKeyBits(self: *Engine, target: u1, at: u32, bits: u16) void {        const stride = std.math.divCeil(usize, self.limits.nodes, 64) catch unreachable;        self.key_bits[target][at] = bits;        for (0..key_columns) |column| {            if (bits & (@as(u16, 1) << @intCast(column)) != 0) {                self.key_index[target][column * stride + at / 64] |= @as(u64, 1) << @intCast(at % 64);            }        }    }    fn updateKeyBits(self: *Engine, view: tree.View, at: u32) void {        const bits = nodeKeys(view, at);        const old = self.key_bits[self.live][at];        if (bits == old) return;        const stride = std.math.divCeil(usize, self.limits.nodes, 64) catch unreachable;        for (0..key_columns) |column| {            const key = @as(u16, 1) << @intCast(column);            if ((old ^ bits) & key == 0) continue;            const word = &self.key_index[self.live][column * stride + at / 64];            const member = @as(u64, 1) << @intCast(at % 64);            if (bits & key != 0) word.* |= member else word.* &= ~member;        }        self.key_bits[self.live][at] = bits;    }    fn markBucketKeys(self: *Engine, count: usize, bits: u16) void {        const stride = std.math.divCeil(usize, self.limits.nodes, 64) catch unreachable;        for (0..key_columns) |column| {            if (bits & (@as(u16, 1) << @intCast(column)) == 0) continue;            for (0..std.math.divCeil(usize, count, 64) catch unreachable) |word| {                var members = self.key_index[self.live][column * stride + word];                while (members != 0) {                    const bit: usize = @intCast(@ctz(members));                    self.flags[word * 64 + bit] = 1;                    members &= members - 1;                }            }        }    }    fn markFastBucketKeys(self: *Engine, target: u1, count: usize, bits: u16, universal: bool) void {        if (universal) {            for (0..count) |at| self.markFast(@intCast(at));            return;        }        const stride = std.math.divCeil(usize, self.limits.nodes, 64) catch unreachable;        for (0..key_columns) |column| {            if (bits & (@as(u16, 1) << @intCast(column)) == 0) continue;            for (0..std.math.divCeil(usize, count, 64) catch unreachable) |word| {                var members = self.key_index[target][column * stride + word];                while (members != 0) {                    self.markFast(@intCast(word * 64 + @ctz(members)));                    members &= members - 1;                }            }        }    }    fn markSubtree(self: *Engine, view: tree.View, index: u32) void {        const end = index + view.nodes[index].subtree_count + 1;        @memset(self.flags[index..end], 1);    }    fn countGroup(index: css.bucket.Index, group: []const css.bucket.Entry, result: *Result) void {        result.candidates += group.len;        for (group) |entry| {            const selector = index.source.rules[entry.rule].selectors[entry.selector];            result.combinator_candidates += @intFromBool(selector.combinators.len != 0);        }    }    fn countCandidates(index: css.bucket.Index, callbacks: css.match.Element, node: u32, result: *Result) void {        countGroup(index, index.group(.universal, 0), result);        const kind = callbacks.kind(callbacks.context, node);        if (kind != 0) countGroup(index, index.group(.kind, kind), result);        const role = callbacks.role(callbacks.context, node);        if (role != 0) countGroup(index, index.group(.role, role), result);        const identifier = callbacks.identifier(callbacks.context, node);        if (identifier != 0) countGroup(index, index.group(.identifier, identifier), result);        var held: [css.match.max_element_classes]u32 = undefined;        const count = callbacks.classes(callbacks.context, node, &held, css.match.max_element_classes);        for (held[0..count]) |class| {            if (class != 0) countGroup(index, index.group(.class, class), result);        }    }    fn markInteraction(self: *Engine, adapter: *const element.Adapter, id: u64, class: css.selector.PseudoClass) void {        if (id == 0) return;        const found = adapter.find(id) orelse return;        var at = found;        while (true) {            const old = self.oldAt(adapter.view.nodes[at].id);            const sensitive = if (class == .hover and self.hover_filter_enabled) if (old) |state| state.style & hover_sensitive != 0 else true else true;            if (sensitive) {                self.flags[at] = 1;                if (self.summary.ancestorPseudoSensitive(class) or                    (class == .focus and self.summary.ancestorPseudoSensitive(.focus_within)))                {                    self.markSubtree(adapter.view, at);                }            }            if (at == 0) break;            at = adapter.view.nodes[at].parent;        }    }    fn markFast(self: *Engine, at: u32) void {        const word = at / 64;        const bit = @as(u64, 1) << @intCast(at % 64);        if (self.touched[word] & bit == 0) self.flags[at] = 1;        self.touched[word] |= bit;    }    fn markFastSubtree(self: *Engine, view: tree.View, at: u32) void {        const end = at + view.nodes[at].subtree_count + 1;        var index = at;        while (index < end) : (index += 1) self.markFast(index);    }    fn markFastDescendants(self: *Engine, view: tree.View, at: u32) void {        const end = at + view.nodes[at].subtree_count + 1;        var index = at + 1;        while (index < end) : (index += 1) self.markFast(index);    }    fn markFastChildren(self: *Engine, view: tree.View, at: u32) void {        const end = at + view.nodes[at].subtree_count + 1;        var index = at + 1;        while (index < end) : (index += view.nodes[index].subtree_count + 1) self.markFast(index);    }    fn findInView(view: tree.View, id: u64) ?u32 {        if (id == 0) return null;        if (view.identity) |identity| return identity.lookup(view.nodes, id);        for (view.nodes, 0..) |node, at| if (node.id == id) return @intCast(at);        return null;    }    fn chain(view: tree.View, id: u64, held: *[65]u32) []const u32 {        var at = findInView(view, id) orelse return held[0..0];        var used: usize = 0;        while (true) {            std.debug.assert(used < held.len);            held[used] = at;            used += 1;            if (at == 0) break;            at = view.nodes[at].parent;        }        return held[0..used];    }    fn markFastPseudoNode(self: *Engine, adapter: *element.Adapter, at: u32, class: css.selector.PseudoClass) void {        const sensitive = class != .hover or !self.hover_filter_enabled or self.direct_hover or            (self.summary.ancestorPseudoSensitive(.hover) and                self.hoverAncestorKeyHit(adapter.element(), at, classBloom(adapter, at)));        if (!sensitive) return;        self.markFast(at);        if (self.summary.ancestorPseudoSensitive(class) or            (class == .focus and self.summary.ancestorPseudoSensitive(.focus_within)))        {            self.markFastSubtree(adapter.view, at);        }    }    fn markFastTransition(self: *Engine, adapter: *element.Adapter, old_id: u64, new_id: u64, class: css.selector.PseudoClass) void {        var old_buffer: [65]u32 = undefined;        var new_buffer: [65]u32 = undefined;        const old_chain = chain(self.last_view, old_id, &old_buffer);        const new_chain = chain(adapter.view, new_id, &new_buffer);        if (class == .hover and !self.hover_filter_enabled) {            for (old_chain) |at| {                const current = adapter.find(self.last_view.nodes[at].id) orelse continue;                self.markFastPseudoNode(adapter, current, class);            }            for (new_chain) |at| self.markFastPseudoNode(adapter, at, class);            return;        }        var old_set = ChainSet{};        var new_set = ChainSet{};        for (old_chain) |at| old_set.insert(self.last_view.nodes[at].id);        for (new_chain) |at| new_set.insert(adapter.view.nodes[at].id);        for (old_chain) |at| {            const id = self.last_view.nodes[at].id;            if (new_set.contains(id)) continue;            const current = adapter.find(id) orelse continue;            self.markFastPseudoNode(adapter, current, class);        }        for (new_chain) |at| {            if (old_set.contains(adapter.view.nodes[at].id)) continue;            self.markFastPseudoNode(adapter, at, class);        }    }    fn fastCascade(        self: *Engine,        view: tree.View,        dirty: []const u32,        sheet: *const css.StyleSheet,        generation: u32,        interaction: element.Interaction,        index: css.bucket.Index,        metrics: computed.Metrics,        font_environment: ?computed.FontSource,        carry: bool,        carried_count: u32,        changed_selector_bits: u16,        changed_universal: bool,        ancestor_classes: u64,    ) Error!Result {        const words = std.math.divCeil(usize, view.nodes.len, 64) catch unreachable;        if (!carry) @memset(self.touched[0..words], 0);        if (changed_selector_bits != 0 or changed_universal) {            self.markFastBucketKeys(if (carry) self.live ^ 1 else self.live, view.nodes.len, changed_selector_bits, changed_universal);        }        var adapter = element.Adapter{ .view = view, .sheet = sheet, .interaction = interaction };        for (dirty) |at| {            const old_index = if (carry) view.prior[at] & ~tree.prior_dirty else tree.prior_absent;            if (!carry or old_index == tree.prior_absent or                !sameMatchInputs(self.last_view, old_index, view, at)) self.markFast(at);            const bloom = classBloom(&adapter, at);            const old = self.states[if (carry) self.live ^ 1 else self.live][at];            if ((old.ancestor_classes ^ bloom) & ancestor_classes != 0) {                self.markFastSubtree(view, at);            }            if (self.summary.sibling_sensitive or self.summary.positional) {                self.markFastChildren(view, at);                if (at != 0) self.markFastChildren(view, view.nodes[at].parent);            }        }        if (carry or interaction.hovered != self.interaction.hovered) {            self.markFastTransition(&adapter, self.interaction.hovered, interaction.hovered, .hover);        }        if (carry or interaction.focused != self.interaction.focused) {            self.markFastTransition(&adapter, self.interaction.focused, interaction.focused, .focus);        }        if (interaction.focused != self.interaction.focused) {            if (adapter.find(self.interaction.focused)) |old| self.markFastPseudoNode(&adapter, old, .focus);            if (adapter.find(interaction.focused)) |new| self.markFastPseudoNode(&adapter, new, .focus);        }        if (interaction.keyboard_focus != self.interaction.keyboard_focus) {            if (adapter.find(interaction.focused)) |focused| self.markFastPseudoNode(&adapter, focused, .focus_visible);        }        if (carry or interaction.active != self.interaction.active) {            self.markFastTransition(&adapter, self.interaction.active, interaction.active, .active);        }        const stage: u1 = self.live ^ 1;        const pool = &self.pools[self.pool_live];        var result = Result{ .carried = carried_count, .carried_bytes = @as(u64, carried_count) * @sizeOf(NodeState) };        var resolver = css.cascade.Resolver{};        for (0..words) |word| {            var processed: u64 = 0;            while (self.touched[word] & ~processed != 0) {                const bit: u6 = @intCast(@ctz(self.touched[word] & ~processed));                const at: u32 = @intCast(word * 64 + bit);                processed |= @as(u64, 1) << bit;                self.flags[at] = 0;                result.visited += 1;                const node = view.nodes[at];                const prior = self.states[if (carry) stage else self.live][at];                const bloom = classBloom(&adapter, at);                const parent_style: u32 = if (at == 0) 0 else blk: {                    const parent = node.parent;                    if (carry) break :blk self.states[stage][parent].style & style_mask;                    const parent_bit = @as(u64, 1) << @intCast(parent % 64);                    const staged = self.touched[parent / 64] & parent_bit != 0 and self.flags[parent] == 0;                    break :blk (if (staged) self.states[stage][parent].style else self.states[self.live][parent].style) & style_mask;                };                const callbacks = adapter.element();                countCandidates(index, callbacks, at, &result);                const before_steps = adapter.ancestor_steps;                const matched = css.bucket.matchElement(index, callbacks, at, self.matches);                if (matched.rejected != 0) return error.MatchedRuleQuota;                result.ancestor_walks += adapter.ancestor_steps - before_steps;                var rule_hash: u32 = 2166136261;                for (self.matches[0..matched.count]) |item| {                    rule_hash = (rule_hash ^ item.rule) *% 16777619;                    rule_hash = (rule_hash ^ item.selector) *% 16777619;                }                const key = share.Key{                    .matched_rules = rule_hash,                    .inline_first = node.declaration_first,                    .inline_count = node.declaration_count,                    .parent_style = parent_style,                };                const sensitive = self.direct_hover or (self.summary.ancestorPseudoSensitive(.hover) and                    self.hoverAncestorKeyHit(callbacks, at, bloom));                resolver.begin(sheet.source, &sheet.atoms, null);                for (self.matches[0..matched.count]) |item| {                    const rule = sheet.rules[item.rule];                    for (rule.lowered) |declaration| resolver.add(declaration, .author, item.specificity, item.order);                }                for (view.declarationsOf(at)) |declaration| resolver.add(declaration, .author, css.Specificity.inlineStyle(), std.math.maxInt(u32));                const inherited = if (parent_style == 0) null else &pool.get(parent_style).computed;                var style: css.cascade.Computed = undefined;                resolver.finish(inherited, &style);                const style_id = try pool.intern(key, self.matches[0..matched.count], style, metrics, font_environment);                self.states[stage][at] = .{                    .id = node.id,                    .ancestor_classes = bloom,                    .style = style_id | (if (sensitive) hover_sensitive else 0),                    .computed_generation = generation,                };                result.restyled += 1;                if (prior.style & style_mask == 0 or !std.mem.eql(u8, std.mem.asBytes(&pool.get(prior.style & style_mask).computed), std.mem.asBytes(&pool.get(style_id).computed))) {                    self.markFastDescendants(view, at);                }            }        }        if (carry) {            self.live = stage;            self.count = @intCast(view.nodes.len);            self.node_buffer_addr = @intFromPtr(view.nodes.ptr);            self.view_revision = view.header.revision;        } else {            for (self.touched[0..words], 0..) |held, word| {                var members = held;                while (members != 0) {                    const bit: u6 = @intCast(@ctz(members));                    const at = word * 64 + bit;                    self.states[self.live][at] = self.states[stage][at];                    self.updateKeyBits(view, @intCast(at));                    members &= members - 1;                }            }        }        self.interaction = interaction;        self.last_view = view;        return result;    }    pub fn cascade(        self: *Engine,        store: *const tree.Store,        sheet: *const css.StyleSheet,        generation: u32,        interaction: element.Interaction,    ) Error!Result {        std.debug.assert(self.phase == .steady);        return self.cascadeView(store.retained(), store.dirty(), sheet, generation, interaction);    }    pub fn cascadeView(        self: *Engine,        view: tree.View,        dirty: []const u32,        sheet: *const css.StyleSheet,        generation: u32,        interaction: element.Interaction,    ) Error!Result {        std.debug.assert(self.phase == .steady);        if (view.nodes.len > self.limits.nodes) return error.CapacityOverflow;        const selector_count = css.bucket.Limits.inspect(sheet).entries;        if (selector_count > self.entries.len) return error.SelectorQuota;        var adapter = element.Adapter{ .view = view, .sheet = sheet, .interaction = interaction };        const metrics = computed.Metrics{            .root_font_size = view.header.root_font_size,            .viewport_width = view.header.viewport.width,            .viewport_height = view.header.viewport.height,        };        const changed_sheet = self.sheet != sheet or self.stylesheet_generation != generation;        const changed_fonts = self.applied_font_revision != self.font_revision or            (if (self.fonts) |registry| self.applied_font_epoch != registry.epoch else false);        const changed_inputs = !std.meta.eql(self.metrics, metrics) or changed_fonts or (changed_sheet and self.fonts != null);        const font_environment: ?computed.FontSource = if (self.fonts) |registry|            .{ .registry = registry, .source = sheet.source }        else            null;        const previous_summary = self.summary;        const previous_hover = self.direct_hover;        const previous_custom = self.custom_sheet;        errdefer if (changed_sheet) {            self.summary = previous_summary;            self.direct_hover = previous_hover;            self.custom_sheet = previous_custom;            if (self.sheet) |retained_sheet| _ = css.bucket.build(self.entries, retained_sheet);        };        const index: css.bucket.Index = if (changed_sheet) css.bucket.build(self.entries, sheet) else .{ .entries = self.entries[0..self.selector_count], .source = sheet };        const keys: SelectorKeys = if (changed_sheet) selectorKeys(index) else .{ .bits = self.selector_key_bits, .universal = self.universal_selector };        if (changed_sheet) {            self.summary = css.bucket.summarize(sheet);            self.direct_hover = false;            self.custom_sheet = false;            for (sheet.rules) |rule| {                if (rule.customs.len != 0) self.custom_sheet = true;                for (rule.selectors) |selector| {                    if (selector.rightmost().pseudo & css.selector.bit(.hover) != 0) self.direct_hover = true;                }            }        }        const changed_selector_bits = if (changed_sheet) self.selector_key_bits | keys.bits else @as(u16, 0);        const changed_universal = changed_sheet and (self.universal_selector or keys.universal);        const ancestor_classes = previous_summary.ancestor_classes | self.summary.ancestor_classes;        const complete_refresh = blk: {            if (dirty.len != view.nodes.len) break :blk false;            for (dirty, 0..) |at, index_at| if (at != index_at) break :blk false;            break :blk true;        };        if (!complete_refresh and self.count != 0 and !changed_inputs and !self.custom_sheet and !previous_custom and            self.count == view.nodes.len and self.node_buffer_addr == @intFromPtr(view.nodes.ptr) and            self.view_revision == view.header.revision)        {            var stable_ids = true;            for (dirty) |at| {                if (at >= self.count or self.states[self.live][at].id != view.nodes[at].id) {                    stable_ids = false;                    break;                }            }            if (stable_ids) {                const retained_styles = self.pools[self.pool_live].used;                if (self.fastCascade(view, dirty, sheet, generation, interaction, index, metrics, font_environment, false, 0, changed_selector_bits, changed_universal, ancestor_classes)) |fast| {                    self.sheet = sheet;                    self.stylesheet_generation = generation;                    self.selector_key_bits = keys.bits;                    self.universal_selector = keys.universal;                    self.selector_count = @intCast(index.entries.len);                    return fast;                } else |err| {                    self.pools[self.pool_live].truncate(retained_styles);                    @memset(self.touched[0 .. std.math.divCeil(usize, view.nodes.len, 64) catch unreachable], 0);                    switch (err) {                        error.DistinctStyleQuota => {},                        else => return err,                    }                }            }        }        if (!complete_refresh and self.count != 0 and !changed_inputs and !self.custom_sheet and !previous_custom and            view.prior.len == view.nodes.len and self.last_view.nodes.len == self.count and            (view.header.base_revision == 0 or view.header.base_revision == self.view_revision) and            (self.node_buffer_addr != @intFromPtr(view.nodes.ptr) or self.view_revision != view.header.revision))        {            const stage: u1 = self.live ^ 1;            @memset(self.touched[0 .. std.math.divCeil(usize, view.nodes.len, 64) catch unreachable], 0);            @memset(self.key_index[stage], 0);            var carried: u32 = 0;            var valid = true;            for (view.nodes, 0..) |node, at| {                const old = view.prior[at] & ~tree.prior_dirty;                if (old == tree.prior_absent) {                    self.states[stage][at] = .{ .id = node.id };                    self.putKeyBits(stage, @intCast(at), nodeKeys(view, @intCast(at)));                    continue;                }                if (old >= self.count or self.states[self.live][old].id != node.id) {                    valid = false;                    break;                }                self.states[stage][at] = self.states[self.live][old];                const bits = if (view.prior[at] & tree.prior_dirty != 0)                    nodeKeys(view, @intCast(at))                else                    self.key_bits[self.live][old];                self.putKeyBits(stage, @intCast(at), bits);                carried += 1;                if (at != 0 and self.last_view.nodes[old].parent < self.last_view.nodes.len and                    self.last_view.nodes[self.last_view.nodes[old].parent].id != view.nodes[node.parent].id)                {                    self.markFastSubtree(view, @intCast(at));                    if (self.summary.sibling_sensitive or self.summary.positional) {                        self.markFastChildren(view, node.parent);                        const old_parent_id = self.last_view.nodes[self.last_view.nodes[old].parent].id;                        if (view.identity) |identity| {                            if (identity.lookup(view.nodes, old_parent_id)) |old_parent| {                                self.markFastChildren(view, old_parent);                            }                        }                    }                }            }            if (valid) {                const retained_styles = self.pools[self.pool_live].used;                if (self.fastCascade(view, dirty, sheet, generation, interaction, index, metrics, font_environment, true, carried, changed_selector_bits, changed_universal, ancestor_classes)) |fast| {                    self.sheet = sheet;                    self.stylesheet_generation = generation;                    self.selector_key_bits = keys.bits;                    self.universal_selector = keys.universal;                    self.selector_count = @intCast(index.entries.len);                    return fast;                } else |err| {                    self.pools[self.pool_live].truncate(retained_styles);                    @memset(self.touched[0 .. std.math.divCeil(usize, view.nodes.len, 64) catch unreachable], 0);                    switch (err) {                        error.DistinctStyleQuota => {},                        else => return err,                    }                }            }        }        @memset(self.flags[0..view.nodes.len], if (self.count == 0 or changed_inputs or            self.custom_sheet or (changed_sheet and (self.universal_selector or keys.universal or            self.node_buffer_addr != @intFromPtr(view.nodes.ptr) or self.view_revision != view.header.revision))) 1 else 0);        if (changed_sheet and self.count != 0 and !changed_inputs and !self.universal_selector and !keys.universal and            self.node_buffer_addr == @intFromPtr(view.nodes.ptr) and self.view_revision == view.header.revision)        {            self.markBucketKeys(view.nodes.len, self.selector_key_bits | keys.bits);        }        for (dirty) |at| self.flags[at] = 1;        if (self.summary.sibling_sensitive or self.summary.positional) {            for (dirty) |at| {                const parent = view.nodes[at].parent;                self.markSubtree(view, parent);            }        }        if (interaction.hovered != self.interaction.hovered) {            self.markInteraction(&adapter, self.interaction.hovered, .hover);            self.markInteraction(&adapter, interaction.hovered, .hover);        }        if (interaction.focused != self.interaction.focused or interaction.keyboard_focus != self.interaction.keyboard_focus) {            self.markInteraction(&adapter, self.interaction.focused, .focus);            self.markInteraction(&adapter, interaction.focused, .focus);        }        if (interaction.active != self.interaction.active) {            self.markInteraction(&adapter, self.interaction.active, .active);            self.markInteraction(&adapter, interaction.active, .active);        }        const next: u1 = self.live ^ 1;        const next_pool = &self.pools[self.pool_live ^ 1];        next_pool.reset();        @memset(self.key_index[next], 0);        @memset(self.remap, 0);        var result = Result{};        var resolver = css.cascade.Resolver{};        var custom_stack: [65]css.cascade.CustomTable = undefined;        var custom_nodes: [65]u32 = undefined;        var custom_depth: usize = 0;        for (view.nodes, 0..) |node, i| {            result.visited += 1;            const at: u32 = @intCast(i);            self.putKeyBits(next, at, nodeKeys(view, at));            const prior = self.oldAtIndex(view, at);            const bloom = classBloom(&adapter, at);            const parent_style: u32 = if (at == 0) 0 else self.states[next][node.parent].style & style_mask;            const ancestor_change = if (prior) |state| (state.ancestor_classes ^ bloom) & self.summary.ancestor_classes != 0 else false;            if (ancestor_change) self.markSubtree(view, at);            const same_view = self.count == view.nodes.len and self.node_buffer_addr == @intFromPtr(view.nodes.ptr) and                self.view_revision == view.header.revision;            const shifted = prior == null or if (same_view)                self.states[self.live][at].id != node.id            else if (view.prior.len == view.nodes.len)                (view.prior[at] & ~tree.prior_dirty) != at            else                at >= self.count or self.states[self.live][at].id != node.id;            const should_compute = changed_inputs or self.flags[at] != 0 or prior == null or shifted;            if (!should_compute) {                var same = prior.?;                const old_id = same.style & style_mask;                var new_id = self.remap[old_id - 1];                if (new_id == 0) {                    const inherited = self.pools[self.pool_live].get(old_id);                    new_id = try next_pool.intern(.{                        .matched_rules = 0,                        .inline_first = 0,                        .inline_count = 0,                        .parent_style = parent_style,                    }, &.{}, inherited.computed, metrics, font_environment);                    self.remap[old_id - 1] = new_id;                }                const old_hover = same.style & hover_sensitive;                const next_hover = if (changed_sheet)                    if (self.direct_hover or (self.summary.ancestorPseudoSensitive(.hover) and                        self.hoverAncestorKeyHit(adapter.element(), at, bloom))) hover_sensitive else @as(u32, 0)                else                    old_hover;                same.style = new_id | next_hover;                same.ancestor_classes = bloom;                same.computed_generation = generation;                self.states[next][at] = same;                result.shared += 1;                continue;            }            const callbacks = adapter.element();            countCandidates(index, callbacks, at, &result);            const before_steps = adapter.ancestor_steps;            const matched = css.bucket.matchElement(index, callbacks, at, self.matches);            if (matched.rejected != 0) return error.MatchedRuleQuota;            result.ancestor_walks += adapter.ancestor_steps - before_steps;            var rule_hash: u32 = 2166136261;            for (self.matches[0..matched.count]) |item| {                rule_hash = (rule_hash ^ item.rule) *% 16777619;                rule_hash = (rule_hash ^ item.selector) *% 16777619;            }            const key = share.Key{                .matched_rules = rule_hash,                .inline_first = node.declaration_first,                .inline_count = node.declaration_count,                .parent_style = parent_style,            };            const sensitive = self.direct_hover or (self.summary.ancestorPseudoSensitive(.hover) and                self.hoverAncestorKeyHit(callbacks, at, bloom));            if (if (self.custom_sheet) null else next_pool.lookup(key, self.matches[0..matched.count])) |style_id| {                self.states[next][at] = .{                    .id = node.id,                    .ancestor_classes = bloom,                    .style = style_id | (if (sensitive) hover_sensitive else 0),                    .computed_generation = generation,                };                result.restyled += 1;                result.shared += 1;                if (prior) |old| {                    const old_style = self.pools[self.pool_live].get(old.style & style_mask);                    const new_style = next_pool.get(style_id);                    if (!std.mem.eql(u8, std.mem.asBytes(&old_style.computed), std.mem.asBytes(&new_style.computed))) {                        self.markSubtree(view, at);                    }                }                continue;            }            var parent_customs: ?*const css.cascade.CustomTable = null;            if (self.custom_sheet and at != 0) {                while (custom_depth > 0 and custom_nodes[custom_depth - 1] != node.parent) custom_depth -= 1;                std.debug.assert(custom_depth > 0);                parent_customs = &custom_stack[custom_depth - 1];            }            resolver.begin(sheet.source, &sheet.atoms, parent_customs);            for (self.matches[0..matched.count]) |item| {                const rule = sheet.rules[item.rule];                for (rule.lowered) |declaration| resolver.add(declaration, .author, item.specificity, item.order);                for (rule.customs) |custom| resolver.addCustom(custom, .author, item.specificity, item.order);            }            for (view.declarationsOf(at)) |declaration| resolver.add(declaration, .author, css.Specificity.inlineStyle(), std.math.maxInt(u32));            const inherited = if (parent_style == 0) null else &next_pool.get(parent_style).computed;            var style: css.cascade.Computed = undefined;            resolver.finish(inherited, &style);            if (self.custom_sheet) {                if (custom_depth == custom_stack.len) return error.CapacityOverflow;                custom_stack[custom_depth] = resolver.customs;                custom_nodes[custom_depth] = at;                custom_depth += 1;            }            const style_id = try next_pool.intern(key, self.matches[0..matched.count], style, metrics, font_environment);            self.states[next][at] = .{                .id = node.id,                .ancestor_classes = bloom,                .style = style_id | (if (sensitive) hover_sensitive else 0),                .computed_generation = generation,            };            result.restyled += 1;            if (prior) |old| {                const old_style = self.pools[self.pool_live].get(old.style & style_mask);                if (!std.mem.eql(u8, std.mem.asBytes(&old_style.computed), std.mem.asBytes(&style))) {                    self.markSubtree(view, at);                }            }        }        self.live = next;        self.pool_live ^= 1;        std.debug.assert(self.style_epoch < std.math.maxInt(u64));        self.style_epoch += 1;        self.count = @intCast(view.nodes.len);        self.node_buffer_addr = @intFromPtr(view.nodes.ptr);        self.view_revision = view.header.revision;        self.last_view = view;        self.selector_count = @intCast(index.entries.len);        self.sheet = sheet;        self.stylesheet_generation = generation;        self.selector_key_bits = keys.bits;        self.universal_selector = keys.universal;        self.interaction = interaction;        self.metrics = metrics;        self.applied_font_revision = self.font_revision;        self.applied_font_epoch = if (self.fonts) |registry| registry.epoch else 0;        return result;    }};comptime {    alloc_phase.capacity.requireAllocatorExactOwnerShape(Engine);}test "incremental cascade agrees with a cold cascade after one dirty node" {    const allocator = std.testing.allocator;    var workspace = css.Workspace.init(allocator);    defer workspace.deinit();    const sheet = try workspace.parse("box { color: #ff0000; } text { color: #0000ff; }", .default());    const nodes = [_]@import("../abi/root.zig").Node{        .{ .id = 1, .revision = 1, .kind = @backingInt(@import("../abi/root.zig").Kind.box), .subtree_count = 1 },        .{ .id = 2, .revision = 1, .kind = @backingInt(@import("../abi/root.zig").Kind.text), .parent = 0 },    };    const view = tree.View{ .header = .{ .root_font_size = 16 }, .nodes = &nodes };    const limits = Limits{ .nodes = 2, .distinct_styles = 4, .selectors = 4, .bytes = 16 * 1024 };    var incremental = try Engine.init(allocator, limits);    defer incremental.deinit(allocator);    incremental.activate();    const first = try incremental.cascadeView(view, &.{ 0, 1 }, &sheet, 1, .{});    try std.testing.expectEqual(@as(u32, 2), first.restyled);    const next = try incremental.cascadeView(view, &.{1}, &sheet, 1, .{});    try std.testing.expectEqual(@as(u32, 1), next.restyled);    var cold = try Engine.init(allocator, limits);    defer cold.deinit(allocator);    cold.activate();    _ = try cold.cascadeView(view, &.{ 0, 1 }, &sheet, 1, .{});    for (nodes, 0..) |_, at| {        try std.testing.expect(std.meta.eql(incremental.record(@intCast(at)).computed, cold.record(@intCast(at)).computed));    }}test "stylesheet generation visits only indexed bucket keys" {    const allocator = std.testing.allocator;    var first_workspace = css.Workspace.init(allocator);    defer first_workspace.deinit();    var second_workspace = css.Workspace.init(allocator);    defer second_workspace.deinit();    const first = try first_workspace.parse(".alpha { color: red; }", .default());    const second = try second_workspace.parse(".beta { color: blue; }", .default());    var nodes: [128]abi.Node = undefined;    nodes[0] = .{ .id = 1, .subtree_count = 127 };    for (1..nodes.len) |at| nodes[at] = .{ .id = @intCast(at + 1), .parent = 0 };    nodes[1].class_first = 0;    nodes[1].class_count = 1;    nodes[2].class_first = 1;    nodes[2].class_count = 1;    const classes = [_]u32{ 1, 2 };    const atoms = [_]abi.Atom{ .{}, .{ .offset = 0, .len = 5 }, .{ .offset = 5, .len = 4 } };    const view = tree.View{ .nodes = &nodes, .classes = &classes, .atoms = &atoms, .strings = "alphabeta" };    var incremental = try Engine.init(allocator, .{ .nodes = 128, .distinct_styles = 8, .selectors = 4, .bytes = 64 * 1024 });    defer incremental.deinit(allocator);    incremental.activate();    _ = try incremental.cascadeView(view, &.{}, &first, 1, .{});    const changed = try incremental.cascadeView(view, &.{}, &second, 2, .{});    try std.testing.expectEqual(@as(u32, 2), changed.visited);    try std.testing.expectEqual(@as(u32, 2), changed.restyled);    var cold = try Engine.init(allocator, .{ .nodes = 128, .distinct_styles = 8, .selectors = 4, .bytes = 64 * 1024 });    defer cold.deinit(allocator);    cold.activate();    _ = try cold.cascadeView(view, &.{}, &second, 2, .{});    for (nodes, 0..) |_, at| {        try std.testing.expect(std.meta.eql(incremental.record(@intCast(at)).computed, cold.record(@intCast(at)).computed));    }}test "ui_style_capacity derives the caller bound" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_capacity"), null, null, null, null, null, null);    }    const capacity = try Capacity.derive(.{});    try std.testing.expectEqual(@as(usize, 2_097_152), capacity.total_bytes);    try std.testing.expect(capacity.required_bytes <= capacity.total_bytes);}test "ui_style_acquisition and ui_style_teardown own one block" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_acquisition"), null, null, null, null, null, null);        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_teardown"), null, null, null, null, null, null);    }    var engine = try Engine.init(std.testing.allocator, .{});    try std.testing.expectEqual(@as(usize, 2_097_152), engine.bytes.len);    engine.activate();    engine.deinit(std.testing.allocator);    try std.testing.expectEqual(alloc_phase.capacity.Phase.teardown, engine.phase);}fn openAndClose(allocator: Allocator, limits: LimitsType) !void {    var engine = try Engine.init(allocator, limits);    engine.activate();    engine.deinit(allocator);}test "ui_style_oom refuses acquisition without a partial block" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_oom"), null, null, null, null, null, null);    }    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });    try std.testing.expectError(error.OutOfMemory, Engine.init(failing.allocator(), .{}));    try std.testing.expectEqual(@as(usize, 0), failing.allocated_bytes - failing.freed_bytes);    try std.testing.checkAllAllocationFailures(std.testing.allocator, openAndClose, .{LimitsType{}});}test "ui_style_sharing_quota rejects before changing the live generation" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_sharing_quota"), null, null, null, null, null, null);    }    const allocator = std.testing.allocator;    var workspace = css.Workspace.init(allocator);    defer workspace.deinit();    const sheet = try workspace.parse("box { color: red; } text { color: blue; }", .default());    const nodes = [_]abi.Node{        .{ .id = 1, .subtree_count = 1, .kind = @backingInt(abi.Kind.box) },        .{ .id = 2, .parent = 0, .kind = @backingInt(abi.Kind.text) },    };    var engine = try Engine.init(allocator, .{ .nodes = 2, .distinct_styles = 1, .selectors = 2, .bytes = 8 * 1024 });    defer engine.deinit(allocator);    engine.activate();    const view = tree.View{ .header = .{ .root_font_size = 16 }, .nodes = &nodes };    try std.testing.expectError(error.DistinctStyleQuota, engine.cascadeView(view, &.{ 0, 1 }, &sheet, 1, .{}));    try std.testing.expectEqual(@as(u32, 0), engine.count);    try std.testing.expectEqual(@as(u32, 0), engine.stylesheet_generation);}test "ui_style_font_chain rejects a face quota before binding" {    comptime {        @stardustClaim(alloc_phase.capacity.witness(Engine, "ui_style_font_chain"), null, null, null, null, null, null);    }    const allocator = std.testing.allocator;    var registry = try asset.Registry.init(allocator, .{ .fonts = 2, .images = 0, .owned_bytes = 0 });    defer registry.deinit(allocator);    registry.activate();    var engine = try Engine.init(allocator, .{ .nodes = 1, .distinct_styles = 1, .selectors = 1, .font_faces = 1, .bytes = 8 * 1024 });    defer engine.deinit(allocator);    engine.activate();    try std.testing.expectError(error.FontChainQuota, engine.setFonts(&registry));    try std.testing.expect(engine.fonts == null);}test "sparse quota failure preserves the live pool and node states" {    const allocator = std.testing.allocator;    var workspace = css.Workspace.init(allocator);    defer workspace.deinit();    const sheet = try workspace.parse(".red { color: red; } .blue { color: blue; }", .default());    const nodes = [_]abi.Node{        .{ .id = 1, .subtree_count = 2 },        .{ .id = 2, .parent = 0, .class_first = 0, .class_count = 1 },        .{ .id = 3, .parent = 0, .class_first = 1, .class_count = 1 },    };    var classes = [_]u32{ 1, 1 };    const atoms = [_]abi.Atom{ .{}, .{ .offset = 0, .len = 3 }, .{ .offset = 3, .len = 4 } };    const view = tree.View{ .nodes = &nodes, .classes = &classes, .atoms = &atoms, .strings = "redblue" };    var engine = try Engine.init(allocator, .{ .nodes = 3, .distinct_styles = 2, .selectors = 4, .bytes = 64 * 1024 });    defer engine.deinit(allocator);    engine.activate();    _ = try engine.cascadeView(view, &.{ 0, 1, 2 }, &sheet, 1, .{});    const original = engine.record(2).computed;    const original_epoch = engine.styleEpoch();    const retained_styles = engine.pools[engine.pool_live].used;    classes[1] = 2;    try std.testing.expectError(error.DistinctStyleQuota, engine.cascadeView(view, &.{2}, &sheet, 1, .{}));    try std.testing.expectEqual(retained_styles, engine.pools[engine.pool_live].used);    try std.testing.expectEqual(original_epoch, engine.styleEpoch());    try std.testing.expect(std.meta.eql(original, engine.record(2).computed));    classes[1] = 1;    const restored = try engine.cascadeView(view, &.{2}, &sheet, 1, .{});    try std.testing.expectEqual(@as(u32, 1), restored.visited);    try std.testing.expect(std.meta.eql(original, engine.record(2).computed));}test "style ids share within an epoch and a full cascade advances it" {    const allocator = std.testing.allocator;    var workspace = css.Workspace.init(allocator);    defer workspace.deinit();    const red = try workspace.parse("box { color: red; }", .default());    const blue = try workspace.parse("box { color: blue; }", .default());    const nodes = [_]abi.Node{        .{ .id = 1, .subtree_count = 1, .kind = @backingInt(abi.Kind.box) },        .{ .id = 2, .parent = 0, .kind = @backingInt(abi.Kind.box) },    };    const view = tree.View{        .header = .{ .root_font_size = 16 },        .nodes = &nodes,    };    var engine = try Engine.init(allocator, .{        .nodes = 2,        .distinct_styles = 4,        .selectors = 2,        .bytes = 16 * 1024,    });    defer engine.deinit(allocator);    engine.activate();    try std.testing.expectEqual(@as(u64, 0), engine.styleEpoch());    _ = try engine.cascadeView(view, &.{ 0, 1 }, &red, 1, .{});    const first_epoch = engine.styleEpoch();    const first_id = engine.styleId(0);    try std.testing.expectEqual(@as(u64, 1), first_epoch);    try std.testing.expectEqual(first_id, engine.styleId(1));    _ = try engine.cascadeView(view, &.{1}, &red, 1, .{});    try std.testing.expectEqual(first_epoch, engine.styleEpoch());    try std.testing.expectEqual(first_id, engine.styleId(0));    _ = try engine.cascadeView(view, &.{ 0, 1 }, &blue, 2, .{});    try std.testing.expectEqual(first_epoch + 1, engine.styleEpoch());}

Source: lib/ui/src/style/root.zig:5

zig
pub const generation = @import("generation.zig");

Complete caller list for style.Engine.activate

12 direct callers.

Complete caller list for style.Engine.cascadeView

10 direct callers.

Complete call list for style.Engine.cascadeView

13 direct calls.

Complete caller list for style.Engine.deinit

12 direct callers.

Complete caller list for style.Engine.init

13 direct callers.

Complete caller list for style.Engine.record

8 direct callers.

Audit

Definitions26
Public names45
Members55
Version26.7.0
Revisiondaab053ee433