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