Skip to content

Commit 478932a

Browse files
RetroEditMorilli
andauthored
Controller backend improvements (#4552)
* more ControllerDefinition docs * controller: rm old buggy 1-player special case * rename PlayerCount -> NumControllerGroups more accurately reflects usage and hopefully prevents future confusion * avoid recalculating NumControllerGroups * revert and fix "avoid recalculating NumControllerGroups" * Add LogEntry+LogKey tests * fix logic to pass new tests * remove unused using * add comments and rename for clarity --------- Co-authored-by: Morilli <35152647+Morilli@users.noreply.github.com>
1 parent 227bb47 commit 478932a

8 files changed

Lines changed: 88 additions & 25 deletions

File tree

src/BizHawk.Client.Common/movie/bk2/Bk2Controller.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ private class Bk2ControllerDefinition : ControllerDefinition
100100
public Bk2ControllerDefinition(ControllerDefinition sourceDefinition, string logKey)
101101
: base(sourceDefinition)
102102
{
103-
var groups = logKey.Split(new[] { "#" }, StringSplitOptions.RemoveEmptyEntries);
103+
var groups = logKey.Split('#');
104104

105-
_controlsFromLogKey = groups
106-
.Select(group => group.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries)
105+
_controlsFromLogKey = groups.Skip(1) // ignore everything before the first '#'
106+
.Select(group => group.Split('|', StringSplitOptions.RemoveEmptyEntries)
107107
.Select(buttonname => (buttonname, sourceDefinition.Axes.TryGetValue(buttonname, out var axisSpec) ? axisSpec : (AxisSpec?)null))
108108
.ToArray())
109109
.ToArray();

src/BizHawk.Client.Common/movie/bk2/Bk2LogEntryGenerator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Linq;
21
using System.Text;
32

43
using BizHawk.Emulation.Common;
@@ -27,7 +26,7 @@ public static string GenerateLogKey(ControllerDefinition definition)
2726
{
2827
var sb = new StringBuilder();
2928

30-
foreach (var group in definition.ControlsOrdered.Where(static c => c.Count is not 0))
29+
foreach (var group in definition.ControlsOrdered)
3130
{
3231
sb.Append('#');
3332
foreach ((string buttonName, _) in group)

src/BizHawk.Client.Common/movie/import/LsmvImport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected override void RunImport()
8787
controllerDefinition.BuildMnemonicsCache(VSystemID.Raw.SNES);
8888
_emptyController = new SimpleController(controllerDefinition);
8989
_controller = new SimpleController(controllerDefinition);
90-
_playerCount = controllerDefinition.PlayerCount;
90+
_playerCount = controllerDefinition.ControlsOrdered.Count - 1;
9191

9292
Result.Movie.LogKey = Bk2LogEntryGenerator.GenerateLogKey(controllerDefinition);
9393

src/BizHawk.Client.Common/movie/import/bkm/BkmControllerDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ internal class BkmControllerDefinition(string name) : ControllerDefinition(name)
99
// same as ControllerDefinition.GenOrderedControls, just with Axes after BoolButtons
1010
protected override IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> GenOrderedControls()
1111
{
12-
var ret = new List<(string, AxisSpec?)>[PlayerCount + 1];
12+
var ret = new List<(string, AxisSpec?)>[NumControllerGroups];
1313
for (var i = 0; i < ret.Length; i++) ret[i] = new();
1414
foreach (var btn in BoolButtons) ret[PlayerNumber(btn)].Add((btn, null));
1515
foreach ((string buttonName, var axisSpec) in Axes) ret[PlayerNumber(buttonName)].Add((buttonName, axisSpec));

src/BizHawk.Client.Common/tools/TAStudio/MovieZone.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,15 @@ private void PlaceMacroInternal(IMovie movie, int start)
159159
public FileWriteResult Save(string fileName)
160160
{
161161
// Save the controller definition/LogKey
162-
// Save the controller name and player count. (Only for the user.)
162+
// Save the controller name and controller groups count. (Only for the user)
163163
// Save whether or not the macro should use overlay input, and/or replace
164164

165165
return FileWriter.Write(fileName, (fs) =>
166166
{
167167
using var writer = new StreamWriter(fs);
168168
writer.WriteLine(InputKey);
169169
writer.WriteLine(_movieDefinition.Name);
170-
writer.WriteLine(_movieDefinition.PlayerCount.ToString());
170+
writer.WriteLine(_movieDefinition.ControlsOrdered.Count.ToString());
171171
writer.WriteLine($"{Overlay},{Replace}");
172172

173173
foreach (string line in _log)
@@ -197,7 +197,7 @@ public FileWriteResult Save(string fileName)
197197
{
198198
if (!emuKeys.Contains(macroKey))
199199
{
200-
dialogController.ShowMessageBox($"The selected macro is not compatible with the current emulator core.\nMacro controller: {readText[1]}\nMacro player count: {readText[2]}", "Error");
200+
dialogController.ShowMessageBox($"The selected macro is not compatible with the current emulator core.\nMacro controller: {readText[1]}\nMacro controller groups (\"players\" + 1) count: {readText[2]}", "Error");
201201
return null;
202202
}
203203
}

src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ private void SetUpToolStripColumns()
734734
keysMenus[i] = new ToolStripMenuItem();
735735
}
736736

737-
var playerMenus = new ToolStripMenuItem[Emulator.ControllerDefinition.PlayerCount + 1];
737+
var playerMenus = new ToolStripMenuItem[Emulator.ControllerDefinition.ControlsOrdered.Count];
738738
playerMenus[0] = ColumnsSubMenu;
739739

740740
for (int i = 1; i < playerMenus.Length; i++)

src/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Text.RegularExpressions;
55

66
using BizHawk.Common.CollectionExtensions;
7-
using BizHawk.Common.StringExtensions;
87

98
namespace BizHawk.Emulation.Common
109
{
@@ -20,7 +19,7 @@ public class ControllerDefinition
2019

2120
private IReadOnlyList<IReadOnlyList<(string, AxisSpec?)>>? _orderedControls;
2221

23-
/// <summary>starts with console buttons, then each player's buttons individually</summary>
22+
/// <summary>Starts with console buttons, then each player's buttons individually</summary>
2423
public IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> ControlsOrdered
2524
{
2625
get
@@ -35,6 +34,11 @@ public class ControllerDefinition
3534
public readonly string Name;
3635

3736
private Dictionary<string, char>? _mnemonicsCache;
37+
38+
/// <summary>
39+
/// A mapping between buttons names and their Bk2 mnemonics.
40+
/// (it's only relevant for buttons, not axes)
41+
/// </summary>
3842
public IReadOnlyDictionary<string, char>? MnemonicsCache => _mnemonicsCache;
3943

4044
/// <remarks>
@@ -113,17 +117,17 @@ private void AssertMutable()
113117
if (!_mutable) throw new InvalidOperationException(ERR_MSG);
114118
}
115119

116-
/// <remarks>implementors should include empty lists for empty players, including "player 0", to match this base implementation</remarks>
120+
/// <remarks>Implementors should include empty lists for empty players, including "player 0" (console buttons), to match this base implementation</remarks>
117121
protected virtual IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> GenOrderedControls()
118122
{
119-
var ret = new List<(string, AxisSpec?)>[PlayerCount + 1];
123+
var ret = new List<(string, AxisSpec?)>[NumControllerGroups];
120124
for (var i = 0; i < ret.Length; i++) ret[i] = new();
121125
foreach ((string buttonName, var axisSpec) in Axes) ret[PlayerNumber(buttonName)].Add((buttonName, axisSpec));
122126
foreach (var btn in BoolButtons) ret[PlayerNumber(btn)].Add((btn, null));
123127
return ret;
124128
}
125129

126-
/// <summary>permanently disables the ability to mutate this instance; returns this reference</summary>
130+
/// <summary>Permanently disables the ability to mutate this instance; returns this reference</summary>
127131
public ControllerDefinition MakeImmutable()
128132
{
129133
BoolButtons = BoolButtons.ToImmutableList();
@@ -134,6 +138,12 @@ public ControllerDefinition MakeImmutable()
134138
return this;
135139
}
136140

141+
/// <summary>
142+
/// Get the player number associated with a control (button, analog axis, etc.).
143+
/// Returns 0 for general console buttons not associated with a particular player's control port.
144+
/// (for example, returns 0 for the Power button on NES)
145+
/// For some consoles like (non-linked) Game Boy, this always returns 0.
146+
/// </summary>
137147
public static int PlayerNumber(string buttonName)
138148
{
139149
var match = PlayerRegex.Match(buttonName);
@@ -144,7 +154,14 @@ public static int PlayerNumber(string buttonName)
144154

145155
private static readonly Regex PlayerRegex = new Regex("^P(\\d+) ");
146156

147-
public int PlayerCount
157+
/// <summary>
158+
/// Returns the number of controller groups.
159+
/// Usually is number of players + 1.
160+
/// Returns 1 for both consoles where all control ports are empty and
161+
/// consoles where system buttons aren't separate from "player" buttons,
162+
/// like (non-linked) Game Boy
163+
/// </summary>
164+
public int NumControllerGroups
148165
{
149166
get
150167
{
@@ -153,14 +170,7 @@ public int PlayerCount
153170
.Select(PlayerNumber)
154171
.DefaultIfEmpty(0)
155172
.Max();
156-
157-
if (player > 0)
158-
{
159-
return player;
160-
}
161-
162-
// Hack for things like gameboy/ti-83 as opposed to genesis with no controllers plugged in
163-
return allNames.Exists(static b => b.StartsWithOrdinal("Up")) ? 1 : 0;
173+
return player + 1;
164174
}
165175
}
166176

src/BizHawk.Tests.Client.Common/Movie/LogGeneratorTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,60 @@ public void GenerateLogEntry_Floats()
5555
var actual = Bk2LogEntryGenerator.GenerateLogEntry(_axisController);
5656
Assert.AreEqual("| 100, 100,|", actual);
5757
}
58+
59+
[TestMethod]
60+
public void GenerateLogEntry_NoStupidHack()
61+
{
62+
var upController = new SimpleController(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "Up" } }.MakeImmutable());
63+
upController.Definition.BuildMnemonicsCache(VSystemID.Raw.NES);
64+
65+
var logEntry = Bk2LogEntryGenerator.GenerateLogEntry(upController);
66+
Assert.AreEqual("|.|", logEntry);
67+
}
68+
69+
[TestMethod]
70+
public void GenerateLogEntry_EmptyPlayerGroups()
71+
{
72+
var upController = new SimpleController(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "P2 Up" } }.MakeImmutable());
73+
upController.Definition.BuildMnemonicsCache(VSystemID.Raw.NES);
74+
75+
var logEntry = Bk2LogEntryGenerator.GenerateLogEntry(upController);
76+
Assert.AreEqual("|||.|", logEntry);
77+
}
78+
79+
[TestMethod]
80+
public void GenerateLogKey_EmptyPlayerGroups()
81+
{
82+
var upControllerDefinition = new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "P2 Up" } }.MakeImmutable();
83+
upControllerDefinition.BuildMnemonicsCache(VSystemID.Raw.NES);
84+
85+
var logKey = Bk2LogEntryGenerator.GenerateLogKey(upControllerDefinition);
86+
Assert.AreEqual("###P2 Up|", logKey);
87+
}
88+
89+
[TestMethod]
90+
public void GenerateLogEntry_Bk2Controller()
91+
{
92+
var simpleController = new SimpleController(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "P1 Up", "P3 A" } }.MakeImmutable());
93+
simpleController.Definition.BuildMnemonicsCache(VSystemID.Raw.NES);
94+
95+
var originalLogEntry = Bk2LogEntryGenerator.GenerateLogEntry(simpleController);
96+
var originalLogKey = Bk2LogEntryGenerator.GenerateLogKey(simpleController.Definition);
97+
98+
// just for safety, should be covered by the above tests already
99+
Assert.AreEqual("||.||.|", originalLogEntry);
100+
Assert.AreEqual("##P1 Up|##P3 A|", originalLogKey);
101+
102+
// ensure a Bk2Controller constructed with ControllerDefinition and LogKey
103+
// generates the exact same outputs as the original SimpleController
104+
Bk2Controller bk2Controller = new Bk2Controller(simpleController.Definition, originalLogKey);
105+
106+
var newLogEntry = Bk2LogEntryGenerator.GenerateLogEntry(bk2Controller);
107+
Assert.AreEqual(originalLogEntry, newLogEntry);
108+
109+
var newLogKey = Bk2LogEntryGenerator.GenerateLogKey(bk2Controller.Definition);
110+
Assert.AreEqual(originalLogKey, newLogKey);
111+
}
58112
#pragma warning restore BHI1600
59113
}
60114
}

0 commit comments

Comments
 (0)