diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs index 364dfdd603f..886cb32d599 100644 --- a/src/BizHawk.Client.Common/config/Config.cs +++ b/src/BizHawk.Client.Common/config/Config.cs @@ -18,50 +18,15 @@ public class Config { public static string ControlDefaultPath => Path.Combine(PathUtils.ExeDirectoryPath, "defctrl.json"); - /// - /// CoreNames[0] is the default (out-of-the-box) core.
- /// AppliesTo are concatenated to make the submenu's label, and - /// Config.PreferredCores[AppliesTo[0]] (lookup on global instance) determines which option is shown as checked.
- /// The order within submenus and the order of the submenus themselves are determined by the declaration order here. - ///
- public static readonly IReadOnlyList<(string[] AppliesTo, string[] CoreNames)> CorePickerUIData = new List<(string[], string[])> - { - ([ VSystemID.Raw.A26 ], - [ CoreNames.Atari2600Hawk, CoreNames.Stella ]), - ([ VSystemID.Raw.Satellaview ], - [ CoreNames.Bsnes115, CoreNames.SubBsnes115 ]), - ([ VSystemID.Raw.GB, VSystemID.Raw.GBC ], - [ CoreNames.Gambatte, CoreNames.Sameboy, CoreNames.GbHawk, CoreNames.SubGbHawk ]), - ([ VSystemID.Raw.GBL ], - [ CoreNames.GambatteLink, CoreNames.GBHawkLink, CoreNames.GBHawkLink3x, CoreNames.GBHawkLink4x ]), - ([ VSystemID.Raw.SGB ], - [ CoreNames.Gambatte, CoreNames.Bsnes115, CoreNames.SubBsnes115, CoreNames.Bsnes ]), - ([ VSystemID.Raw.GEN ], - [ CoreNames.Gpgx, CoreNames.PicoDrive ]), - ([ VSystemID.Raw.N64 ], - [ CoreNames.Mupen64Plus, CoreNames.Ares64 ]), - ([ VSystemID.Raw.NES ], - [ CoreNames.QuickNes, CoreNames.NesHawk, CoreNames.SubNesHawk ]), - ([ VSystemID.Raw.PCE, VSystemID.Raw.PCECD, VSystemID.Raw.SGX, VSystemID.Raw.SGXCD ], - [ CoreNames.TurboNyma, CoreNames.HyperNyma, CoreNames.PceHawk ]), - ([ VSystemID.Raw.PSX ], - [ CoreNames.Nymashock, CoreNames.Octoshock ]), - ([ VSystemID.Raw.SMS, VSystemID.Raw.GG, VSystemID.Raw.SG ], - [ CoreNames.Gpgx, CoreNames.SMSHawk ]), - ([ VSystemID.Raw.SNES ], - [ CoreNames.Snes9X, CoreNames.Bsnes115, CoreNames.SubBsnes115, CoreNames.Faust, CoreNames.Bsnes ]), - ([ VSystemID.Raw.TI83 ], - [ CoreNames.Emu83, CoreNames.TI83Hawk ]), - }; - public static Dictionary GenDefaultCorePreferences() { Dictionary dict = new(); - foreach (var (appliesTo, coreNames) in CorePickerUIData) + foreach(var (systemId, cores) in CoreInventory.Instance.AllCores) { - var defaultCore = coreNames[0]; - foreach (var sysID in appliesTo) dict[sysID] = defaultCore; + if (cores.Count > 1) + dict[systemId] = cores.Find(core => core.Priority == CorePriority.DefaultPreference)?.Name; } + return dict; } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 2d9ccd8ec28..f251d438664 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -75,30 +75,32 @@ private void MainForm_Load(object sender, EventArgs e) } } - foreach (var (appliesTo, coreNames) in Config.CorePickerUIData) + foreach (var (systemIds, coreNames) in CoreInventory.Instance.SystemGroups + .Where(tuple => tuple.CoreNames.Count >= 2) + .OrderBy(tuple => tuple.SystemIds[0])) { - var submenu = new ToolStripMenuItem { Text = string.Join(" | ", appliesTo) }; + var submenu = new ToolStripMenuItem { Text = string.Join(" | ", systemIds) }; submenu.DropDownItems.AddRange(coreNames.Select(coreName => { var entry = new ToolStripMenuItem { Text = coreName }; entry.Click += (_, _) => { string currentCoreName = Emulator.Attributes().CoreName; if (coreName != currentCoreName && coreNames.Contains(currentCoreName)) FlagNeedsReboot(); - foreach (string system in appliesTo) - Config.PreferredCores[system] = coreName; + foreach (string systemId in systemIds) + Config.PreferredCores[systemId] = coreName; }; return (ToolStripItem) entry; }).ToArray()); submenu.DropDownOpened += (openedSender, _1) => { - _ = Config.PreferredCores.TryGetValue(appliesTo[0], out var preferred); + _ = Config.PreferredCores.TryGetValue(systemIds[0], out var preferred); if (!coreNames.Contains(preferred)) { // invalid --> default (doing this here rather than when reading config file to allow for hacked-in values, though I'm not sure if that could do anything at the moment --yoshi) var defaultCore = coreNames[0]; Console.WriteLine($"setting preferred core for {submenu.Text} to {defaultCore} (was {preferred ?? "null"})"); preferred = defaultCore; - foreach (var sysID in appliesTo) Config.PreferredCores[sysID] = preferred; + foreach (var sysID in systemIds) Config.PreferredCores[sysID] = preferred; } foreach (ToolStripMenuItem entry in ((ToolStripMenuItem) openedSender).DropDownItems) entry.Checked = entry.Text == preferred; }; diff --git a/src/BizHawk.Emulation.Cores/Calculators/Emu83/Emu83.cs b/src/BizHawk.Emulation.Cores/Calculators/Emu83/Emu83.cs index b1c8153b489..4fd11bd03ce 100644 --- a/src/BizHawk.Emulation.Cores/Calculators/Emu83/Emu83.cs +++ b/src/BizHawk.Emulation.Cores/Calculators/Emu83/Emu83.cs @@ -26,7 +26,7 @@ static Emu83() private readonly TI83Disassembler _disassembler = new(); - [CoreConstructor(VSystemID.Raw.TI83)] + [CoreConstructor(VSystemID.Raw.TI83, Priority = CorePriority.DefaultPreference)] public Emu83(CoreLoadParameters lp) { try diff --git a/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs b/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs index 4704d1e42f0..61d8a866a23 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs @@ -18,7 +18,7 @@ internal static class RomChecksums public const string Tapper = "SHA1:E986E1818E747BEB9B33CE4DFF1CDC6B55BDB620"; } - [CoreConstructor(VSystemID.Raw.A26)] + [CoreConstructor(VSystemID.Raw.A26, Priority = CorePriority.DefaultPreference)] public Atari2600(GameInfo game, byte[] rom, Atari2600.A2600Settings settings, Atari2600.A2600SyncSettings syncSettings) { var ser = new BasicServiceProvider(this); diff --git a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs index 3ba961c73b9..0ae18007d9c 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs @@ -34,10 +34,10 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm) private readonly LibHyperNyma _hyperNyma; private readonly bool _hasCds; - [CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.Low)] + [CoreConstructor(VSystemID.Raw.PCE)] + [CoreConstructor(VSystemID.Raw.SGX)] + [CoreConstructor(VSystemID.Raw.PCECD)] + [CoreConstructor(VSystemID.Raw.SGXCD)] public HyperNyma(CoreLoadParameters lp) : base(lp.Comm, VSystemID.Raw.PCE, "PC Engine Controller", lp.Settings, lp.SyncSettings) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs index ca7cb4b3fac..56b6d28f851 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs @@ -36,10 +36,10 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm) private readonly LibTurboNyma _turboNyma; private readonly bool _hasCds; - [CoreConstructor(VSystemID.Raw.PCE)] - [CoreConstructor(VSystemID.Raw.SGX)] - [CoreConstructor(VSystemID.Raw.PCECD)] - [CoreConstructor(VSystemID.Raw.SGXCD)] + [CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.DefaultPreference)] + [CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.DefaultPreference)] + [CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.DefaultPreference)] + [CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.DefaultPreference)] public TurboNyma(CoreLoadParameters lp) : base(lp.Comm, VSystemID.Raw.PCE, "PC Engine Controller", lp.Settings, lp.SyncSettings) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index 0109ea1adb8..cd18bf497dc 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -17,8 +17,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES [ServiceNotApplicable(new[] { typeof(IDriveLight) })] public partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable, IBSNESForGfxDebugger, IBoardInfo { - [CoreConstructor(VSystemID.Raw.Satellaview)] - [CoreConstructor(VSystemID.Raw.SGB)] + [CoreConstructor(VSystemID.Raw.Satellaview, Priority = CorePriority.DefaultPreference)] + [CoreConstructor(VSystemID.Raw.SGB, Priority = CorePriority.DefaultPreference)] [CoreConstructor(VSystemID.Raw.SNES)] public BsnesCore(CoreLoadParameters loadParameters) : this(loadParameters, false) { } public BsnesCore(CoreLoadParameters loadParameters, bool subframe = false) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 2c841e8c224..8b2ffad6c6f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -19,8 +19,8 @@ public partial class Gameboy : IInputPollable, IRomInfo, IGameboyCommon, ICycleT /// HACK disables BIOS requirement if the environment looks like a test runner... private static readonly bool TestromsBIOSDisableHack = Type.GetType("Microsoft.VisualStudio.TestTools.UnitTesting.Assert, Microsoft.VisualStudio.TestPlatform.TestFramework") is not null; - [CoreConstructor(VSystemID.Raw.GB)] - [CoreConstructor(VSystemID.Raw.GBC)] + [CoreConstructor(VSystemID.Raw.GB, Priority = CorePriority.DefaultPreference)] + [CoreConstructor(VSystemID.Raw.GBC, Priority = CorePriority.DefaultPreference)] [CoreConstructor(VSystemID.Raw.SGB)] public Gameboy(CoreComm comm, IGameInfo game, byte[] file, GambatteSettings settings, GambatteSyncSettings syncSettings, bool deterministic) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs index e66dc3b3c89..ef72bdabd93 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [ServiceNotApplicable(new[] { typeof(IDriveLight) })] public partial class GambatteLink : ILinkable, ILinkedGameBoyCommon, IRomInfo { - [CoreConstructor(VSystemID.Raw.GBL)] + [CoreConstructor(VSystemID.Raw.GBL, Priority = CorePriority.DefaultPreference)] public GambatteLink(CoreLoadParameters lp) { if (lp.Roms.Count < MIN_PLAYERS || lp.Roms.Count > MAX_PLAYERS) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs index db5a6dc4f2b..bcd4cffb8d9 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs @@ -18,7 +18,7 @@ public partial class N64 : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPo /// Rom that should be loaded /// rom data with consistent endianness/order /// N64SyncSettings object - [CoreConstructor(VSystemID.Raw.N64)] + [CoreConstructor(VSystemID.Raw.N64, Priority = CorePriority.DefaultPreference)] public N64(GameInfo game, byte[] file, byte[] rom, N64Settings settings, N64SyncSettings syncSettings) { if (OSTailoredCode.IsUnixHost) throw new NotImplementedException(); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 9acad9ba4df..760fc154a0b 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -28,7 +28,7 @@ static QuickNES() QN = BizInvoker.GetInvoker(resolver, CallingConventionAdapters.Native); } - [CoreConstructor(VSystemID.Raw.NES)] + [CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.DefaultPreference)] public QuickNES(byte[] file, QuickNESSettings settings, QuickNESSyncSettings syncSettings) { ServiceProvider = new BasicServiceProvider(this); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs index 6d637228cdd..4d26afc33f0 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs @@ -14,7 +14,7 @@ public class Snes9x : WaterboxCore, { private readonly LibSnes9x _core; - [CoreConstructor(VSystemID.Raw.SNES)] + [CoreConstructor(VSystemID.Raw.SNES, Priority = CorePriority.DefaultPreference)] public Snes9x(CoreLoadParameters loadParameters) :base(loadParameters.Comm, new Configuration { diff --git a/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs b/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs index 8cc473e6c5a..dfaa3c81a86 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs @@ -19,10 +19,10 @@ public sealed partial class PCEngine : IEmulator, ISaveRam, IInputPollable, IVid int IVideoLogicalOffsets.ScreenY => Settings.TopLine; - [CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.Low)] - [CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.Low)] + [CoreConstructor(VSystemID.Raw.PCE)] + [CoreConstructor(VSystemID.Raw.SGX)] + [CoreConstructor(VSystemID.Raw.PCECD)] + [CoreConstructor(VSystemID.Raw.SGXCD)] public PCEngine(CoreLoadParameters lp) { if (lp.Discs.Count == 1 && lp.Roms.Count == 0) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs index 081e6ce71fa..ab1699902ac 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs @@ -18,7 +18,7 @@ public class PicoDrive : WaterboxCore, IDriveLight, IRegionable, ISettable lp) { LoadCallback = LoadArchive; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Nymashock.cs b/src/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Nymashock.cs index 5442f218ee1..90fb0758159 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Nymashock.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Nymashock.cs @@ -57,7 +57,7 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm) return _cachedSettingsInfo; } - [CoreConstructor(VSystemID.Raw.PSX)] + [CoreConstructor(VSystemID.Raw.PSX, Priority = CorePriority.DefaultPreference)] public Nymashock(CoreLoadParameters lp) : base(lp.Comm, VSystemID.Raw.PSX, "PSX Front Panel", lp.Settings, lp.SyncSettings) { diff --git a/src/BizHawk.Emulation.Cores/CoreInventory.cs b/src/BizHawk.Emulation.Cores/CoreInventory.cs index a6515a34184..65310ce974f 100644 --- a/src/BizHawk.Emulation.Cores/CoreInventory.cs +++ b/src/BizHawk.Emulation.Cores/CoreInventory.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Runtime.ExceptionServices; +using BizHawk.Common; using BizHawk.Common.CollectionExtensions; using BizHawk.Emulation.Common; @@ -14,10 +15,14 @@ namespace BizHawk.Emulation.Cores public class CoreInventory { private readonly Dictionary> _systems = new Dictionary>(); + private readonly List<(List, List)> _systemGroups = [ ]; /// keys are system IDs; values are core/ctor info for all that system's cores public IReadOnlyDictionary> AllCores => _systems; + // list of system ids groups; each system id in the group shares the same core choices + public IReadOnlyList<(List SystemIds, List CoreNames)> SystemGroups => _systemGroups; + public readonly IReadOnlyCollection SystemsFlat; public class Core @@ -157,7 +162,7 @@ public IEnumerable GetCores(string system) /// /// create a core inventory, collecting all IEmulators from some assemblies /// - public CoreInventory(IEnumerable> assys) + public CoreInventory(IEnumerable> assemblies) { var systemsFlat = new Dictionary(); void ProcessConstructor(Type type, CoreConstructorAttribute consAttr, CoreAttribute coreAttr, ConstructorInfo cons) @@ -166,27 +171,41 @@ void ProcessConstructor(Type type, CoreConstructorAttribute consAttr, CoreAttrib _systems.GetValueOrPutNew(consAttr.System).Add(core); systemsFlat[type] = core; } - foreach (var assy in assys) + foreach (var type in assemblies.SelectMany(assembly => assembly).OrderBy(type => type.AssemblyQualifiedName)) { - foreach (var typ in assy) + if (!type.IsAbstract && type.GetInterfaces().Contains(typeof(IEmulator))) { - if (!typ.IsAbstract && typ.GetInterfaces().Contains(typeof(IEmulator))) + var coreAttr = type.GetCustomAttributes(typeof(CoreAttribute), false); + if (coreAttr.Length != 1) + throw new InvalidOperationException($"{nameof(IEmulator)} {type} without {nameof(CoreAttribute)}s!"); + var cons = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance) + .Where(c => c.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Length > 0); + foreach(var con in cons) { - var coreAttr = typ.GetCustomAttributes(typeof(CoreAttribute), false); - if (coreAttr.Length != 1) - throw new InvalidOperationException($"{nameof(IEmulator)} {typ} without {nameof(CoreAttribute)}s!"); - var cons = typ.GetConstructors(BindingFlags.Public | BindingFlags.Instance) - .Where(c => c.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Length > 0); - foreach(var con in cons) + foreach (var consAttr in con.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Cast()) { - foreach (var consAttr in con.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Cast()) - { - ProcessConstructor(typ, consAttr, (CoreAttribute)coreAttr[0], con); - } + ProcessConstructor(type, consAttr, (CoreAttribute)coreAttr[0], con); } } } } + foreach (var (systemId, cores) in _systems) + { + var coreNames = cores.Select(core => core.Name).ToList(); + bool found = false; + foreach (var (systemIds, existingCores) in _systemGroups) + { + if (existingCores.SequenceEqual(coreNames)) + { + systemIds.Add(systemId); + found = true; + break; + } + } + + if (!found) + _systemGroups.Add(([ systemId ], coreNames)); + } SystemsFlat = systemsFlat.Values; } @@ -205,9 +224,9 @@ public enum CorePriority UserPreference = -200, /// - /// A very good core that should be preferred over normal cores. Don't use this? + /// The default core for a system when no other preferences exist. Must be set once per system /// - High = -100, + DefaultPreference = -100, /// /// Most cores should use this diff --git a/src/BizHawk.Tests/Client.Common/config/CorePickerStabilityTests.cs b/src/BizHawk.Tests/Client.Common/config/CorePickerStabilityTests.cs index a5f4903ebd0..a3b51032be5 100644 --- a/src/BizHawk.Tests/Client.Common/config/CorePickerStabilityTests.cs +++ b/src/BizHawk.Tests/Client.Common/config/CorePickerStabilityTests.cs @@ -13,22 +13,24 @@ public sealed class CorePickerStabilityTests private static readonly IReadOnlyDictionary DefaultCorePrefDict = new Config().PreferredCores; [TestMethod] - public void AssertAllChoicesInMenu() + public void AssertAllPrefencesExist() { var multiCoreSystems = CoreInventory.Instance.AllCores.Where(kvp => kvp.Value.Count != 1) - .Select(kvp => kvp.Key) - .ToHashSet(); - foreach (var sysID in DefaultCorePrefDict.Keys) + .Select(kvp => kvp.Key); + foreach (var sysID in multiCoreSystems) { - Assert.IsTrue(multiCoreSystems.Contains(sysID), $"a default core preference exists for {sysID} but that system doesn't have alternate cores"); + Assert.IsTrue(DefaultCorePrefDict.ContainsKey(sysID), $"{sysID} has multiple cores, but no default core preference exists for it!"); } - foreach (var (appliesTo, _) in Config.CorePickerUIData) + } + + [TestMethod] + public void AssertNoExtraPreferences() + { + var singleCoreSystems = CoreInventory.Instance.AllCores.Where(kvp => kvp.Value.Count == 1) + .Select(kvp => kvp.Key); + foreach (var sysID in singleCoreSystems) { - Assert.IsTrue( - appliesTo.Any(multiCoreSystems.Contains), - appliesTo.Length is 1 - ? $"core picker has submenu for {appliesTo[0]}, but that system doesn't have alternate cores" - : $"core picker has submenu for {appliesTo[0]} ({string.Join("/", appliesTo)}), but none of those systems have alternate cores"); + Assert.IsFalse(DefaultCorePrefDict.ContainsKey(sysID), $"{sysID} only has one core implementing it, but an unnecessary preference choice exists for it!"); } } @@ -40,26 +42,33 @@ public void AssertNoMissingCores() { Assert.IsTrue(allCoreNames.Contains(coreName), $"default core preference for {sysID} is \"{coreName}\", which doesn't exist"); } - foreach (var (appliesTo, coreNames) in Config.CorePickerUIData) foreach (var coreName in coreNames) - { - Assert.IsTrue(allCoreNames.Contains(coreName), $"core picker includes nonexistant core \"{coreName}\" under {appliesTo[0]} group"); - } } - /// this really shouldn't be necessary [TestMethod] - public void AssertNoMissingSystems() + public void AssertExactlyOnePreferredCore() { - var allSysIDs = CoreInventory.Instance.AllCores.Keys.ToHashSet(); -#if false // already covered by AssertAllChoicesInMenu - foreach (var sysID in DefaultCorePrefDict.Keys) + foreach(var (systemId, cores) in CoreInventory.Instance.AllCores) { - Assert.IsTrue(allSysIDs.Contains(sysID), $"a default core preference exists for {sysID}, which isn't emulated by any core"); + if (cores.Count >= 2) + { + int preferredCoresCount = cores.Count(core => core.Priority == CorePriority.DefaultPreference); + Assert.IsTrue(preferredCoresCount == 1, $"{systemId} has {preferredCoresCount} preferred cores, expected exactly 1."); + } } -#endif - foreach (var (appliesTo, _) in Config.CorePickerUIData) foreach (var sysID in appliesTo) + } + + [TestMethod] + public void AssertNoConflictingPreferenceInGroup() + { + foreach(var (systemIds, cores) in CoreInventory.Instance.SystemGroups.Where(tuple => tuple.CoreNames.Count > 1)) { - Assert.IsTrue(allSysIDs.Contains(sysID), $"core picker has choices for {sysID}, which isn't emulated by any core"); + var preferredCoreForGroup = DefaultCorePrefDict[systemIds[0]]; + foreach (var systemId in systemIds) + { + var preferredCore = DefaultCorePrefDict[systemId]; + + Assert.AreEqual(preferredCoreForGroup, preferredCore, $"Default core preference for {systemId} does not match the preferred core for the whole group ({string.Join(" | ", systemIds)})"); + } } } }