1use super::*;
2use crate::document::TextDocument;
3use crate::document::TextPosition;
4use onestore::document::Format;
5use onestore::page::Page;
6use onestore::page::text::Paragraph;
7use std::sync::Arc;
8use std::{hint::black_box, process::Command};
9
10fn timed<T>(case: &str, phase: &str, sample: usize, operation: impl FnOnce() -> T) -> T {
11 let start = Instant::now();
12 let result = operation();
13 eprintln!(
14 "canvas_profile\t{case}\t{phase}\t{sample}\t{}",
15 start.elapsed().as_nanos()
16 );
17 result
18}
19
20fn resident(case: &str, phase: &str, sample: usize) {
21 let output = Command::new("/bin/ps")
22 .args(["-o", "rss=", "-p", &std::process::id().to_string()])
23 .output()
24 .unwrap();
25 assert!(output.status.success());
26 let kib: u64 = std::str::from_utf8(&output.stdout)
27 .unwrap()
28 .trim()
29 .parse()
30 .unwrap();
31 eprintln!("canvas_rss_kib\t{case}\t{phase}\t{sample}\t{kib}");
32}
33
34fn layout_ids(editor: &CanvasEditor) -> Vec<u64> {
35 editor
36 .outlines()
37 .iter()
38 .flat_map(|outline| outline.layouts().map(|(_, paragraph)| paragraph.text.id()))
39 .collect()
40}
41
42fn editing_cost(
43 case: &str,
44 engine: &mut TextEngine,
45 editor: &mut CanvasEditor,
46 scene: Option<&(PageScene, [f32; 2])>,
47) {
48 let mut viewport = Viewport {
49 size: [1000, 720],
50 scale: 96.0 / 72.0,
51 origin: [48.0; 2],
52 };
53 let initial: Vec<_> = timed(case, "opening_snapshot", 0, || {
54 editor
55 .outlines()
56 .iter()
57 .map(|outline| outline.document().clone())
58 .collect()
59 });
60 let mut access = accessibility::Accessibility::default();
61 let update = timed(case, "ax_first", 0, || {
62 access
63 .update_with(editor, scene, viewport, "Profile", None, None, None)
64 .unwrap()
65 });
66 eprintln!("canvas_count\t{case}\tax_nodes\t0\t{}", update.nodes.len());
67 drop(update);
68 let count: usize = editor
69 .outlines()
70 .iter()
71 .map(|outline| outline.document().nodes().len())
72 .sum();
73 eprintln!("canvas_count\t{case}\tparagraphs\t0\t{count}");
74 for sample in 0..32 {
75 let index = sample % editor.outlines().len();
76 let id = editor.outlines()[index].id;
77 editor.focus_outline(id).unwrap();
78 let paragraph = if sample % 2 == 0 {
79 0
80 } else {
81 editor.active_outline().layouts().last().unwrap().0
82 };
83 editor
84 .select(
85 [TextPosition {
86 paragraph,
87 offset: 0,
88 }; 2]
89 .into(),
90 )
91 .unwrap();
92 let original_layouts = layout_ids(editor);
93 timed(case, "insert", sample, || {
94 editor.insert(engine, "x").unwrap()
95 });
96 let edited_layouts = layout_ids(editor);
97 let changed = original_layouts
98 .iter()
99 .zip(&edited_layouts)
100 .filter(|(a, b)| a != b)
101 .count();
102 assert_eq!(changed, 1);
103 eprintln!("canvas_count\t{case}\tchanged_layouts\t{sample}\t{changed}");
104 let primitives = timed(case, "frame_primitives", sample, || {
105 let primitives = page_primitives(
106 editor,
107 scene,
108 None,
109 None,
110 None,
111 super::tests::paint(true, viewport.scale, 1.0),
112 )
113 .unwrap();
114 black_box(&primitives);
115 primitives.len()
116 });
117 eprintln!("canvas_count\t{case}\tprimitives\t{sample}\t{primitives}");
118 timed(case, "ax_insert", sample, || {
119 black_box(
120 access
121 .update_with(editor, scene, viewport, "Profile", None, None, None)
122 .unwrap(),
123 );
124 });
125 viewport.origin[1] -= 40.0;
126 timed(case, "ax_scroll", sample, || {
127 black_box(
128 access
129 .update_with(editor, scene, viewport, "Profile", None, None, None)
130 .unwrap(),
131 );
132 });
133 assert_eq!(edited_layouts, layout_ids(editor));
134 timed(case, "undo", sample, || {
135 assert!(editor.undo(engine).unwrap())
136 });
137 timed(case, "ax_undo", sample, || {
138 black_box(
139 access
140 .update_with(editor, scene, viewport, "Profile", None, None, None)
141 .unwrap(),
142 );
143 });
144 assert_eq!(editor.active_outline().document(), &initial[index]);
145 }
146 resident(case, "after_edits", 0);
147}
148
149#[test]
150#[ignore = "manual release canvas pipeline timing probe"]
151fn canvas_pipeline_cost() {
152 if let Some(path) = std::env::var_os("CANVAS_TEST_SECTION") {
153 let bytes = timed("native", "read", 0, || std::fs::read(path).unwrap());
154 let title = std::env::var("CANVAS_TEST_PAGE").unwrap();
155 let mut engine = timed("native", "engine", 0, TextEngine::default);
156 if let Some(font) = std::env::var_os("CANVAS_TEST_SUBSTITUTE") {
157 timed("native", "font", 0, || {
158 engine
159 .register_substitute(parley::fontique::Blob::new(Arc::new(
160 std::fs::read(font).unwrap(),
161 )))
162 .unwrap()
163 });
164 }
165 let cycles = std::env::var("CANVAS_PROFILE_CYCLES")
166 .map(|value| value.parse::<usize>().unwrap())
167 .unwrap_or(16);
168 assert!(cycles > 0);
169 for cycle in 0..cycles {
170 let page = timed("native", "parse", cycle, || {
171 let store = onestore::Store::parse(&bytes).unwrap();
172 let index = onestore::RevisionIndex::parse(&store).unwrap();
173 Page::from_document(
174 &onestore::document::Document::parse(&index).unwrap(),
175 &title,
176 )
177 .unwrap()
178 });
179 let (scene, mut editor) = timed("native", "scene", cycle, || {
180 PageScene::from_page(page, &mut engine).unwrap()
181 });
182 let scene = (scene, [0.0; 2]);
183 if cycle == 0 {
184 editing_cost("native", &mut engine, &mut editor, Some(&scene));
185 } else {
186 black_box(
187 page_primitives(
188 &editor,
189 Some(&scene),
190 None,
191 None,
192 None,
193 super::tests::paint(false, 96.0 / 72.0, 1.0),
194 )
195 .unwrap(),
196 );
197 }
198 resident("native", "open", cycle);
199 drop(editor);
200 drop(scene);
201 resident("native", "closed", cycle);
202 }
203 } else {
204 for count in [100, 1_000, 5_000] {
205 let case = format!("synthetic_{count}");
206 let mut engine = timed(&case, "engine", 0, TextEngine::default);
207 let document = timed(&case, "document", 0, || {
208 TextDocument::new(
209 (0..count)
210 .map(|_| {
211 Paragraph::new(
212 "A paragraph of plain text with several words.".into(),
213 Format::default(),
214 )
215 })
216 .collect(),
217 )
218 .unwrap()
219 });
220 let mut editor = timed(&case, "scene", 0, || {
221 CanvasEditor::new(&mut engine, document, 480.0).unwrap()
222 });
223 editing_cost(&case, &mut engine, &mut editor, None);
224 drop(editor);
225 drop(engine);
226 resident(&case, "closed", 0);
227 }
228 }
229}