1use std::collections::HashMap;
14use std::sync::Arc;
15
16use sequoia_core::{Rect, RenderList, RoundedClip, Transform, WindowId};
17
18use crate::paint_region::CullCtx;
19
20#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
25pub struct LayerKey {
26 pub element_id: u64,
27 pub revision: u64,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq)]
37pub(crate) struct LayerStamp {
38 pub rect: Rect,
39 pub scale: f32,
40 pub transform: Transform,
41 pub clip: Option<Rect>,
42 pub rounded_clip: Option<RoundedClip>,
43 pub opacity: f32,
44 pub text_atlas: u64,
45 pub text_epoch: u64,
46 pub window: WindowId,
47 pub hovered: Option<u64>,
48 pub pressed: Option<u64>,
49 pub focused: Option<u64>,
50 pub captured: Option<u64>,
51 pub cull: CullCtx,
52}
53
54struct LayerEntry {
55 list: Arc<RenderList>,
56 stamp: Option<LayerStamp>,
57}
58
59#[derive(Default)]
61pub struct LayerCache {
62 map: HashMap<LayerKey, LayerEntry>,
63 capacity: usize,
64}
65
66impl LayerCache {
67 pub fn new(capacity: usize) -> Self {
68 Self {
69 map: HashMap::new(),
70 capacity: capacity.max(1),
71 }
72 }
73
74 pub fn get(&self, key: LayerKey) -> Option<Arc<RenderList>> {
75 self.map.get(&key).map(|entry| Arc::clone(&entry.list))
76 }
77
78 pub(crate) fn get_stamped(&self, key: LayerKey, stamp: &LayerStamp) -> Option<Arc<RenderList>> {
79 self.map
80 .get(&key)
81 .filter(|entry| entry.stamp.as_ref() == Some(stamp))
82 .map(|entry| Arc::clone(&entry.list))
83 }
84
85 pub fn insert(&mut self, key: LayerKey, list: RenderList) -> Arc<RenderList> {
86 let list = Arc::new(list);
87 self.store(key, Arc::clone(&list), None);
88 list
89 }
90
91 pub(crate) fn insert_stamped(
92 &mut self,
93 key: LayerKey,
94 list: RenderList,
95 stamp: LayerStamp,
96 ) -> Arc<RenderList> {
97 let list = Arc::new(list);
98 self.store(key, Arc::clone(&list), Some(stamp));
99 list
100 }
101
102 fn store(&mut self, key: LayerKey, list: Arc<RenderList>, stamp: Option<LayerStamp>) {
103 if !self.map.contains_key(&key)
104 && self.map.len() >= self.capacity
105 && let Some(victim) = self.map.keys().next().copied()
106 {
107 self.map.remove(&victim);
108 }
109 self.map.insert(key, LayerEntry { list, stamp });
110 }
111
112 pub fn invalidate_element(&mut self, element_id: u64) {
114 self.map.retain(|key, _| key.element_id != element_id);
115 }
116
117 pub fn invalidate_all(&mut self) {
118 self.map.clear();
119 }
120
121 pub fn len(&self) -> usize {
122 self.map.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
126 self.map.is_empty()
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use sequoia_core::{Color, Rect};
134
135 fn layer(element_id: u64, revision: u64) -> LayerKey {
136 LayerKey {
137 element_id,
138 revision,
139 }
140 }
141
142 #[test]
143 fn layer_cache_hit_returns_same_list() {
144 let mut cache = LayerCache::new(4);
145 let mut list = RenderList::new();
146 list.push_quad(Rect::from_xywh(0.0, 0.0, 10.0, 10.0), Color::RED, 0.0);
147 let first = cache.insert(layer(1, 0), list);
148 let second = cache.get(layer(1, 0)).unwrap();
149 assert!(Arc::ptr_eq(&first, &second));
150 }
151
152 #[test]
153 fn layer_cache_new_revision_is_miss() {
154 let mut cache = LayerCache::new(4);
155 let _ = cache.insert(layer(1, 0), RenderList::new());
156 assert!(cache.get(layer(1, 0)).is_some());
157 assert!(cache.get(layer(1, 1)).is_none());
158 }
159
160 #[test]
161 fn layer_cache_invalidate_element() {
162 let mut cache = LayerCache::new(8);
163 let _ = cache.insert(layer(1, 0), RenderList::new());
164 let _ = cache.insert(layer(2, 0), RenderList::new());
165 cache.invalidate_element(1);
166 assert!(cache.get(layer(1, 0)).is_none());
167 assert!(cache.get(layer(2, 0)).is_some(), "other element untouched");
168 }
169
170 #[test]
171 fn layer_cache_evicts_when_full() {
172 let mut cache = LayerCache::new(2);
173 let _ = cache.insert(layer(1, 0), RenderList::new());
174 let _ = cache.insert(layer(2, 0), RenderList::new());
175 let _ = cache.insert(layer(3, 0), RenderList::new());
176 assert_eq!(cache.len(), 2);
177 }
178
179 #[test]
180 fn layer_cache_replacing_key_does_not_evict_another() {
181 let mut cache = LayerCache::new(2);
182 let _ = cache.insert(layer(1, 0), RenderList::new());
183 let _ = cache.insert(layer(2, 0), RenderList::new());
184 let _ = cache.insert(layer(1, 0), RenderList::new());
185 assert_eq!(cache.len(), 2);
186 assert!(cache.get(layer(2, 0)).is_some());
187 }
188
189 fn stamp(rect: Rect) -> LayerStamp {
190 LayerStamp {
191 rect,
192 scale: 1.0,
193 transform: Transform::IDENTITY,
194 clip: Some(rect),
195 rounded_clip: None,
196 opacity: 1.0,
197 text_atlas: 0,
198 text_epoch: 0,
199 window: WindowId::PRIMARY,
200 hovered: None,
201 pressed: None,
202 focused: None,
203 captured: None,
204 cull: CullCtx::ENABLED,
205 }
206 }
207
208 #[test]
209 fn layer_cache_stamp_must_match() {
210 let mut cache = LayerCache::new(4);
211 let rect = Rect::from_xywh(0.0, 0.0, 10.0, 10.0);
212 let _ = cache.insert_stamped(layer(1, 0), RenderList::new(), stamp(rect));
213 assert!(
214 cache.get_stamped(layer(1, 0), &stamp(rect)).is_some(),
215 "same context hits"
216 );
217 assert!(
218 cache
219 .get_stamped(layer(1, 0), &stamp(Rect::from_xywh(1.0, 0.0, 10.0, 10.0)))
220 .is_none(),
221 "moved layer misses"
222 );
223 let mut hovered = stamp(rect);
224 hovered.hovered = Some(7);
225 assert!(
226 cache.get_stamped(layer(1, 0), &hovered).is_none(),
227 "hover target change misses"
228 );
229 }
230}