using System; using System.Collections.Generic; using Clave; namespace Glass.PerformanceTests { /// /// Times moving focus back and forth between two controls whose focus chains hold sets of shortcuts, so what the /// manager does to its chords when the chain changes shows. The control is matching a chord against an idle /// keyboard once per chord of a control, which doesn't change with the manager. /// public static class KeyChainBenchmark { public const string Argument = "--keychain"; public const string Usage = "Usage: Glass.PerformanceTests --keychain [chords per control, comma separated]"; public static readonly int[] DefaultCounts = { 16, 64, 256 }; public const int Ancestors = 4; public const int LeafChords = 4; private static readonly KeyState Modifiers = KeyState.Shift | KeyState.Control | KeyState.Alt; public sealed class ChainControl : Control { public ChordSet Chords { get; set; } public override ChordSet KeyChords => Chords; } private static List TypingKeys() { var returnMe = new List(); foreach (KeyCode key in Enum.GetValues(typeof(KeyCode))) { if (key != KeyCode.None && !Modifiers.IsKeySet(key)) { returnMe.Add(key); } } return returnMe; } /// /// Press chords of each key with each combination of Ctrl, Shift and Alt, as an application's shortcuts are. /// public static ChordSetBuilder Shortcuts(int count, int firstKey) { List keys = TypingKeys(); var returnMe = new ChordSetBuilder(); for (int i = 0; i < count; ++i) { Chord chord = Chord.Press(keys[(firstKey + i / 8) % keys.Count]); if ((i & 1) != 0) { chord = chord.WithAnyHeld(KeyState.Control); } if ((i & 2) != 0) { chord = chord.WithAnyHeld(KeyState.Shift); } if ((i & 4) != 0) { chord = chord.WithAnyHeld(KeyState.Alt); } returnMe.Add(chord); } return returnMe; } private static ChainControl Container(int chords, int firstKey) { return new ChainControl { Chords = Shortcuts(chords, firstKey).Build() }; } private static ChainControl Leaf(int firstKey) { return new ChainControl { Focusable = true, Chords = Shortcuts(LeafChords, firstKey).Build() }; } /// /// Two leaves under one chain of controls, so moving between them keeps every set. Each control binds its own /// keys unless has them all bind the same ones. /// public static (KeyboardManager manager, Control first, Control second) Siblings(int chords, bool shareKeys) { var manager = new KeyboardManager(); ChainControl parent = Container(chords, 0); manager.Controls.Add(parent); ChainControl outer = parent; for (int i = 1; i < Ancestors; ++i) { ChainControl inner = Container(chords, shareKeys ? 0 : i * chords); outer.CanvasChildren.Add(inner); outer = inner; } ChainControl first = Leaf(Ancestors * chords); ChainControl second = Leaf(Ancestors * chords + LeafChords); outer.CanvasChildren.Add(first); outer.CanvasChildren.Add(second); return (manager, first, second); } /// /// Two leaves that share only the outermost control, so moving between them keeps one set. /// public static (KeyboardManager manager, Control first, Control second) Cousins(int chords, bool shareKeys) { var manager = new KeyboardManager(); ChainControl root = Container(chords, 0); manager.Controls.Add(root); var leaves = new Control[2]; for (int branch = 0; branch < 2; ++branch) { ChainControl outer = root; for (int i = 1; i < Ancestors; ++i) { ChainControl inner = Container(chords, shareKeys ? 0 : (branch * Ancestors + i) * chords); outer.CanvasChildren.Add(inner); outer = inner; } ChainControl leaf = Leaf((2 * Ancestors + branch) * chords); outer.CanvasChildren.Add(leaf); leaves[branch] = leaf; } return (manager, leaves[0], leaves[1]); } private static void MeasureShape(string name, (KeyboardManager manager, Control first, Control second) shape) { shape.manager.TrySetFocus(shape.first); Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure(name, () => { shape.manager.TrySetFocus(shape.second); shape.manager.TrySetFocus(shape.first); }))); } /// /// Two text boxes under one container, with game shortcuts in play, so each focus /// move swaps the editing bindings every text box shares. /// public static (KeyboardManager manager, Control first, Control second) TextBoxes(int shortcuts) { var manager = new KeyboardManager(); ChainControl window = Container(LeafChords, 0); manager.Controls.Add(window); TextBox first = TypingBenchmark.NewBox("one", withScrollBar: false); TextBox second = TypingBenchmark.NewBox("two", withScrollBar: false); window.CanvasChildren.Add(first); window.CanvasChildren.Add(second); ChordSetBuilder game = Shortcuts(shortcuts, firstKey: 3); foreach (Chord chord in game) { manager.AddShortcut(chord); } return (manager, first, second); } public static int Run(string[] args) { var counts = new List(DefaultCounts); if (args.Length > 2) { Console.Error.WriteLine(Usage); return 1; } if (args.Length == 2) { counts.Clear(); foreach (string part in args[1].Split(',')) { if (!int.TryParse(part, System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out int count) || count < 1) { Console.Error.WriteLine(Usage); return 1; } counts.Add(count); } } Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure("new TextBox", () => TypingBenchmark.NewBox("one", withScrollBar: false)))); Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure("new TextBoxBehavior", () => new TextBoxBehavior { Multiline = true }))); string bitness = Environment.Is64BitProcess ? "x64" : "x86"; Console.WriteLine($"Focus moved to the other leaf and back, {Ancestors} controls up the chain, {bitness}."); foreach (int count in counts) { Console.WriteLine($"--- {count} chords per control"); Chord probe = Chord.Press(KeyCode.A).WithAnyHeld(KeyState.Control); KeyboardSnapshot idle = default; Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure("control", () => { for (int i = 0; i < count; ++i) { probe.Matches(idle); } }))); MeasureShape("siblings, own keys", Siblings(count, shareKeys: false)); MeasureShape("siblings, shared keys", Siblings(count, shareKeys: true)); MeasureShape("cousins, own keys", Cousins(count, shareKeys: false)); MeasureShape("cousins, shared keys", Cousins(count, shareKeys: true)); MeasureShape("text boxes, no shortcuts", TextBoxes(shortcuts: 0)); MeasureShape("text boxes, shortcuts", TextBoxes(count)); } return 0; } } }