use std::collections::{HashMap, HashSet};
use crate::focus::WidgetId;
use crate::tcss::matcher::MatchedRule;
use crate::tcss::tree::WidgetTree;
pub struct MatchCache {
entries: HashMap<WidgetId, Vec<MatchedRule>>,
dirty: HashSet<WidgetId>,
}
impl MatchCache {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
dirty: HashSet::new(),
}
}
pub fn get(&self, id: WidgetId) -> Option<&Vec<MatchedRule>> {
if self.dirty.contains(&id) {
return None;
}
self.entries.get(&id)
}
pub fn insert(&mut self, id: WidgetId, matches: Vec<MatchedRule>) {
self.dirty.remove(&id);
self.entries.insert(id, matches);
}
pub fn invalidate(&mut self, id: WidgetId) {
self.dirty.insert(id);
}
pub fn invalidate_all(&mut self) {
for &id in self.entries.keys() {
self.dirty.insert(id);
}
}
pub fn invalidate_subtree(&mut self, tree: &WidgetTree, id: WidgetId) {
self.dirty.insert(id);
for &child_id in tree.children(id) {
self.invalidate_subtree(tree, child_id);
}
}
pub fn is_dirty(&self, id: WidgetId) -> bool {
self.dirty.contains(&id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
self.dirty.clear();
}
}
impl Default for MatchCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tcss::property::{Declaration, PropertyName};
use crate::tcss::tree::WidgetNode;
use crate::tcss::value::CssValue;
fn sample_matches() -> Vec<MatchedRule> {
vec![MatchedRule {
specificity: (0, 0, 1),
source_order: 0,
declarations: vec![Declaration::new(
PropertyName::Color,
CssValue::Keyword("red".into()),
)],
}]
}
#[test]
fn empty_cache() {
let cache = MatchCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn insert_and_get() {
let mut cache = MatchCache::new();
cache.insert(1, sample_matches());
assert!(!cache.is_empty());
assert_eq!(cache.len(), 1);
assert!(cache.get(1).is_some());
let matches = match cache.get(1) {
Some(m) => m,
None => unreachable!(),
};
assert_eq!(matches.len(), 1);
}
#[test]
fn get_missing() {
let cache = MatchCache::new();
assert!(cache.get(42).is_none());
}
#[test]
fn invalidate_single() {
let mut cache = MatchCache::new();
cache.insert(1, sample_matches());
assert!(cache.get(1).is_some());
cache.invalidate(1);
assert!(cache.get(1).is_none());
assert!(cache.is_dirty(1));
assert_eq!(cache.len(), 1);
}
#[test]
fn invalidate_all() {
let mut cache = MatchCache::new();
cache.insert(1, sample_matches());
cache.insert(2, sample_matches());
cache.insert(3, sample_matches());
cache.invalidate_all();
assert!(cache.get(1).is_none());
assert!(cache.get(2).is_none());
assert!(cache.get(3).is_none());
assert!(cache.is_dirty(1));
assert!(cache.is_dirty(2));
assert!(cache.is_dirty(3));
}
#[test]
fn invalidate_subtree() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Root"));
let mut child = WidgetNode::new(2, "Child");
child.parent = Some(1);
tree.add_node(child);
let mut grandchild = WidgetNode::new(3, "Grandchild");
grandchild.parent = Some(2);
tree.add_node(grandchild);
let mut cache = MatchCache::new();
cache.insert(1, sample_matches());
cache.insert(2, sample_matches());
cache.insert(3, sample_matches());
cache.invalidate_subtree(&tree, 1);
assert!(cache.is_dirty(1));
assert!(cache.is_dirty(2));
assert!(cache.is_dirty(3));
}
#[test]
fn is_dirty_check() {
let mut cache = MatchCache::new();
assert!(!cache.is_dirty(1));
cache.insert(1, sample_matches());
assert!(!cache.is_dirty(1));
cache.invalidate(1);
assert!(cache.is_dirty(1));
cache.insert(1, sample_matches());
assert!(!cache.is_dirty(1));
}
#[test]
fn clear_removes_all() {
let mut cache = MatchCache::new();
cache.insert(1, sample_matches());
cache.insert(2, sample_matches());
cache.invalidate(1);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
assert!(!cache.is_dirty(1));
}
}