1use leaf_core::{Doc, View, wysiwyg};
5use std::collections::HashMap;
6use std::hash::{Hash, Hasher};
7use std::time::Instant;
8use twig::{Editor, Format};
9
10fn body(bytes: usize) -> String {
11 let mut s = String::new();
12 let mut i = 0;
13 while s.len() < bytes {
14 s.push_str(&format!(
15 "## Section {i}\n\nThe quick brown fox jumps over the lazy dog, and \
16 **bold** text with a [link](https://example.dev) and `code` besides. \
17 Another sentence follows to make the paragraph a realistic length.\n\n"
18 ));
19 i += 1;
20 }
21 s
22}
23
24fn time<T>(label: &str, n: usize, mut f: impl FnMut() -> T) -> f64 {
25 let t = Instant::now();
26 for _ in 0..n {
27 std::hint::black_box(f());
28 }
29 let ms = t.elapsed().as_secs_f64() * 1000.0 / n as f64;
30 println!(" {label:<30}{ms:8.2} ms");
31 ms
32}
33
34fn main() {
35 for kb in [10usize, 100, 1000] {
36 let src = body(kb * 1024);
37 println!("=== {} KB ===", src.len() / 1024);
38
39 let mut ed = Editor::new_str(&src, Format::Markdown).unwrap();
40 let nodes = ed.nodes().unwrap();
41 let map = wysiwyg::build(&nodes, &src, None, false, &HashMap::new(), None);
42 println!(" ({} AST nodes, {} map rows)", nodes.len(), map.rows.len());
43
44 println!(" -- per edit (unavoidable today) --");
45 time("twig edit_range (reparse)", 5, || {
46 ed.edit_range(src.len() / 2, src.len() / 2, "x").is_ok()
47 });
48 time("twig nodes() FFI marshal", 5, || ed.nodes().unwrap().len());
49 time("wysiwyg::build", 5, || {
50 wysiwyg::build(&nodes, &src, None, false, &HashMap::new(), None)
51 .rows
52 .len()
53 });
54 {
55 let mut cache = wysiwyg::BlockCache::default();
62 let top = ed.child_spans(None).unwrap();
63 let _ = wysiwyg::build_cached(
64 &top,
65 &src,
66 None,
67 false,
68 &HashMap::new(),
69 None,
70 &mut cache,
71 |id| ed.subtree(twig::NodeId(id)).unwrap_or_default(),
72 );
73 time("wysiwyg::build_cached (all reused)", 5, || {
74 let top = ed.child_spans(None).unwrap();
75 wysiwyg::build_cached(
76 &top,
77 &src,
78 None,
79 false,
80 &HashMap::new(),
81 None,
82 &mut cache,
83 |id| ed.subtree(twig::NodeId(id)).unwrap_or_default(),
84 )
85 .rows
86 .len()
87 });
88 }
89
90 println!(" -- claimed hot, actually noise --");
91 time("twig source_str() (full copy)", 5, || {
92 ed.source_str().unwrap().len()
93 });
94 let clean = src.clone();
95 time("dirty compare (full cmp)", 5, || src == clean);
96
97 println!(" -- what the GUI adds on a cache miss --");
98 time("clone every row's glyphs", 5, || {
99 map.rows
100 .iter()
101 .map(|r| r.glyphs.clone())
102 .collect::<Vec<_>>()
103 .len()
104 });
105 time("hash every glyph (cache key?)", 5, || {
106 let mut n = 0u64;
107 for r in &map.rows {
108 let mut h = std::collections::hash_map::DefaultHasher::new();
109 for g in &r.glyphs {
110 g.ch.hash(&mut h);
111 }
112 n ^= h.finish();
113 }
114 n
115 });
116
117 println!(" -- the whole path, as a frontend calls it --");
118 let mut p = std::env::temp_dir();
119 p.push(format!("leaf_bench_{kb}.md"));
120 std::fs::write(&p, &src).unwrap();
121 let mut d = Doc::open(p).unwrap();
122 d.view = View::Wysiwyg;
123 d.place_caret(src.len() / 2, false);
124 d.build_visual_unwrapped();
125 time("build_visual (cached: a repaint)", 200, || {
126 d.build_visual_unwrapped()
127 });
128 time("Doc::insert + rebuild (a keystroke)", 5, || {
129 d.insert("x");
130 d.build_visual_unwrapped();
131 });
132 println!();
133 }
134}