From 26ed1f900e16bd32a62e8da87a253c273b00ed22 Mon Sep 17 00:00:00 2001 From: Glint Dev Date: Wed, 22 Jul 2026 13:01:33 +0300 Subject: [PATCH] Revert "Phase 4.1-4.2: StyleIndex with by_tag/by_class/by_id indexes, replace matching_rules with query_index, build_index after parsing" This reverts commit 87feba394d228154a4dadb150abc7646dd70dcb7. --- src/interpreter/mod.rs | 4 +- src/interpreter/style.rs | 345 ++++----------------------------------- src/renderer.rs | 8 +- 3 files changed, 33 insertions(+), 324 deletions(-) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 05e75c9..3bb2ed3 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -48,8 +48,6 @@ impl Interpreter { //let rhei_ctx = RheiContext::new(&rhei_scripts); //rhei_ctx.initialize(&mut variables); - stylesheet.build_index(); - Ok(Document { roots, components, variables, rhei_scripts, stylesheet, interner: Interner::new(), tracker }) } @@ -529,7 +527,7 @@ impl Interpreter { .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); - stylesheet.matching_rules(Some(el.element_id), el.type_name, el_id, &classes, active_pseudo, structural, ancestors, preceding_siblings, &el_attributes) + stylesheet.matching_rules(el.type_name, el_id, &classes, active_pseudo, structural, ancestors, preceding_siblings, &el_attributes) } fn resolve_prop(v: &str, variables: &HashMap, rhei: &RheiContext) -> String { diff --git a/src/interpreter/style.rs b/src/interpreter/style.rs index 111b84b..f8a41c5 100644 --- a/src/interpreter/style.rs +++ b/src/interpreter/style.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; use std::borrow::Cow; -use std::cell::RefCell; -use super::reactive::ElementId; #[derive(Debug, Clone)] @@ -459,19 +457,9 @@ impl StyleRule { } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct StyleSheet { rules: Vec, - index: StyleIndex, -} - -impl Default for StyleSheet { - fn default() -> Self { - Self { - rules: Vec::new(), - index: StyleIndex::new(Vec::new()), - } - } } impl StyleSheet { @@ -485,14 +473,8 @@ impl StyleSheet { } } - /// Rebuild the style index (call after all rules are added). - pub fn build_index(&mut self) { - self.index = StyleIndex::new(std::mem::take(&mut self.rules)); - } - pub fn matching_rules<'a>( &'a self, - element_id: Option, type_name: &str, el_id: Option<&str>, el_classes: &[&str], @@ -502,17 +484,29 @@ impl StyleSheet { preceding_siblings: &[AncestorInfo], el_attributes: &HashMap, ) -> Vec<&'a HashMap> { - self.index.query_cached( - element_id, active_pseudo, - type_name, el_id, el_classes, - structural, ancestors, preceding_siblings, el_attributes, - ) + let mut matched: Vec<(usize, &StyleRule)> = self + .rules + .iter() + .enumerate() + .filter(|(_, rule)| { + rule.selector + .matches(type_name, el_id, el_classes, active_pseudo, structural, ancestors, preceding_siblings, el_attributes) + }) + .collect(); + + matched.sort_by(|(i, a), (j, b)| { + a.selector + .specificity() + .cmp(&b.selector.specificity()) + .then_with(|| i.cmp(j)) + }); + + matched.into_iter().map(|(_, rule)| &rule.properties).collect() } pub fn matching_pseudo_rules( &self, pseudo: &str, - element_id: Option, type_name: &str, el_id: Option<&str>, el_classes: &[&str], @@ -522,12 +516,14 @@ impl StyleSheet { el_attributes: &HashMap, ) -> HashMap { let mut props = HashMap::new(); - let all_sheets = self.matching_rules( - element_id, type_name, el_id, el_classes, &[pseudo], - structural, ancestors, preceding_siblings, el_attributes, - ); - for sheet in &all_sheets { - for (k, v) in sheet.iter() { + for rule in &self.rules { + if !rule.selector.has_pseudo_class(pseudo) { + continue; + } + if !rule.selector.matches(type_name, el_id, el_classes, &[pseudo], structural, ancestors, preceding_siblings, el_attributes) { + continue; + } + for (k, v) in &rule.properties { props.insert(k.clone(), v.clone()); } } @@ -535,292 +531,7 @@ impl StyleSheet { } pub fn is_empty(&self) -> bool { - self.index.rules.is_empty() - } -} - -type RuleId = usize; - -/// Pre-built index for O(1) rule candidate lookup instead of O(N) scan. -#[derive(Debug, Clone)] -pub struct StyleIndex { - /// Rules keyed by tag name (e.g. "Button", "Panel") - by_tag: HashMap>, - /// Rules keyed by class name - by_class: HashMap>, - /// Rules keyed by id - by_id: HashMap>, - /// Rules keyed by (tag, class) pair - by_tag_class: HashMap<(String, String), Vec>, - /// Rules keyed by (tag, id) pair - by_tag_id: HashMap<(String, String), Vec>, - /// Complex selectors that can't be indexed (combinators, pseudo-classes, etc.) - complex_rules: Vec<(ComplexSelector, RuleId)>, - /// All rules, sorted by specificity once at build time - rules: Vec, - /// Epoch counter: incremented on rebuild, used for cache invalidation - epoch: u64, - /// Cache: element_id → (matched_rule_ids, epoch_at_insert) - match_cache: RefCell, u64)>>, - /// Cache for pseudo-class matches: (element_id, pseudo_class) → (matched_rule_ids, epoch) - pseudo_cache: RefCell, u64)>>, -} - -impl StyleIndex { - pub fn new(rules: Vec) -> Self { - let mut idx = Self { - by_tag: HashMap::new(), - by_class: HashMap::new(), - by_id: HashMap::new(), - by_tag_class: HashMap::new(), - by_tag_id: HashMap::new(), - complex_rules: Vec::new(), - rules: Vec::new(), - epoch: 0, - match_cache: RefCell::new(HashMap::new()), - pseudo_cache: RefCell::new(HashMap::new()), - }; - - // Sort rules by specificity once - let mut sorted_rules: Vec<(usize, StyleRule)> = rules.into_iter().enumerate().collect(); - sorted_rules.sort_by(|(i, a), (j, b)| { - a.selector - .specificity() - .cmp(&b.selector.specificity()) - .then_with(|| i.cmp(j)) - }); - - for (new_id, (_, rule)) in sorted_rules.into_iter().enumerate() { - let rid = idx.rules.len(); - idx.rules.push(rule); - - let simple = idx.rules[rid].selector.as_simple().cloned(); - - if let Some(compound) = simple { - let has_pseudo = !compound.pseudo_classes.is_empty(); - let has_complex_combinator = false; // simple selector = no combinators - - if has_pseudo || has_complex_combinator { - idx.complex_rules.push((idx.rules[rid].selector.clone(), rid)); - } else { - // Index by tag - if let Some(ref tag) = compound.tag { - idx.by_tag.entry(tag.clone()).or_default().push(rid); - if let Some(ref id) = compound.id { - idx.by_tag_id.entry((tag.clone(), id.clone())).or_default().push(rid); - } - for cls in &compound.classes { - idx.by_tag_class.entry((tag.clone(), cls.clone())).or_default().push(rid); - } - } - // Index by class - for cls in &compound.classes { - idx.by_class.entry(cls.clone()).or_default().push(rid); - } - // Index by id - if let Some(ref id) = compound.id { - idx.by_id.entry(id.clone()).or_default().push(rid); - } - } - } else { - // Complex selector (combinators, multiple compounds) - idx.complex_rules.push((idx.rules[rid].selector.clone(), rid)); - } - } - - idx - } - - pub fn epoch(&self) -> u64 { - self.epoch - } - - pub fn query_cached<'a>( - &'a self, - element_id: Option, - active_pseudo: &[&str], - type_name: &str, - el_id: Option<&str>, - el_classes: &[&str], - structural: &StructuralContext, - ancestors: &[AncestorInfo], - preceding_siblings: &[AncestorInfo], - el_attributes: &HashMap, - ) -> Vec<&'a HashMap> { - let eid = match element_id { - Some(id) => id, - None => return self.query( - type_name, el_id, el_classes, active_pseudo, - structural, ancestors, preceding_siblings, el_attributes, - ), - }; - - // Check cache - if active_pseudo.is_empty() { - let cache = self.match_cache.borrow(); - if let Some((cached_ids, cached_epoch)) = cache.get(&eid) { - if *cached_epoch == self.epoch { - return cached_ids.iter().map(|&rid| &self.rules[rid].properties).collect(); - } - } - } else { - let pseudo_key = active_pseudo.join(","); - let cache = self.pseudo_cache.borrow(); - if let Some((cached_ids, cached_epoch)) = cache.get(&(eid, pseudo_key)) { - if *cached_epoch == self.epoch { - return cached_ids.iter().map(|&rid| &self.rules[rid].properties).collect(); - } - } - } - - // Cache miss — run full query - let result = self.query( - type_name, el_id, el_classes, active_pseudo, - structural, ancestors, preceding_siblings, el_attributes, - ); - - // Extract rule IDs from matched results (track which rules matched) - let matched_ids: Vec = { - result.iter().filter_map(|props| { - self.rules.iter().position(|r| &r.properties == *props) - }).collect() - }; - - // Populate cache (short-lived mutable borrow) - if active_pseudo.is_empty() { - if !matched_ids.is_empty() || self.match_cache.borrow().len() < 2048 { - self.match_cache.borrow_mut().insert(eid, (matched_ids, self.epoch)); - } - } else { - let pseudo_key = active_pseudo.join(","); - if !matched_ids.is_empty() || self.pseudo_cache.borrow().len() < 1024 { - self.pseudo_cache.borrow_mut().insert((eid, pseudo_key), (matched_ids, self.epoch)); - } - } - - result - } - - pub fn query<'a>( - &'a self, - type_name: &str, - el_id: Option<&str>, - el_classes: &[&str], - active_pseudo: &[&str], - structural: &StructuralContext, - ancestors: &[AncestorInfo], - preceding_siblings: &[AncestorInfo], - el_attributes: &HashMap, - ) -> Vec<&'a HashMap> { - // Collect candidates from indexes - let mut candidates: Vec = Vec::new(); - - // Start with tag index - if let Some(tag_rules) = self.by_tag.get(type_name) { - // If we have tag+id, prefer that for more precise filtering - if let Some(id) = el_id { - if let Some(tag_id_rules) = self.by_tag_id.get(&(type_name.to_string(), id.to_string())) { - candidates = tag_id_rules.clone(); - } else { - candidates = tag_rules.clone(); - } - } else { - candidates = tag_rules.clone(); - } - - // Intersect with class if applicable - if !el_classes.is_empty() { - let first_class = el_classes[0]; - if let Some(tag_class_rules) = self.by_tag_class.get(&(type_name.to_string(), first_class.to_string())) { - candidates.retain(|r| tag_class_rules.contains(r)); - } else { - // No rules for tag+class, clear non-matching - candidates.retain(|r| { - el_classes.iter().any(|cls| { - self.by_class.get(*cls).map_or(false, |cr| cr.contains(r)) - }) - }); - } - } - - // Also check additional class-only rules - for cls in el_classes.iter().skip(if el_id.is_some() { 0 } else { 1 }) { - if let Some(cls_rules) = self.by_class.get(*cls) { - for &r in cls_rules { - if !candidates.contains(&r) { - candidates.push(r); - } - } - } - } - - // Also check id-only rules - if let Some(id) = el_id { - if let Some(id_rules) = self.by_id.get(id) { - for &r in id_rules { - if !candidates.contains(&r) { - candidates.push(r); - } - } - } - } - } else { - // No tag match — check class and id indexes - for cls in el_classes { - if let Some(cls_rules) = self.by_class.get(*cls) { - candidates.extend(cls_rules); - } - } - if let Some(id) = el_id { - if let Some(id_rules) = self.by_id.get(id) { - candidates.extend(id_rules); - } - } - } - - // De-duplicate while preserving order - let mut seen = std::collections::HashSet::new(); - candidates.retain(|r| seen.insert(*r)); - - // Merge candidate and complex matches in a single pass. - // Both lists reference self.rules, which is already sorted by specificity, - // so we collect all matches and they stay in insertion order. - let mut matched: Vec<&StyleRule> = Vec::new(); - let mut ci = 0usize; - - for &rid in &candidates { - // Insert any complex rules that precede this candidate in the sorted rules - while ci < self.complex_rules.len() && self.complex_rules[ci].1 < rid { - let (sel, crid) = &self.complex_rules[ci]; - if sel.matches( - type_name, el_id, el_classes, active_pseudo, - structural, ancestors, preceding_siblings, el_attributes, - ) { - matched.push(&self.rules[*crid]); - } - ci += 1; - } - - let rule = &self.rules[rid]; - if rule.selector.matches( - type_name, el_id, el_classes, active_pseudo, - structural, ancestors, preceding_siblings, el_attributes, - ) { - matched.push(rule); - } - } - - // Remaining complex rules - for (sel, crid) in &self.complex_rules[ci..] { - if sel.matches( - type_name, el_id, el_classes, active_pseudo, - structural, ancestors, preceding_siblings, el_attributes, - ) { - matched.push(&self.rules[*crid]); - } - } - - matched.into_iter().map(|r| &r.properties).collect() + self.rules.is_empty() } } diff --git a/src/renderer.rs b/src/renderer.rs index 9d5defb..0ef27be 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -59,10 +59,10 @@ pub fn collect_hover_active<'a>( .collect(); let hover_props = stylesheet.matching_pseudo_rules( - "hover", Some(el.element_id), el.type_name, el_id, &classes, &default_struct, &[], &[], &el_attributes, + "hover", el.type_name, el_id, &classes, &default_struct, &[], &[], &el_attributes, ); let active_props = stylesheet.matching_pseudo_rules( - "active", Some(el.element_id), el.type_name, el_id, &classes, &default_struct, &[], &[], &el_attributes, + "active", el.type_name, el_id, &classes, &default_struct, &[], &[], &el_attributes, ); (hover_props, active_props) @@ -1032,9 +1032,9 @@ fn render_button<'a>( .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); let hover_sheets: Vec<&HashMap> = - stylesheet.matching_rules(Some(el.element_id), el.type_name, el_id, &classes, &["hover"], &default_struct, &[], &[], &el_attributes); + stylesheet.matching_rules(el.type_name, el_id, &classes, &["hover"], &default_struct, &[], &[], &el_attributes); let active_sheets: Vec<&HashMap> = - stylesheet.matching_rules(Some(el.element_id), el.type_name, el_id, &classes, &["active"], &default_struct, &[], &[], &el_attributes); + stylesheet.matching_rules(el.type_name, el_id, &classes, &["active"], &default_struct, &[], &[], &el_attributes); let mut hover_props = std::collections::HashMap::new(); for sheet in &hover_sheets {