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, || wysiwyg::build(&nodes, &src, None, false, &HashMap::new(), None).rows.len());
50 {
51 let mut cache = wysiwyg::BlockCache::default();
58 let top = ed.child_spans(None).unwrap();
59 let _ = wysiwyg::build_cached(&top, &src, None, false, &HashMap::new(), None, &mut cache, |id| {
60 ed.subtree(twig::NodeId(id)).unwrap_or_default()
61 });
62 time("wysiwyg::build_cached (all reused)", 5, || {
63 let top = ed.child_spans(None).unwrap();
64 wysiwyg::build_cached(&top, &src, None, false, &HashMap::new(), None, &mut cache, |id| {
65 ed.subtree(twig::NodeId(id)).unwrap_or_default()
66 })
67 .rows
68 .len()
69 });
70 }
71
72 println!(" -- claimed hot, actually noise --");
73 time("twig source_str() (full copy)", 5, || ed.source_str().unwrap().len());
74 let clean = src.clone();
75 time("dirty compare (full cmp)", 5, || src == clean);
76
77 println!(" -- what the GUI adds on a cache miss --");
78 time("clone every row's glyphs", 5, || {
79 map.rows.iter().map(|r| r.glyphs.clone()).collect::<Vec<_>>().len()
80 });
81 time("hash every glyph (cache key?)", 5, || {
82 let mut n = 0u64;
83 for r in &map.rows {
84 let mut h = std::collections::hash_map::DefaultHasher::new();
85 for g in &r.glyphs {
86 g.ch.hash(&mut h);
87 }
88 n ^= h.finish();
89 }
90 n
91 });
92
93 println!(" -- the whole path, as a frontend calls it --");
94 let mut p = std::env::temp_dir();
95 p.push(format!("leaf_bench_{kb}.md"));
96 std::fs::write(&p, &src).unwrap();
97 let mut d = Doc::open(p).unwrap();
98 d.view = View::Wysiwyg;
99 d.place_caret(src.len() / 2, false);
100 d.build_visual_unwrapped();
101 time("build_visual (cached: a repaint)", 200, || d.build_visual_unwrapped());
102 time("Doc::insert + rebuild (a keystroke)", 5, || {
103 d.insert("x");
104 d.build_visual_unwrapped();
105 });
106 println!();
107 }
108}