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(editor, scene, viewport, "Profile", 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 super::tests::paint(true, viewport.scale, 1.0),
111 )
112 .unwrap();
113 black_box(&primitives);
114 primitives.len()
115 });
116 eprintln!("canvas_count\t{case}\tprimitives\t{sample}\t{primitives}");
117 timed(case, "ax_insert", sample, || {
118 black_box(
119 access
120 .update(editor, scene, viewport, "Profile", None, None)
121 .unwrap(),
122 );
123 });
124 viewport.origin[1] -= 40.0;
125 timed(case, "ax_scroll", sample, || {
126 black_box(
127 access
128 .update(editor, scene, viewport, "Profile", None, None)
129 .unwrap(),
130 );
131 });
132 assert_eq!(edited_layouts, layout_ids(editor));
133 timed(case, "undo", sample, || {
134 assert!(editor.undo(engine).unwrap())
135 });
136 timed(case, "ax_undo", sample, || {
137 black_box(
138 access
139 .update(editor, scene, viewport, "Profile", None, None)
140 .unwrap(),
141 );
142 });
143 assert_eq!(editor.active_outline().document(), &initial[index]);
144 }
145 resident(case, "after_edits", 0);
146}
147
148#[test]
149#[ignore = "manual release canvas pipeline timing probe"]
150fn canvas_pipeline_cost() {
151 if let Some(path) = std::env::var_os("CANVAS_TEST_SECTION") {
152 let bytes = timed("native", "read", 0, || std::fs::read(path).unwrap());
153 let title = std::env::var("CANVAS_TEST_PAGE").unwrap();
154 let mut engine = timed("native", "engine", 0, TextEngine::default);
155 if let Some(font) = std::env::var_os("CANVAS_TEST_SUBSTITUTE") {
156 timed("native", "font", 0, || {
157 engine
158 .register_substitute(parley::fontique::Blob::new(Arc::new(
159 std::fs::read(font).unwrap(),
160 )))
161 .unwrap()
162 });
163 }
164 let cycles = std::env::var("CANVAS_PROFILE_CYCLES")
165 .map(|value| value.parse::<usize>().unwrap())
166 .unwrap_or(16);
167 assert!(cycles > 0);
168 for cycle in 0..cycles {
169 let page = timed("native", "parse", cycle, || {
170 let store = onestore::Store::parse(&bytes).unwrap();
171 let index = onestore::RevisionIndex::parse(&store).unwrap();
172 Page::from_document(
173 &onestore::document::Document::parse(&index).unwrap(),
174 &title,
175 )
176 .unwrap()
177 });
178 let (scene, mut editor) = timed("native", "scene", cycle, || {
179 PageScene::from_page(page, &mut engine).unwrap()
180 });
181 let scene = (scene, [0.0; 2]);
182 if cycle == 0 {
183 editing_cost("native", &mut engine, &mut editor, Some(&scene));
184 } else {
185 black_box(
186 page_primitives(
187 &editor,
188 Some(&scene),
189 None,
190 None,
191 super::tests::paint(false, 96.0 / 72.0, 1.0),
192 )
193 .unwrap(),
194 );
195 }
196 resident("native", "open", cycle);
197 drop(editor);
198 drop(scene);
199 resident("native", "closed", cycle);
200 }
201 } else {
202 for count in [100, 1_000, 5_000] {
203 let case = format!("synthetic_{count}");
204 let mut engine = timed(&case, "engine", 0, TextEngine::default);
205 let document = timed(&case, "document", 0, || {
206 TextDocument::new(
207 (0..count)
208 .map(|_| {
209 Paragraph::new(
210 "A paragraph of plain text with several words.".into(),
211 Format::default(),
212 )
213 })
214 .collect(),
215 )
216 .unwrap()
217 });
218 let mut editor = timed(&case, "scene", 0, || {
219 CanvasEditor::new(&mut engine, document, 480.0).unwrap()
220 });
221 editing_cost(&case, &mut engine, &mut editor, None);
222 drop(editor);
223 drop(engine);
224 resident(&case, "closed", 0);
225 }
226 }
227}