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;
}
}
}