diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index c432a89..66cbed3 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -469,7 +469,7 @@ impl Interpreter { } let matched_sheets = Self::collect_matching_styles(&vcomp, stylesheet, &[], &structural, ancestors, &sibling_infos[0..i]); - vcomp.computed_style = ComputedStyle::compute(&vcomp.properties, &matched_sheets); + vcomp.computed_style = stylesheet.compute_cached(vcomp.type_name, &vcomp.properties, &matched_sheets); let child_ancestors = Self::build_ancestor_chain(ancestors, &vcomp); let comp_child_refs: Vec<&Element<'a>> = comp.children.iter().collect(); @@ -502,7 +502,7 @@ impl Interpreter { } let matched_sheets = Self::collect_matching_styles(&vnode, stylesheet, &[], &structural, ancestors, &sibling_infos[0..i]); - vnode.computed_style = ComputedStyle::compute(&vnode.properties, &matched_sheets); + vnode.computed_style = stylesheet.compute_cached(vnode.type_name, &vnode.properties, &matched_sheets); let child_ancestors = Self::build_ancestor_chain(ancestors, &vnode); let child_refs: Vec<&Element<'a>> = el.children.iter().collect(); diff --git a/src/interpreter/style.rs b/src/interpreter/style.rs index 1580875..719010d 100644 --- a/src/interpreter/style.rs +++ b/src/interpreter/style.rs @@ -1,5 +1,9 @@ use std::collections::{HashMap, HashSet}; use std::borrow::Cow; +use std::sync::Mutex; +use std::hash::{Hash, Hasher}; + +use super::reactive::ElementId; #[derive(Debug, Clone)] @@ -458,10 +462,70 @@ impl StyleRule { #[derive(Debug, Clone)] +pub struct StyleCache { + entries: HashMap, + max_entries: usize, +} + +impl StyleCache { + pub fn new(max_entries: usize) -> Self { + Self { entries: HashMap::new(), max_entries } + } + + pub fn get_or_compute( + &mut self, + type_name: &str, + props: &[(Cow<'_, str>, Cow<'_, str>)], + epoch: u64, + matched_sheets: &[&HashMap], + ) -> ComputedStyle { + let key = { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + type_name.hash(&mut hasher); + for (k, v) in props { + k.hash(&mut hasher); + v.hash(&mut hasher); + } + epoch.hash(&mut hasher); + hasher.finish() + }; + + if let Some(cached) = self.entries.get(&key) { + return cached.clone(); + } + + let style = ComputedStyle::compute(props, matched_sheets); + + if self.entries.len() >= self.max_entries { + self.entries.clear(); + } + self.entries.insert(key, style.clone()); + + style + } + + pub fn clear(&mut self) { + self.entries.clear(); + } +} + +#[derive(Debug)] pub struct StyleSheet { rules: Vec, index: Option, epoch: u64, + cache: Mutex, +} + +impl Clone for StyleSheet { + fn clone(&self) -> Self { + Self { + rules: self.rules.clone(), + index: self.index.clone(), + epoch: self.epoch, + cache: Mutex::new(StyleCache::new(1024)), + } + } } impl Default for StyleSheet { @@ -470,6 +534,7 @@ impl Default for StyleSheet { rules: Vec::new(), index: None, epoch: 0, + cache: Mutex::new(StyleCache::new(1024)), } } } @@ -488,6 +553,7 @@ impl StyleSheet { pub fn build_index(&mut self) { self.epoch += 1; + self.cache.lock().unwrap().clear(); let mut index = StyleIndex::new(); index.epoch = self.epoch; @@ -749,6 +815,20 @@ impl StyleSheet { props } + pub fn compute_cached<'a>( + &self, + type_name: &str, + props: &[(Cow<'_, str>, Cow<'_, str>)], + matched_sheets: &[&'a HashMap], + ) -> ComputedStyle { + let epoch = self.epoch; + self.cache.lock().unwrap().get_or_compute(type_name, props, epoch, matched_sheets) + } + + pub fn clear_cache(&self) { + self.cache.lock().unwrap().clear(); + } + pub fn is_empty(&self) -> bool { self.rules.is_empty() }