using System; using System.Globalization; using Amphora; using Glass.Editing; using Glass.Typesetting; namespace Glass.PerformanceTests { /// /// Times what a multiline box does with a text that is one enormous line: scrolling to the caret, moving by lines /// and pages, and typing. /// public static class ViewportBenchmark { public const string Argument = "--viewport"; public const string SingleLineArgument = "--single-line"; public const string NoWrapArgument = "--no-wrap"; public const string RightArgument = "--right"; public const string CenterArgument = "--center"; public const string Usage = "Usage: Glass.PerformanceTests --viewport [text size in chars] " + "[--single-line | --no-wrap [--right | --center]]"; public const int DefaultSize = 100000000; public const int ViewWidth = 300; public const int ViewHeight = 200; public const int SliceChars = 65536; private static void Report(TextViewport viewport, string name, Action operation) { long before = viewport.CharsMeasured; operation(); long measured = viewport.CharsMeasured - before; Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure(name, operation)) + string.Format(CultureInfo.InvariantCulture, " {0:N0} chars laid out", measured)); } private static int RunSingleLine(TextViewport viewport, PagedText paged) { Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "--- {0:N0} chars", paged.Length)); int middle = paged.Length / 2; Report(viewport, "caret to the middle", () => viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false)); Report(viewport, "caret to the end", () => viewport.ScrollToCaret(paged, paged.Length, caretAtLineEnd: false)); Report(viewport, "caret to the start", () => viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false)); Report(viewport, "key + scroll", () => { paged.Replace(middle, 0, "x"); viewport.ScrollToCaret(paged, middle + 1, caretAtLineEnd: false); paged.Replace(middle, 1, ""); viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false); }); return 0; } private static int RunNoWrap(TextViewport viewport, PagedText paged) { Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "--- {0:N0} chars, content width reckoned {1:N0}", paged.Length, viewport.ContentWidth(paged))); var watch = System.Diagnostics.Stopwatch.StartNew(); while (viewport.CountMore(paged, SliceChars)) { } watch.Stop(); long width = viewport.ContentWidth(paged); Console.WriteLine(string.Format(CultureInfo.InvariantCulture, " counted in {0:F2} s, content width {1:N0} px", watch.Elapsed.TotalSeconds, width)); int middle = paged.Length / 2; var bands = new BandsMaterial(new SolidColorMaterial()); Report(viewport, "scroll to caret", () => viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false)); Report(viewport, "scroll to the end", () => viewport.ScrollToCaret(paged, paged.Length, caretAtLineEnd: false)); Report(viewport, "scroll to the start", () => viewport.ScrollToCaret(paged, 0, caretAtLineEnd: false)); Report(viewport, "scroll to 3/4", () => viewport.ScrollToX(paged, width / 4 * 3)); Report(viewport, "scroll 3 columns + back", () => { viewport.ScrollToX(paged, width / 2 + 3 * viewport.Column); viewport.ScrollToX(paged, width / 2); }); Report(viewport, "line edge", () => viewport.LineEdge(paged, middle, false, end: true, out bool _)); Report(viewport, "line down", () => viewport.LineTarget(paged, middle, false, lines: 1, forward: true, version: 1, out bool _)); Report(viewport, "page down", () => viewport.PageTarget(paged, middle, false, forward: true, version: 1, out bool _)); viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false); Report(viewport, "caret, bands and click", () => { viewport.CaretAt(middle, atLineEnd: false); bands.Clear(); viewport.AddBands(bands, middle - 5, middle + 50); viewport.IndexAt(ViewWidth / 2, 5, out bool _, out int _); }); Report(viewport, "key + scroll", () => { paged.Replace(middle, 0, "x"); viewport.NoteEdit(paged, middle, 0, 1); viewport.ScrollToCaret(paged, middle + 1, caretAtLineEnd: false); paged.Replace(middle, 1, ""); viewport.NoteEdit(paged, middle, 1, 0); viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false); }); ReportComposition(viewport, paged); return 0; } // Types a word the way an input method does, again and again, over a few chars of the middle of the text. private static void ReportComposition(TextViewport viewport, PagedText paged) { int middle = paged.Length / 2; var composition = new SplicedText(); string composed = "composing"; Report(viewport, "composition key + scroll", () => { composition.Set(paged, middle, middle + 3, composed); viewport.ScrollToCaret(composition, middle + composed.Length, caretAtLineEnd: false); composed = composed.Substring(1) + composed[0]; }); Report(viewport, "composition, caret and click", () => { viewport.CaretAt(middle + 2, atLineEnd: false); viewport.IndexAt(ViewWidth / 2, 5, out bool _, out int _); viewport.ContentWidth(composition); }); } public static int Run(string[] args) { int size = DefaultSize; bool singleLine = false; bool noWrap = false; bool sizeGiven = false; Alignment.Horizontal alignment = Alignment.Horizontal.Left; for (int i = 1; i < args.Length; ++i) { if (args[i] == SingleLineArgument) { singleLine = true; } else if (args[i] == NoWrapArgument) { noWrap = true; } else if (args[i] == RightArgument) { alignment = Alignment.Horizontal.Right; } else if (args[i] == CenterArgument) { alignment = Alignment.Horizontal.Center; } else if (!sizeGiven && int.TryParse(args[i], NumberStyles.None, CultureInfo.InvariantCulture, out size) && size >= 1) { sizeGiven = true; } else { Console.Error.WriteLine(Usage); return 1; } } if ((singleLine && noWrap) || (alignment != Alignment.Horizontal.Left && !noWrap)) { Console.Error.WriteLine(Usage); return 1; } string bitness = Environment.Is64BitProcess ? "x64" : "x86"; string kind = "one hard line"; if (singleLine) { kind = "a box of one line"; } else if (noWrap) { kind = "one hard line that doesn't wrap"; } Console.WriteLine($"TextViewport, {bitness}, {kind}, {ViewWidth} x {ViewHeight}."); string chunk = TypingBenchmark.TextOfAbout(PagedBenchmark.ChunkSize).Replace('\r', ' ').Replace('\n', ' '); var paged = new PagedText(); while (paged.Length < size) { paged.Replace(paged.Length, 0, chunk); } var viewport = new TextViewport(); var style = new TextMaterialStyle { Font = SampleFonts.Philosopher18, HorizontalAlignment = alignment }; viewport.Configure(style, !singleLine, ViewWidth, singleLine ? (int?)null : ViewHeight, wrap: !noWrap, caretWidth: 2); if (singleLine) { return RunSingleLine(viewport, paged); } if (noWrap) { return RunNoWrap(viewport, paged); } var watch = System.Diagnostics.Stopwatch.StartNew(); while (viewport.CountMore(paged, SliceChars)) { } watch.Stop(); Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "--- {0:N0} chars, counted in {1:F2} s", paged.Length, watch.Elapsed.TotalSeconds)); int middle = paged.Length / 2; Report(viewport, "scroll to caret", () => viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false)); Report(viewport, "line down", () => viewport.LineTarget(paged, middle, false, lines: 1, forward: true, version: 1, out bool _)); Report(viewport, "page down", () => viewport.PageTarget(paged, middle, false, forward: true, version: 1, out bool _)); Report(viewport, "line edge", () => viewport.LineEdge(paged, middle, false, end: true, out bool _)); Report(viewport, "scroll to line", () => viewport.ScrollToLine(paged, viewport.TotalLines(paged) / 2)); Report(viewport, "scroll 3 + back", () => { viewport.ScrollByLines(paged, 3); viewport.ScrollByLines(paged, -3); }); Report(viewport, "scroll 1000 + back", () => { viewport.ScrollByLines(paged, 1000); viewport.ScrollByLines(paged, -1000); }); Report(viewport, "key + scroll", () => { paged.Replace(middle, 0, "x"); viewport.NoteEdit(paged, middle, 0, 1); viewport.ScrollToCaret(paged, middle + 1, caretAtLineEnd: false); paged.Replace(middle, 1, ""); viewport.NoteEdit(paged, middle, 1, 0); viewport.ScrollToCaret(paged, middle, caretAtLineEnd: false); }); var composition = new SplicedText(); string composed = "composing"; Report(viewport, "composition key + scroll", () => { composition.Set(paged, middle, middle, composed); viewport.ScrollToCaret(composition, middle + composed.Length, caretAtLineEnd: false); composed = composed.Substring(1) + composed[0]; }); Report(viewport, "composition, line down", () => { composition.Set(paged, middle, middle, composed); viewport.LineTarget(composition, middle + composed.Length, false, lines: 1, forward: true, version: 1, out bool _); }); return 0; } } }