1use std::collections::hash_map::DefaultHasher;
14use std::collections::{HashMap, HashSet};
15use std::hash::{Hash, Hasher};
16
17use iced::widget::canvas as iced_canvas;
18use iced::widget::{combo_box, markdown, pane_grid, text_editor};
19use serde_json::Value;
20
21use crate::protocol::TreeNode;
22
23pub(crate) const MAX_TREE_DEPTH: usize = 256;
27
28const MAX_HASH_DEPTH: usize = 256;
32
33macro_rules! define_caches {
42 ($($(#[$meta:meta])* $field:ident : $value:ty),* $(,)?) => {
43 pub struct WidgetCaches {
49 $($(#[$meta])* pub(crate) $field: HashMap<String, $value>,)*
50 pub extension: crate::extensions::ExtensionCaches,
53 }
54
55 impl WidgetCaches {
56 pub fn new() -> Self {
57 Self {
58 $($field: HashMap::new(),)*
59 extension: crate::extensions::ExtensionCaches::new(),
60 }
61 }
62
63 pub fn clear_builtin(&mut self) {
69 $(self.$field.clear();)*
70 }
71
72 fn prune_stale(&mut self, live_ids: &HashSet<String>) {
74 $(self.$field.retain(|id, _| live_ids.contains(id));)*
75 }
76 }
77 };
78}
79
80define_caches! {
81 editor_contents: text_editor::Content,
83 editor_content_hashes: u64,
86 markdown_items: (u64, Vec<markdown::Item>),
88 combo_states: combo_box::State<String>,
90 combo_options: Vec<String>,
92 pane_grid_states: pane_grid::State<String>,
94 canvas_caches: HashMap<String, (u64, iced_canvas::Cache)>,
97 qr_code_caches: (u64, iced_canvas::Cache),
99 themer_themes: iced::Theme,
101}
102
103impl Default for WidgetCaches {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109impl WidgetCaches {
110 pub fn clear(&mut self) {
111 self.clear_builtin();
112 self.extension.clear();
113 }
114
115 pub fn editor_content_mut(&mut self, id: &str) -> Option<&mut text_editor::Content> {
121 self.editor_contents.get_mut(id)
122 }
123
124 pub fn pane_grid_state_mut(&mut self, id: &str) -> Option<&mut pane_grid::State<String>> {
126 self.pane_grid_states.get_mut(id)
127 }
128
129 pub fn pane_grid_state(&self, id: &str) -> Option<&pane_grid::State<String>> {
131 self.pane_grid_states.get(id)
132 }
133}
134
135pub fn ensure_caches(node: &TreeNode, caches: &mut WidgetCaches) {
148 let mut live_ids = HashSet::new();
149 ensure_caches_walk(node, caches, &mut live_ids, 0);
150 caches.prune_stale(&live_ids);
151}
152
153fn ensure_caches_walk(
155 node: &TreeNode,
156 caches: &mut WidgetCaches,
157 live_ids: &mut HashSet<String>,
158 depth: usize,
159) {
160 if depth > MAX_TREE_DEPTH {
161 log::warn!(
162 "[id={}] ensure_caches depth exceeds {MAX_TREE_DEPTH}, skipping subtree",
163 node.id
164 );
165 return;
166 }
167 live_ids.insert(node.id.clone());
168
169 match node.type_name.as_str() {
170 "text_editor" => super::input::ensure_text_editor_cache(node, caches),
171 "markdown" => super::display::ensure_markdown_cache(node, caches),
172 "combo_box" => super::input::ensure_combo_box_cache(node, caches),
173 "pane_grid" => super::layout::ensure_pane_grid_cache(node, caches),
174 "canvas" => super::canvas::ensure_canvas_cache(node, caches),
175 "themer" => super::interactive::ensure_themer_cache(node, caches),
176 "qr_code" => super::display::ensure_qr_code_cache(node, caches),
177 _ => {}
178 }
179
180 for child in &node.children {
181 ensure_caches_walk(child, caches, live_ids, depth + 1);
182 }
183}
184
185pub(crate) fn canvas_layer_map(
197 props: Option<&serde_json::Map<String, Value>>,
198) -> std::collections::BTreeMap<String, &Value> {
199 let mut map = std::collections::BTreeMap::new();
200
201 if let Some(layers_obj) = props
202 .and_then(|p| p.get("layers"))
203 .and_then(|v| v.as_object())
204 {
205 for (name, shapes_val) in layers_obj {
206 map.insert(name.clone(), shapes_val);
207 }
208 } else if let Some(shapes_arr) = props.and_then(|p| p.get("shapes")) {
209 map.insert("default".to_string(), shapes_arr);
210 }
211
212 map
213}
214
215pub(crate) fn hash_json_value(v: &serde_json::Value, h: &mut impl std::hash::Hasher) {
222 hash_json_value_inner(v, h, 0);
223}
224
225fn hash_json_value_inner(v: &serde_json::Value, h: &mut impl std::hash::Hasher, depth: usize) {
226 if depth > MAX_HASH_DEPTH {
227 6u8.hash(h);
231 return;
232 }
233 match v {
234 serde_json::Value::Null => 0u8.hash(h),
235 serde_json::Value::Bool(b) => {
236 1u8.hash(h);
237 b.hash(h);
238 }
239 serde_json::Value::Number(n) => {
240 2u8.hash(h);
241 if let Some(f) = n.as_f64() {
242 f.to_bits().hash(h);
243 } else if let Some(i) = n.as_i64() {
244 i.hash(h);
245 } else if let Some(u) = n.as_u64() {
246 u.hash(h);
247 }
248 }
249 serde_json::Value::String(s) => {
250 3u8.hash(h);
251 s.hash(h);
252 }
253 serde_json::Value::Array(arr) => {
254 4u8.hash(h);
255 arr.len().hash(h);
256 for item in arr {
257 hash_json_value_inner(item, h, depth + 1);
258 }
259 }
260 serde_json::Value::Object(obj) => {
261 5u8.hash(h);
262 obj.len().hash(h);
263 for (k, v) in obj {
264 k.hash(h);
265 hash_json_value_inner(v, h, depth + 1);
266 }
267 }
268 }
269}
270
271pub(crate) fn hash_str(s: &str) -> u64 {
275 let mut hasher = DefaultHasher::new();
276 s.hash(&mut hasher);
277 hasher.finish()
278}
279
280#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
291 fn widget_caches_new_is_empty() {
292 let c = WidgetCaches::new();
293 assert!(c.editor_contents.is_empty());
294 assert!(c.markdown_items.is_empty());
295 assert!(c.combo_states.is_empty());
296 assert!(c.combo_options.is_empty());
297 assert!(c.pane_grid_states.is_empty());
298 }
299
300 #[test]
301 fn widget_caches_clear_empties_maps() {
302 let mut c = WidgetCaches::new();
303 c.combo_options.insert("x".into(), vec!["a".into()]);
304 c.clear();
305 assert!(c.combo_options.is_empty());
306 }
307
308 #[test]
311 fn clear_builtin_preserves_extension_caches() {
312 let mut caches = WidgetCaches::new();
313
314 caches
316 .editor_contents
317 .insert("ed1".to_string(), iced::widget::text_editor::Content::new());
318 caches.extension.insert("ext", "key", 42u32);
319
320 caches.clear_builtin();
321
322 assert!(caches.editor_contents.is_empty());
324 assert_eq!(caches.extension.get::<u32>("ext", "key"), Some(&42));
326 }
327
328 #[test]
329 fn clear_wipes_both_builtin_and_extension() {
330 let mut caches = WidgetCaches::new();
331
332 caches
333 .editor_contents
334 .insert("ed1".to_string(), iced::widget::text_editor::Content::new());
335 caches.extension.insert("ext", "key", 42u32);
336
337 caches.clear();
338
339 assert!(caches.editor_contents.is_empty());
340 assert!(!caches.extension.contains("ext", "key"));
341 }
342
343 #[test]
346 fn hash_json_value_same_input_same_hash() {
347 use std::collections::hash_map::DefaultHasher;
348
349 let val = serde_json::json!({"shapes": [{"type": "rect", "x": 0, "y": 0}]});
350 let h1 = {
351 let mut h = DefaultHasher::new();
352 hash_json_value(&val, &mut h);
353 h.finish()
354 };
355 let h2 = {
356 let mut h = DefaultHasher::new();
357 hash_json_value(&val, &mut h);
358 h.finish()
359 };
360 assert_eq!(h1, h2);
361 }
362
363 #[test]
364 fn hash_json_value_different_input_different_hash() {
365 use std::collections::hash_map::DefaultHasher;
366
367 let a = serde_json::json!({"type": "rect"});
368 let b = serde_json::json!({"type": "circle"});
369 let hash_a = {
370 let mut h = DefaultHasher::new();
371 hash_json_value(&a, &mut h);
372 h.finish()
373 };
374 let hash_b = {
375 let mut h = DefaultHasher::new();
376 hash_json_value(&b, &mut h);
377 h.finish()
378 };
379 assert_ne!(hash_a, hash_b);
380 }
381
382 #[test]
383 fn hash_json_value_type_discrimination() {
384 use std::collections::hash_map::DefaultHasher;
385
386 let vals = [
388 serde_json::json!(null),
389 serde_json::json!(false),
390 serde_json::json!(0),
391 serde_json::json!(""),
392 serde_json::json!([]),
393 serde_json::json!({}),
394 ];
395 let hashes: Vec<u64> = vals
396 .iter()
397 .map(|v| {
398 let mut h = DefaultHasher::new();
399 hash_json_value(v, &mut h);
400 h.finish()
401 })
402 .collect();
403
404 for (i, h1) in hashes.iter().enumerate() {
406 for (j, h2) in hashes.iter().enumerate() {
407 if i != j {
408 assert_ne!(h1, h2, "type {i} and {j} should hash differently");
409 }
410 }
411 }
412 }
413}