using System;
using System.Collections.Generic;
namespace Clave.PerformanceTests
{
///
/// Rows for what a ChordArbiter does after it is built, and for making chords. A row that handles a handful of
/// chords but grows with the number in the arbiter is scanning the whole arbiter.
///
public static class ArbiterOperations
{
private const int SmallMountSize = 4;
// Far from the keys the background chords start on, so the few chords rank against little of them.
private const int SmallMountFirstKey = 70;
private static ChordSet SmallSet()
{
return ChordSet.Of(ChordSetBuilding.Shortcuts(SmallMountSize, KeySpace.Physical,
firstKey: SmallMountFirstKey));
}
private static ChordArbiter Background(int count)
{
var returnMe = new ChordArbiter();
ChordMount mount = returnMe.Mount();
foreach (Chord chord in ChordSetBuilding.Shortcuts(count, KeySpace.Physical))
{
returnMe.Add(chord, mount);
}
return returnMe;
}
private static BuildScenario CountOnASmallMount(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount(SmallSet());
return new BuildScenario("CountOn, mount of 4", () =>
{
if (arbiter.CountOn(mount) != SmallMountSize)
{
throw new InvalidOperationException("The mount lost a chord.");
}
});
}
private static BuildScenario MountingAndUnmountingASmallSet(int count)
{
ChordArbiter arbiter = Background(count);
ChordSet set = SmallSet();
return new BuildScenario("Mount and unmount a set of 4", () =>
{
arbiter.Unmount(arbiter.Mount(set));
});
}
private static BuildScenario AddingAndRemovingOneChord(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount();
Chord chord = ChordSetBuilding.Shortcuts(1, KeySpace.Physical, firstKey: SmallMountFirstKey)[0];
return new BuildScenario("Add and Remove one chord", () =>
{
arbiter.Add(chord, mount);
arbiter.Remove(chord, mount);
});
}
private static BuildScenario RemovingASmallSetOneByOne(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount();
ChordSet set = SmallSet();
return new BuildScenario("Add 4, Remove 4 one by one", () =>
{
foreach (Chord chord in set)
{
arbiter.Add(chord, mount);
}
foreach (Chord chord in set)
{
arbiter.Remove(chord, mount);
}
});
}
private static BuildScenario RemovingASmallSetAtOnce(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount();
ChordSet set = SmallSet();
return new BuildScenario("Add 4, RemoveAll 4", () =>
{
foreach (Chord chord in set)
{
arbiter.Add(chord, mount);
}
arbiter.RemoveAll(set, mount);
});
}
private static BuildScenario ChangingThePriorityOfOneChord(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount();
Chord chord = ChordSetBuilding.Shortcuts(1, KeySpace.Physical, firstKey: SmallMountFirstKey)[0];
arbiter.Add(chord, mount);
int priority = 0;
return new BuildScenario("SetPriority of one chord", () =>
{
priority ^= 1;
arbiter.SetPriority(chord, mount, priority);
});
}
private static BuildScenario ChangingThePriorityOfASmallMount(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount(SmallSet());
int priority = 0;
return new BuildScenario("SetPriority of a mount of 4", () =>
{
priority ^= 1;
arbiter.SetPriority(mount, priority);
});
}
// What a control does on each key event: asks about every one of its chords.
private static BuildScenario AskingAboutEveryChord(string name, int count, bool pressAKey)
{
var arbiter = new ChordArbiter();
ChordMount mount = arbiter.Mount();
List chords = ChordSetBuilding.Shortcuts(count, KeySpace.Physical);
foreach (Chord chord in chords)
{
arbiter.Add(chord, mount);
}
KeyboardSnapshot keyboard = default;
if (pressAKey)
{
var key = new KeyState(ChordSetBuilding.TypingKeys()[0]);
keyboard = new KeyboardSnapshot(new KeySpaceSnapshot(key, key, default), default);
}
return new BuildScenario(name, () =>
{
int fired = 0;
for (int i = 0; i < chords.Count; ++i)
{
if (arbiter.ShouldFire(chords[i], mount, keyboard))
{
++fired;
}
}
if (fired != (pressAKey ? 1 : 0))
{
throw new InvalidOperationException("The wrong number of chords fired.");
}
});
}
// Removal is timed against the same adds alone, so a row's removal cost is its difference from "Fill".
private enum Teardown
{
None,
OneByOne,
OneByOneBackwards,
AllAtOnce,
}
private static BuildScenario FillingAndTearingDown(string name, int count, Teardown teardown)
{
List chords = ChordSetBuilding.Shortcuts(count, KeySpace.Physical);
ChordSet set = ChordSet.Of(chords);
return new BuildScenario(name, () =>
{
var arbiter = new ChordArbiter();
ChordMount mount = arbiter.Mount();
for (int i = 0; i < chords.Count; ++i)
{
arbiter.Add(chords[i], mount);
}
if (teardown == Teardown.OneByOne)
{
for (int i = 0; i < chords.Count; ++i)
{
arbiter.Remove(chords[i], mount);
}
}
else if (teardown == Teardown.OneByOneBackwards)
{
for (int i = chords.Count - 1; i >= 0; --i)
{
arbiter.Remove(chords[i], mount);
}
}
else if (teardown == Teardown.AllAtOnce)
{
arbiter.RemoveAll(set, mount);
}
});
}
// An arbiter of one chord ranks nothing, so what is left is the analysis done for each entry.
private static BuildScenario AnalysingEachChordAlone(string name, int count, KeySpace space)
{
List chords = ChordSetBuilding.Shortcuts(count, space);
return new BuildScenario(name, () =>
{
for (int i = 0; i < chords.Count; ++i)
{
new ChordArbiter().Mount(chords[i]);
}
});
}
// A chord on any of several keys pins none of them, so it is ranked against every chord in play.
private static List PinningNothing(int count)
{
List keys = ChordSetBuilding.TypingKeys();
var returnMe = new List(count);
for (int i = 0; i < count; ++i)
{
var group = new KeyState(keys[i % keys.Count], keys[(i * 7 + 3) % keys.Count],
keys[(i * 13 + 5) % keys.Count]);
returnMe.Add(Chord.PressAny(group));
}
return returnMe;
}
// Groups of keys drawn from a small window, as in a text box where every chord reads most of the same keys.
private static List PinningNothingOverlapping(int count)
{
List keys = ChordSetBuilding.TypingKeys();
const int window = 40;
const int groupSize = 8;
var returnMe = new List(count);
for (int i = 0; i < count; ++i)
{
KeyState group = default;
for (int member = 0; member < groupSize; ++member)
{
group |= new KeyState(keys[(i * 5 + member * (2 + i % 5)) % window]);
}
returnMe.Add(Chord.PressAny(group));
}
return returnMe;
}
private static BuildScenario AddingOverlappingChordsThatPinNothing(int count)
{
List chords = PinningNothingOverlapping(count);
return new BuildScenario("Add overlapping chords that pin nothing", () =>
{
var arbiter = new ChordArbiter();
ChordMount mount = arbiter.Mount();
for (int i = 0; i < chords.Count; ++i)
{
arbiter.Add(chords[i], mount);
}
});
}
private static BuildScenario AddingAndRemovingOneChordThatPinsNothing(int count)
{
ChordArbiter arbiter = Background(count);
ChordMount mount = arbiter.Mount();
Chord chord = PinningNothing(1)[0];
return new BuildScenario("Add and Remove a chord that pins nothing", () =>
{
arbiter.Add(chord, mount);
arbiter.Remove(chord, mount);
});
}
private static BuildScenario AddingChordsThatPinNothing(int count)
{
List chords = PinningNothing(count);
return new BuildScenario("Add chords that pin nothing", () =>
{
var arbiter = new ChordArbiter();
ChordMount mount = arbiter.Mount();
for (int i = 0; i < chords.Count; ++i)
{
arbiter.Add(chords[i], mount);
}
});
}
private static BuildScenario ConstructingChords(string name, int count, Func make)
{
List keys = ChordSetBuilding.TypingKeys();
return new BuildScenario(name, () =>
{
for (int i = 0; i < count; ++i)
{
make(keys[i % keys.Count]);
}
});
}
public static List Scenarios(int count)
{
// HoldAny of every typing key is slow enough to swamp the rest, so it runs a sixteenth as often.
int wide = Math.Max(1, count / 16);
return new List
{
CountOnASmallMount(count),
MountingAndUnmountingASmallSet(count),
AddingAndRemovingOneChord(count),
RemovingASmallSetOneByOne(count),
RemovingASmallSetAtOnce(count),
ChangingThePriorityOfOneChord(count),
ChangingThePriorityOfASmallMount(count),
AskingAboutEveryChord("ShouldFire each chord, a key pressed", count, pressAKey: true),
AskingAboutEveryChord("ShouldFire each chord, idle", count, pressAKey: false),
FillingAndTearingDown("Fill", count, Teardown.None),
FillingAndTearingDown("Fill, Remove each in order", count, Teardown.OneByOne),
FillingAndTearingDown("Fill, Remove each backwards", count, Teardown.OneByOneBackwards),
FillingAndTearingDown("Fill, RemoveAll", count, Teardown.AllAtOnce),
AnalysingEachChordAlone("Analyse each chord, arbiter of one", count, KeySpace.Physical),
AnalysingEachChordAlone("Analyse each chord, layout", count, KeySpace.Layout),
AddingAndRemovingOneChordThatPinsNothing(count),
AddingChordsThatPinNothing(count),
AddingOverlappingChordsThatPinNothing(count),
ConstructingChords("Chord.Press", count, key => Chord.Press(key)),
ConstructingChords("Chord.PressOrRepeat", count, key => Chord.PressOrRepeat(key)),
ConstructingChords("Press.WithAnyHeld x2", count, key => Chord.Press(key)
.WithAnyHeld(KeyState.Control).WithAnyHeld(KeyState.Shift)),
ConstructingChords("HoldAny(Typing), 1/16 as many", wide, key => Chord.HoldAny(KeyState.Typing)),
};
}
}
}