Phase 8: StyleCache memoization

Add StyleCache struct with hash-keyed LRU-eviction strategy and
integrate into StyleSheet via Mutex<StyleCache> so no signature
changes are needed on evaluate_vdom_incr.

- StyleCache::get_or_compute hashes (type_name, props, epoch) -> ComputedStyle
- LRU: full clear on overflow (max 1024)
- RefCell avoided in favor of Mutex so StyleSheet remains Sync
  for parallel matching_rules_batch
- Manual Clone impl skips cache (fresh cache on clone)
- Call sites in evaluate_vdom_incr switched from ComputedStyle::compute
  to stylesheet.compute_cached
- Cache cleared in build_index after epoch increment (stale entries)
This commit is contained in:
Glint Dev
2026-07-24 18:47:41 +03:00
parent 906dde1569
commit 28bd450787
2 changed files with 82 additions and 2 deletions

View File

@@ -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<u64, ComputedStyle>,
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<String, String>],
) -> 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<StyleRule>,
index: Option<StyleIndex>,
epoch: u64,
cache: Mutex<StyleCache>,
}
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<String, String>],
) -> 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()
}