Refactor InputDisplayGenerator + LogEntryGenerator + ControllerDefinition (#3782)

* refactor InputDisplayGenerator and LogEntryGenerator handling
* Fix NRE in Tastudio. I can already see this avalanching into 100 different bugs
* cba, revert 48f4e13de and bring back bullshit dummy default MovieController
* Refactor MnemonicCache + make Bk2LogEntryGenerator and Bk2InputDisplayGenerator static. This should simplify stuff and make the logic clearer
This commit is contained in:
Moritz Bender 2024-09-10 21:19:54 +02:00 committed by GitHub
parent 08bd14e800
commit a0800862b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 241 additions and 403 deletions

View File

@ -50,9 +50,7 @@ namespace BizHawk.Client.Common
return string.Empty;
}
var lg = _movieSession.Movie.LogGeneratorInstance(
_movieSession.Movie.GetInputState(frame));
return lg.GenerateLogEntry();
return Bk2LogEntryGenerator.GenerateLogEntry(_movieSession.Movie.GetInputState(frame));
}
public void Save(string filename)

View File

@ -9,8 +9,6 @@ namespace BizHawk.Client.Common
{
public class Controller : IController
{
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public Controller(ControllerDefinition definition)
{
Definition = definition;
@ -186,4 +184,4 @@ namespace BizHawk.Client.Common
.Select(kvp => kvp.Key)
.ToList();
}
}
}

View File

@ -75,7 +75,7 @@ namespace BizHawk.Client.Common
return sb.ToString();
}
return _emulator.Frame.ToString();
}
@ -172,10 +172,10 @@ namespace BizHawk.Client.Common
}
public string InputStrMovie()
=> MakeStringFor(_movieSession.MovieController, cache: true);
=> MakeStringFor(_movieSession.MovieController);
public string InputStrImmediate()
=> MakeStringFor(_inputManager.AutofireStickyXorAdapter, cache: true);
=> MakeStringFor(_inputManager.AutofireStickyXorAdapter);
public string InputPrevious()
{
@ -196,15 +196,9 @@ namespace BizHawk.Client.Common
? MakeStringFor(_inputManager.AutofireStickyXorAdapter.Or(_movieSession.Movie.GetInputState(_emulator.Frame - 1)))
: InputStrImmediate();
private string MakeStringFor(IController controller, bool cache = false)
private static string MakeStringFor(IController controller)
{
var idg = controller.InputDisplayGenerator;
if (idg is null)
{
idg = new Bk2InputDisplayGenerator(_emulator.SystemId, controller);
if (cache) controller.InputDisplayGenerator = idg;
}
return idg.Generate();
return Bk2InputDisplayGenerator.Generate(controller);
}
public string MakeIntersectImmediatePrevious()
@ -292,7 +286,7 @@ namespace BizHawk.Client.Common
// in order to achieve this we want to avoid drawing anything pink that isn't actually held down right now
// so we make an AND adapter and combine it using immediate & sticky
// (adapter creation moved to InputManager)
var autoString = MakeStringFor(_inputManager.WeirdStickyControllerForInputDisplay, cache: true);
var autoString = MakeStringFor(_inputManager.WeirdStickyControllerForInputDisplay);
g.DrawString(autoString, autoColor, point.X, point.Y);
//recolor everything that's changed from the previous input

View File

@ -9,8 +9,6 @@ namespace BizHawk.Client.Common
{
public class AutofireController : IController
{
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public AutofireController(IEmulator emulator, int on, int off)
{
On = on < 1 ? 0 : on;

View File

@ -14,8 +14,6 @@ namespace BizHawk.Client.Common
public ControllerDefinition Definition { get; set; }
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button) => _pressed.Contains(button);
public int AxisValue(string name) => 0;

View File

@ -13,8 +13,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition { get; }
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
protected Dictionary<string, int> Axes { get; private set; } = new();
protected Dictionary<string, bool> Buttons { get; private set; } = new();

View File

@ -1,62 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
/// <summary>
/// An implementation of <see cref="IInputDisplayGenerator"/> that
/// uses .bk2 mnemonics as the basis for display
/// </summary>
public class Bk2InputDisplayGenerator : IInputDisplayGenerator
{
/// <remarks>either <c>Range</c> or <c>Mnemonic</c> is always non-null</remarks>
private readonly IReadOnlyList<(string Name, AxisSpec? Range, char? Mnemonic)> _cachedInputSpecs;
private readonly IController _source;
public Bk2InputDisplayGenerator(string systemId, IController source)
{
const string ERR_MSG = nameof(ControllerDefinition.OrderedControlsFlat) + "/" + nameof(ControllerDefinition.ControlsOrdered) + " contains an input name which is neither a button nor an axis";
_cachedInputSpecs = source.Definition.OrderedControlsFlat.Select(button =>
{
if (source.Definition.Axes.TryGetValue(button, out var range)) return (button, range, null);
if (source.Definition.BoolButtons.Contains(button)) return (button, (AxisSpec?) null, (char?) Bk2MnemonicLookup.Lookup(button, systemId));
throw new Exception(ERR_MSG);
}).ToList();
_source = source;
}
public string Generate()
{
var sb = new StringBuilder();
foreach (var (button, range, mnemonicChar) in _cachedInputSpecs)
{
if (range is not null)
{
var val = _source.AxisValue(button);
if (val == range.Value.Neutral)
{
sb.Append(" ");
}
else
{
sb.Append(val.ToString().PadLeft(5, ' ')).Append(',');
}
}
else
{
sb.Append(_source.IsPressed(button)
? mnemonicChar.Value
: ' ');
}
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,46 @@
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
/// <summary>
/// Generates a display friendly version of the input log entry
/// using .bk2 mnemonics as the basis for display
/// </summary>
public static class Bk2InputDisplayGenerator
{
public static string Generate(IController source)
{
if (source.Definition.MnemonicsCache is null)
throw new InvalidOperationException("Can't generate input display string with empty mnemonics cache");
var sb = new StringBuilder();
foreach ((string buttonName, AxisSpec? axisSpec) in source.Definition.ControlsOrdered.SelectMany(x => x))
{
if (axisSpec.HasValue)
{
int val = source.AxisValue(buttonName);
if (val == axisSpec.Value.Neutral)
{
sb.Append(" ");
}
else
{
sb.Append(val.ToString().PadLeft(5, ' ')).Append(',');
}
}
else
{
sb.Append(source.IsPressed(buttonName)
? source.Definition.MnemonicsCache[buttonName]
: ' ');
}
}
return sb.ToString();
}
}
}

View File

@ -8,8 +8,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button)
{
if (Source != null && SourceAnd != null)
@ -36,8 +34,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button)
{
if (Source != null && SourceXor != null)
@ -64,8 +60,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button)
{
return (Source?.IsPressed(button) ?? false)

View File

@ -11,8 +11,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Curr.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button) => Curr.IsPressed(button);
public int AxisValue(string name) => Curr.AxisValue(name);

View File

@ -54,13 +54,14 @@ namespace BizHawk.Client.Common
public void ResetMainControllers(AutofireController nullAutofireController)
{
ActiveController = new(NullController.Instance.Definition);
ActiveController = new Controller(NullController.Instance.Definition);
AutoFireController = nullAutofireController;
}
public void SyncControls(IEmulator emulator, IMovieSession session, Config config)
{
var def = emulator.ControllerDefinition;
def.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(emulator.SystemId));
ActiveController = BindToDefinition(def, config.AllTrollers, config.AllTrollersAnalog, config.AllTrollersFeedbacks);
AutoFireController = BindToDefinitionAF(emulator, config.AllTrollersAutoFire, config.AutofireOn, config.AutofireOff);
@ -157,4 +158,4 @@ namespace BizHawk.Client.Common
return ret;
}
}
}
}

View File

@ -12,8 +12,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition { get; private set; }
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
private readonly Dictionary<string, bool> _overrides = new Dictionary<string, bool>();
private readonly Dictionary<string, int> _axisOverrides = new Dictionary<string, int>();
private readonly List<string> _inverses = new List<string>();

View File

@ -14,8 +14,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button)
{
var source = Source.IsPressed(button);
@ -110,8 +108,6 @@ namespace BizHawk.Client.Common
{
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button)
{
var source = Source.IsPressed(button);
@ -216,4 +212,4 @@ namespace BizHawk.Client.Common
_justPressed = buttons;
}
}
}
}

View File

@ -17,8 +17,6 @@ namespace BizHawk.Client.Common
public ControllerDefinition Definition => Source.Definition;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public OpposingDirPolicy OpposingDirPolicy { get; set; }
public bool IsPressed(string button)

View File

@ -52,12 +52,13 @@ namespace BizHawk.Client.Common
public IInputAdapter MovieOut { get; } = new CopyControllerAdapter();
public IStickyAdapter StickySource { get; set; }
public IMovieController MovieController { get; private set; } = new Bk2Controller("", NullController.Instance.Definition);
public IMovieController MovieController { get; private set; } = new Bk2Controller(NullController.Instance.Definition);
public IMovieController GenerateMovieController(ControllerDefinition definition = null)
public IMovieController GenerateMovieController(ControllerDefinition definition = null, string logKey = null)
{
// TODO: expose Movie.LogKey and pass in here
return new Bk2Controller("", definition ?? MovieController.Definition);
// TODO: should this fallback to Movie.LogKey?
// this function is kinda weird
return new Bk2Controller(definition ?? MovieController.Definition, logKey);
}
public void HandleFrameBefore()

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using BizHawk.Common;
@ -14,38 +13,22 @@ namespace BizHawk.Client.Common
private readonly Dictionary<string, bool> _myBoolButtons = new();
private readonly Bk2ControllerDefinition _type;
private IReadOnlyList<ControlMap> _controlsOrdered;
private IReadOnlyList<ControlMap> ControlsOrdered
=> _controlsOrdered
??= _type.OrderedControlsFlat.Select(name => new ControlMap(name, _type)).ToArray();
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public Bk2Controller(string key, ControllerDefinition definition) : this(definition)
public Bk2Controller(ControllerDefinition definition, string logKey) : this(definition)
{
if (!string.IsNullOrEmpty(key))
{
var groups = key.Split(new[] { "#" }, StringSplitOptions.RemoveEmptyEntries);
_type.ControlsFromLog = groups
.Select(group => group.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries).ToList())
.ToList();
}
if (!string.IsNullOrEmpty(logKey))
Definition = new Bk2ControllerDefinition(definition, logKey);
}
public Bk2Controller(ControllerDefinition definition)
{
_type = new Bk2ControllerDefinition(definition);
Definition = definition;
foreach ((string axisName, AxisSpec range) in definition.Axes)
{
_myAxisControls[axisName] = range.Neutral;
}
}
public ControllerDefinition Definition => _type;
public ControllerDefinition Definition { get; }
public int AxisValue(string name)
=> _myAxisControls.GetValueOrDefault(name);
@ -87,16 +70,12 @@ namespace BizHawk.Client.Common
if (string.IsNullOrWhiteSpace(mnemonic)) return;
var iterator = 0;
foreach (var key in ControlsOrdered)
foreach (var playerControls in Definition.ControlsOrdered)
foreach ((string buttonName, AxisSpec? axisSpec) in playerControls)
{
while (mnemonic[iterator] == '|') iterator++;
if (key.IsBool)
{
_myBoolButtons[key.Name] = mnemonic[iterator] != '.';
iterator++;
}
else if (key.IsAxis)
if (axisSpec.HasValue)
{
var commaIndex = mnemonic.IndexOf(',', iterator);
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
@ -105,10 +84,15 @@ namespace BizHawk.Client.Common
var axisValueString = mnemonic.Substring(startIndex: iterator, length: commaIndex - iterator);
var val = int.Parse(axisValueString);
#endif
_myAxisControls[key.Name] = val;
_myAxisControls[buttonName] = val;
iterator = commaIndex + 1;
}
else
{
_myBoolButtons[buttonName] = mnemonic[iterator] != '.';
iterator++;
}
}
}
@ -122,40 +106,23 @@ namespace BizHawk.Client.Common
_myAxisControls[buttonName] = value;
}
private readonly struct ControlMap
{
public readonly bool IsAxis;
public readonly bool IsBool;
public readonly string Name;
public ControlMap(string name, bool isButton, bool isAxis)
{
Debug.Assert(isButton ^ isAxis, "axis conflicts with button of the same name?");
Name = name;
IsBool = isButton;
IsAxis = isAxis;
}
public ControlMap(string name, ControllerDefinition def)
: this(
name: name,
isButton: def.BoolButtons.Contains(name),
isAxis: def.Axes.ContainsKey(name)) {}
}
private class Bk2ControllerDefinition : ControllerDefinition
{
public IReadOnlyList<IReadOnlyList<string>> ControlsFromLog = null;
private readonly IReadOnlyList<IReadOnlyList<(string, AxisSpec?)>> _controlsFromLogKey;
public Bk2ControllerDefinition(ControllerDefinition source)
: base(source)
public Bk2ControllerDefinition(ControllerDefinition sourceDefinition, string logKey)
: base(sourceDefinition)
{
var groups = logKey.Split(new[] { "#" }, StringSplitOptions.RemoveEmptyEntries);
_controlsFromLogKey = groups
.Select(group => group.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries)
.Select(buttonname => (buttonname, sourceDefinition.Axes.TryGetValue(buttonname, out var axisSpec) ? axisSpec : (AxisSpec?)null))
.ToArray())
.ToArray();
}
protected override IReadOnlyList<IReadOnlyList<string>> GenOrderedControls()
=> ControlsFromLog is not null && ControlsFromLog.Count is not 0 ? ControlsFromLog : base.GenOrderedControls();
protected override IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> GenOrderedControls() => _controlsFromLogKey;
}
}
}

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -6,43 +5,17 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
// Designed to be able to last the lifetime of an IMovie
public sealed class Bk2LogEntryGenerator
public static class Bk2LogEntryGenerator
{
private readonly string _systemId;
private readonly IController _source;
private readonly Dictionary<string, char> _mnemonics = new();
private readonly List<IReadOnlyList<string>> _controlsOrdered;
public Bk2LogEntryGenerator(string systemId, IController source)
{
_systemId = systemId;
_source = source;
_controlsOrdered = _source.Definition.ControlsOrdered.Where(static c => c.Count is not 0).ToList();
foreach (var group in _controlsOrdered) foreach (var button in group)
{
var found = Bk2MnemonicLookup.Lookup(button, _systemId);
try
{
_mnemonics.Add(button, found);
}
catch (ArgumentException e)
{
throw new ArgumentException(innerException: e, paramName: nameof(source), message: $"duplicate KEY {button} in input log mnemonic cache (was {_mnemonics[button]}, attempting to set {found})");
}
}
}
/// <summary>
/// Gets an input log entry that is considered empty. (booleans will be false, axes will be neutral)
/// </summary>
public static string EmptyEntry(IController source) => CreateLogEntry(source, createEmpty: true);
/// <summary>
/// Gets an input log entry that is considered empty. (booleans will be false, axes will be 0)
/// Generates an input log entry for the current state of source
/// </summary>
public string EmptyEntry => CreateLogEntry(createEmpty: true);
/// <summary>
/// Generates an input log entry for the current state of Source
/// </summary>
public string GenerateLogEntry() => CreateLogEntry();
public static string GenerateLogEntry(IController source) => CreateLogEntry(source);
/// <summary>
/// Generates a human readable key that will specify the names of the
@ -57,55 +30,37 @@ namespace BizHawk.Client.Common
foreach (var group in definition.ControlsOrdered.Where(static c => c.Count is not 0))
{
sb.Append('#');
foreach (var button in group)
foreach ((string buttonName, _) in group)
{
sb.Append(button).Append('|');
sb.Append(buttonName).Append('|');
}
}
return sb.ToString();
}
/// <summary>
/// Generates a dictionary of button names to their corresponding mnemonic values
/// </summary>
public IDictionary<string, string> Map()
private static string CreateLogEntry(IController source, bool createEmpty = false)
{
var dict = new Dictionary<string, string>();
foreach (var button in _source.Definition.OrderedControlsFlat)
{
if (_source.Definition.BoolButtons.Contains(button))
{
dict.Add(button, Bk2MnemonicLookup.Lookup(button, _systemId).ToString());
}
else if (_source.Definition.Axes.ContainsKey(button))
{
dict.Add(button, Bk2MnemonicLookup.LookupAxis(button, _systemId));
}
}
if (!createEmpty && source.Definition.MnemonicsCache is null)
throw new InvalidOperationException("Can't generate log entry with empty mnemonics cache");
return dict;
}
private string CreateLogEntry(bool createEmpty = false)
{
var sb = new StringBuilder();
sb.Append('|');
foreach (var group in _controlsOrdered)
foreach (var group in source.Definition.ControlsOrdered)
{
foreach (var button in group)
foreach ((string buttonName, var axisSpec) in group)
{
if (_source.Definition.Axes.TryGetValue(button, out var range))
if (axisSpec.HasValue)
{
var val = createEmpty ? range.Neutral : _source.AxisValue(button);
var val = createEmpty ? axisSpec.Value.Neutral : source.AxisValue(buttonName);
sb.Append(val.ToString().PadLeft(5, ' ')).Append(',');
}
else
{
sb.Append(!createEmpty && _source.IsPressed(button)
? _mnemonics[button]
sb.Append(!createEmpty && source.IsPressed(buttonName)
? source.Definition.MnemonicsCache[buttonName]
: '.');
}
}

View File

@ -1,13 +1,11 @@
using System.Collections.Generic;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Libretro;
// ReSharper disable StyleCop.SA1509
namespace BizHawk.Client.Common
{
internal static class Bk2MnemonicLookup
public static class Bk2MnemonicLookup
{
public static char Lookup(string button, string systemId)
{
@ -65,6 +63,8 @@ namespace BizHawk.Client.Common
return button;
}
public static Func<string, char> MnemonicFunc(string systemId) => buttonName => Lookup(buttonName, systemId);
private static readonly Dictionary<string, char> BaseMnemonicLookupTable = new Dictionary<string, char>
{
["Power"] = 'P',

27
src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs Executable file → Normal file
View File

@ -6,7 +6,7 @@ namespace BizHawk.Client.Common
public partial class Bk2Movie : BasicMovieInfo, IMovie
{
private Bk2Controller _adapter;
//private Bk2LogEntryGenerator _logGenerator;
public Bk2Movie(IMovieSession session, string filename) : base(filename)
{
Session = session;
@ -32,18 +32,6 @@ namespace BizHawk.Client.Common
public virtual bool Changes { get; protected set; }
public bool IsCountingRerecords { get; set; } = true;
public Bk2LogEntryGenerator LogGeneratorInstance(IController source)
{
// Hack because initial movie loading is a mess, and you will immediate create a file with an undefined controller
//if (!source.Definition.Any())
{
return new Bk2LogEntryGenerator(Emulator?.SystemId ?? SystemID, source);
}
//_logGenerator ??= new Bk2LogEntryGenerator(Emulator?.SystemId ?? SystemID, source);
//return _logGenerator;
}
public override int FrameCount => Log.Count;
public int InputLogLength => Log.Count;
@ -60,8 +48,7 @@ namespace BizHawk.Client.Common
public void AppendFrame(IController source)
{
var lg = LogGeneratorInstance(source);
Log.Add(lg.GenerateLogEntry());
Log.Add(Bk2LogEntryGenerator.GenerateLogEntry(source));
Changes = true;
}
@ -75,8 +62,7 @@ namespace BizHawk.Client.Common
}
}
var lg = LogGeneratorInstance(source);
SetFrameAt(frame, lg.GenerateLogEntry());
SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source));
Changes = true;
}
@ -94,8 +80,8 @@ namespace BizHawk.Client.Common
{
if (frame < FrameCount && frame >= -1)
{
_adapter ??= new Bk2Controller(LogKey, Session.MovieController.Definition);
_adapter.SetFromMnemonic(frame >= 0 ? Log[frame] : Session.Movie.LogGeneratorInstance(Session.MovieController).EmptyEntry);
_adapter ??= new Bk2Controller(Session.MovieController.Definition, LogKey);
_adapter.SetFromMnemonic(frame >= 0 ? Log[frame] : Bk2LogEntryGenerator.EmptyEntry(_adapter));
return _adapter;
}
@ -104,8 +90,7 @@ namespace BizHawk.Client.Common
public virtual void PokeFrame(int frame, IController source)
{
var lg = LogGeneratorInstance(source);
SetFrameAt(frame, lg.GenerateLogEntry());
SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source));
Changes = true;
}

View File

@ -93,6 +93,7 @@ namespace BizHawk.Client.Common
private void ImportInputFrame(string line)
{
DeSmuMEControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
SimpleController controller = new(DeSmuMEControllerDef);
controller["LidOpen"] = false;

View File

@ -37,6 +37,7 @@ namespace BizHawk.Client.Common.movie.import
NesRightPort = nameof(ControllerNES)
};
_deck = controllerSettings.Instantiate((x, y) => true).AddSystemToControllerDef();
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
// 004 4-byte little-endian unsigned int: version number, must be 2
uint version = r.ReadUInt32();

View File

@ -26,6 +26,7 @@ namespace BizHawk.Client.Common
};
_deck = controllerSettings.Instantiate((x, y) => true).AddSystemToControllerDef();
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
Result.Movie.HeaderEntries[HeaderKeys.Platform] = platform;
@ -120,6 +121,7 @@ namespace BizHawk.Client.Common
{
controllerSettings.NesLeftPort = nameof(ControllerNES);
_deck = controllerSettings.Instantiate((x, y) => false).AddSystemToControllerDef();
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
}
}
else if (line.StartsWith("port1", StringComparison.OrdinalIgnoreCase))
@ -128,6 +130,7 @@ namespace BizHawk.Client.Common
{
controllerSettings.NesRightPort = nameof(ControllerNES);
_deck = controllerSettings.Instantiate((x, y) => false).AddSystemToControllerDef();
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
}
}
else if (line.StartsWith("port2", StringComparison.OrdinalIgnoreCase))
@ -148,6 +151,7 @@ namespace BizHawk.Client.Common
}
_deck = controllerSettings.Instantiate((x, y) => false)/*.AddSystemToControllerDef()*/; //TODO call omitted on purpose? --yoshi
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
}
else
{
@ -157,6 +161,7 @@ namespace BizHawk.Client.Common
syncSettings.Controls = controllerSettings;
Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings);
Result.Movie.LogKey = Bk2LogEntryGenerator.GenerateLogKey(_deck.ControllerDef);
}
private IControllerDeck _deck;

View File

@ -94,6 +94,7 @@ namespace BizHawk.Client.Common.movie.import
NesRightPort = controller2 ? nameof(ControllerNES) : nameof(UnpluggedNES)
};
_deck = controllerSettings.Instantiate((x, y) => true).AddSystemToControllerDef();
_deck.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
syncSettings.Controls.NesLeftPort = controllerSettings.NesLeftPort;
syncSettings.Controls.NesRightPort = controllerSettings.NesRightPort;

View File

@ -89,7 +89,8 @@ namespace BizHawk.Client.Common.movie.import
}
GPGXControlConverter controlConverter = new(input, systemId: VSystemID.Raw.GEN, cdButtons: false);
controlConverter.ControllerDef.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
SimpleController controller = new(controlConverter.ControllerDef);
// Unknown.

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
@ -21,9 +20,9 @@ namespace BizHawk.Client.Common.movie.import
private SimpleController _controller;
private SimpleController _emptyController;
private readonly string[][] _lsnesGamepadButtons = Enumerable.Range(1, 8)
private readonly (string, AxisSpec?)[][] _lsnesGamepadButtons = Enumerable.Range(1, 8)
.Select(player => new[] { "B", "Y", "Select", "Start", "Up", "Down", "Left", "Right", "A", "X", "L", "R" }
.Select(button => $"P{player} {button}").ToArray())
.Select(button => ($"P{player} {button}", (AxisSpec?)null)).ToArray())
.ToArray();
protected override void RunImport()
@ -85,6 +84,7 @@ namespace BizHawk.Client.Common.movie.import
}
ControllerDefinition controllerDefinition = new BsnesControllers(ss, true).Definition;
controllerDefinition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.SNES));
_emptyController = new SimpleController(controllerDefinition);
_controller = new SimpleController(controllerDefinition);
_playerCount = controllerDefinition.PlayerCount;
@ -312,8 +312,8 @@ namespace BizHawk.Client.Common.movie.import
{
if (player > _playerCount) break;
IReadOnlyList<string> buttons = _controller.Definition.ControlsOrdered[player];
if (buttons[0].EndsWithOrdinal("Up")) // hack to identify gamepad / multitap which have a different button order in bizhawk compared to lsnes
var buttons = _controller.Definition.ControlsOrdered[player];
if (buttons[0].Name.EndsWithOrdinal("Up")) // hack to identify gamepad / multitap which have a different button order in bizhawk compared to lsnes
{
buttons = _lsnesGamepadButtons[player - 1];
}
@ -323,7 +323,7 @@ namespace BizHawk.Client.Common.movie.import
for (int button = 0; button < buttons.Count; button++)
{
// Consider the button pressed so long as its spot is not occupied by a ".".
_controller[buttons[button]] = sections[player][button] != '.';
_controller[buttons[button].Name] = sections[player][button] != '.';
}
}
}

View File

@ -146,6 +146,7 @@ namespace BizHawk.Client.Common.movie.import
{
var buttons = new[] { "Up", "Down", "Left", "Right", "B1", "B2", "Run", "Select" };
_deck.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
SimpleController controllers = new(_deck.Definition);
// Split up the sections of the frame.

View File

@ -95,6 +95,7 @@ namespace BizHawk.Client.Common.movie.import
var ss = new SMS.SmsSyncSettings();
var cd = new SMSControllerDeck(ss.Port1, ss.Port2, isGameGear, ss.UseKeyboard);
cd.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
SimpleController controllers = new(cd.Definition);
/*

View File

@ -181,6 +181,7 @@ namespace BizHawk.Client.Common
OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None
};
SimpleController controllers = new(Octoshock.CreateControllerDefinition(settings));
controllers.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
string[] buttons =
{
@ -291,6 +292,7 @@ namespace BizHawk.Client.Common
OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None, OctoshockDll.ePeripheralType.None
};
SimpleController controllers = new(Octoshock.CreateControllerDefinition(settings));
controllers.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
string[] buttons =
{

View File

@ -27,7 +27,7 @@ namespace BizHawk.Client.Common.movie.import
return;
}
Result.Movie.HeaderEntries[HeaderKeys.Platform] = VSystemID.Raw.SNES;
Result.Movie.SystemID = VSystemID.Raw.SNES;
// 004 4-byte little-endian unsigned int: version number
uint versionNumber = r.ReadUInt32();
@ -189,6 +189,7 @@ namespace BizHawk.Client.Common.movie.import
}
ControllerDefinition definition = new Snes9xControllers(ss).ControllerDefinition;
definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
SimpleController controllers = new(definition);
Result.Movie.LogKey = Bk2LogEntryGenerator.GenerateLogKey(definition);

View File

@ -203,6 +203,7 @@ namespace BizHawk.Client.Common.movie.import
SimpleController controllers = isGBA
? GbaController()
: GbController();
controllers.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(isGBA ? VSystemID.Raw.GBA : VSystemID.Raw.GB));
/*
* 01 00 A

View File

@ -106,6 +106,7 @@ namespace BizHawk.Client.Common.movie.import
"Reset", "Power", "Previous Disk", "Next Disk", "P1 Left", "P1 Right", "P1 Up", "P1 Down", "P1 Start", "P1 A", "P1 B", "P1 C", "P1 X", "P1 Y", "P1 Z", "P1 L", "P1 R"
}
}.MakeImmutable());
controllers.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(Result.Movie.SystemID));
// Split up the sections of the frame.
var sections = line.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
@ -128,7 +129,7 @@ namespace BizHawk.Client.Common.movie.import
for (int button = 0; button < buttonNames.Count; button++)
{
// Consider the button pressed so long as its spot is not occupied by a ".".
controllers[buttonNames[button]] = sections[1][button] != '.';
controllers[buttonNames[button].Name] = sections[1][button] != '.';
}
}

View File

@ -7,8 +7,6 @@ namespace BizHawk.Client.Common
{
internal class BkmControllerAdapter : IController
{
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public BkmControllerAdapter(ControllerDefinition definition, string systemId)
{
// We do need to map the definition name to the legacy
@ -34,6 +32,7 @@ namespace BizHawk.Client.Common
_ => "Null Controller",
};
Definition = new(copyFrom: definition, withName: name);
Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(systemId));
}
public ControllerDefinition Definition { get; set; }

View File

@ -74,11 +74,6 @@ namespace BizHawk.Client.Common
/// </summary>
void SaveBackup();
/// <summary>
/// Creates a log generator using the given input source
/// </summary>
Bk2LogEntryGenerator LogGeneratorInstance(IController source);
/// <summary>
/// Instructs the movie to save the current contents to Filename
/// </summary>

View File

@ -48,11 +48,10 @@ namespace BizHawk.Client.Common
/// <summary>
/// Creates a <see cref="IMovieController" /> instance based on the
/// given button definition if provided else the
/// current <see cref="MovieController" /> button definition
/// will be used
/// given button definition if provided else the current
/// <see cref="MovieController"/>s button definition will be used
/// </summary>
IMovieController GenerateMovieController(ControllerDefinition definition = null);
IMovieController GenerateMovieController(ControllerDefinition definition = null, string logKey = null);
void HandleFrameBefore();
void HandleFrameAfter();

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common

View File

@ -20,8 +20,7 @@ namespace BizHawk.Client.Common
ChangeLog.AddGeneralUndo(frame - 1, frame - 1, $"Record Frame: {frame}");
}
var lg = LogGeneratorInstance(source);
SetFrameAt(frame, lg.GenerateLogEntry());
SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source));
Changes = true;
@ -86,8 +85,7 @@ namespace BizHawk.Client.Common
{
ChangeLog.AddGeneralUndo(frame, frame, $"Clear Frame: {frame}");
var lg = LogGeneratorInstance(Session.MovieController);
SetFrameAt(frame, lg.EmptyEntry);
SetFrameAt(frame, Bk2LogEntryGenerator.EmptyEntry(Session.MovieController));
Changes = true;
InvalidateAfter(frame);
@ -210,8 +208,7 @@ namespace BizHawk.Client.Common
foreach (var input in inputStates)
{
var lg = LogGeneratorInstance(input);
inputLog.Add(lg.GenerateLogEntry());
inputLog.Add(Bk2LogEntryGenerator.GenerateLogEntry(input));
}
InsertInput(frame, inputLog); // Sets the ChangeLog
@ -238,8 +235,7 @@ namespace BizHawk.Client.Common
break;
}
var lg = LogGeneratorInstance(states[i]);
var entry = lg.GenerateLogEntry();
var entry = Bk2LogEntryGenerator.GenerateLogEntry(states[i]);
if (firstChangedFrame == -1 && Log[frame + i] != entry)
{
firstChangedFrame = frame + i;
@ -260,8 +256,7 @@ namespace BizHawk.Client.Common
{
frame = Math.Min(frame, Log.Count);
var lg = LogGeneratorInstance(Session.MovieController);
Log.InsertRange(frame, Enumerable.Repeat(lg.EmptyEntry, count).ToList());
Log.InsertRange(frame, Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count));
ShiftBindedMarkers(frame, count);
@ -280,11 +275,9 @@ namespace BizHawk.Client.Common
Session.MovieController.SetFromSticky(Session.StickySource);
// account for autohold. needs autohold pattern to be already recorded in the current frame
var lg = LogGeneratorInstance(Session.MovieController);
for (int i = 0; i < numFrames; i++)
{
Log.Add(lg.GenerateLogEntry());
Log.Add(Bk2LogEntryGenerator.GenerateLogEntry(Session.MovieController));
}
Changes = true;
@ -306,8 +299,7 @@ namespace BizHawk.Client.Common
var adapter = GetInputState(frame);
adapter.SetBool(buttonName, !adapter.IsPressed(buttonName));
var lg = LogGeneratorInstance(adapter);
Log[frame] = lg.GenerateLogEntry();
Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter);
Changes = true;
InvalidateAfter(frame);
@ -325,8 +317,7 @@ namespace BizHawk.Client.Common
var old = adapter.IsPressed(buttonName);
adapter.SetBool(buttonName, val);
var lg = LogGeneratorInstance(adapter);
Log[frame] = lg.GenerateLogEntry();
Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter);
if (old != val)
{
@ -352,8 +343,7 @@ namespace BizHawk.Client.Common
bool old = adapter.IsPressed(buttonName);
adapter.SetBool(buttonName, val);
var lg = LogGeneratorInstance(adapter);
Log[frame + i] = lg.GenerateLogEntry();
Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(adapter);
if (changed == -1 && old != val)
{
@ -381,8 +371,7 @@ namespace BizHawk.Client.Common
var old = adapter.AxisValue(buttonName);
adapter.SetAxis(buttonName, val);
var lg = LogGeneratorInstance(adapter);
Log[frame] = lg.GenerateLogEntry();
Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter);
if (old != val)
{
@ -408,8 +397,7 @@ namespace BizHawk.Client.Common
var old = adapter.AxisValue(buttonName);
adapter.SetAxis(buttonName, val);
var lg = LogGeneratorInstance(adapter);
Log[frame + i] = lg.GenerateLogEntry();
Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(adapter);
if (changed == -1 && old != val)
{

View File

@ -56,15 +56,8 @@ namespace BizHawk.Client.Common
}
base.Attach(emulator);
foreach (var button in emulator.ControllerDefinition.BoolButtons)
{
_mnemonicCache[button] = Bk2MnemonicLookup.Lookup(button, emulator.SystemId);
}
}
private readonly Dictionary<string, char> _mnemonicCache = new Dictionary<string, char>();
public override bool StartsFromSavestate
{
get => base.StartsFromSavestate;
@ -146,7 +139,7 @@ namespace BizHawk.Client.Common
public void InvalidateEntireGreenzone()
=> InvalidateAfter(0);
private (int Frame, IMovieController Controller) _displayCache = (-1, new Bk2Controller("", NullController.Instance.Definition));
private (int Frame, IMovieController Controller) _displayCache = (-1, new Bk2Controller(NullController.Instance.Definition));
/// <summary>
/// Returns the mnemonic value for boolean buttons, and actual value for axes,
@ -162,12 +155,14 @@ namespace BizHawk.Client.Common
return CreateDisplayValueForButton(_displayCache.Controller, buttonName);
}
private string CreateDisplayValueForButton(IController adapter, string buttonName)
private static string CreateDisplayValueForButton(IController adapter, string buttonName)
{
// those Contains checks could be avoided by passing in the button type
// this should be considered if this becomes a significant performance issue
if (adapter.Definition.BoolButtons.Contains(buttonName))
{
return adapter.IsPressed(buttonName)
? _mnemonicCache[buttonName].ToString()
? adapter.Definition.MnemonicsCache![buttonName].ToString()
: "";
}

View File

@ -58,7 +58,6 @@ namespace BizHawk.Client.EmuHawk
private int _dataSize;
private Dictionary<string, double> _cachedControlProbabilities;
private Bk2LogEntryGenerator _logGenerator;
private bool _previousDisplayMessage;
private bool _previousInvisibleEmulation;
@ -1001,7 +1000,7 @@ namespace BizHawk.Client.EmuHawk
InputManager.SyncControls(Emulator, MovieSession, Config);
if (clear_log) { _currentBotAttempt.Log.Clear(); }
_currentBotAttempt.Log.Add(_logGenerator.GenerateLogEntry());
_currentBotAttempt.Log.Add(Bk2LogEntryGenerator.GenerateLogEntry(InputManager.ClickyVirtualPadController));
}
private void StartBot()
@ -1027,7 +1026,6 @@ namespace BizHawk.Client.EmuHawk
MovieSession.Movie.IsCountingRerecords = false;
}
_logGenerator = MovieSession.Movie.LogGeneratorInstance(InputManager.ClickyVirtualPadController);
_cachedControlProbabilities = ControlProbabilities;
_doNotUpdateValues = true;

View File

@ -34,8 +34,6 @@ namespace BizHawk.Client.EmuHawk
// Get a IController that only contains buttons in key.
InitController(_inputKey);
var logGenerator = movieSession.Movie.LogGeneratorInstance(_controller);
string movieKey = Bk2LogEntryGenerator.GenerateLogKey(_controller.Definition).Replace("#", "");
movieKey = movieKey.Substring(startIndex: 0, length: movieKey.Length - 1); // drop last char
if (key == movieKey)
@ -50,7 +48,7 @@ namespace BizHawk.Client.EmuHawk
for (int i = 0; i < length; i++)
{
_controller.SetFrom(movieSession.Movie.GetInputState(i + start));
_log[i] = logGenerator.GenerateLogEntry();
_log[i] = Bk2LogEntryGenerator.GenerateLogEntry(_controller);
}
}
}
@ -115,18 +113,15 @@ namespace BizHawk.Client.EmuHawk
}
var newController = _movieSession.GenerateMovieController(d.MakeImmutable());
var logGenerator = _movieSession.Movie.LogGeneratorInstance(newController);
logGenerator.GenerateLogEntry(); // Reference and create all buttons.
// Reset all buttons in targetController (it may still have buttons that aren't being set here set true)
var tC = _movieSession.Movie.LogGeneratorInstance(_targetController);
_targetController.SetFromMnemonic(tC.EmptyEntry);
_targetController.SetFromMnemonic(Bk2LogEntryGenerator.EmptyEntry(_targetController));
for (int i = 0; i < Length; i++)
{
_controller.SetFromMnemonic(_log[i]);
LatchFromSourceButtons(_targetController, _controller);
newController.SetFrom(_targetController);
_log[i] = logGenerator.GenerateLogEntry();
_log[i] = Bk2LogEntryGenerator.GenerateLogEntry(newController);
}
_controller = newController;

View File

@ -418,7 +418,7 @@ namespace BizHawk.Client.EmuHawk
}
_tasClipboard.Add(new TasClipboardEntry(index, input));
var logEntry = CurrentTasMovie.LogGeneratorInstance(input).GenerateLogEntry();
var logEntry = Bk2LogEntryGenerator.GenerateLogEntry(input);
sb.AppendLine(Settings.CopyIncludesFrameNo ? $"{FrameToStringPadded(index)} {logEntry}" : logEntry);
}
@ -537,8 +537,7 @@ namespace BizHawk.Client.EmuHawk
}
_tasClipboard.Add(new TasClipboardEntry(index, input));
var lg = CurrentTasMovie.LogGeneratorInstance(input);
sb.AppendLine(lg.GenerateLogEntry());
sb.AppendLine(Bk2LogEntryGenerator.GenerateLogEntry(input));
}
Clipboard.SetDataObject(sb.ToString());

View File

@ -7,7 +7,6 @@ using System.ComponentModel;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.ToolExtensions;
using BizHawk.Client.EmuHawk.Properties;
using BizHawk.Common;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
@ -351,31 +350,17 @@ namespace BizHawk.Client.EmuHawk
Rotatable = true,
});
var columnNames = MovieSession.Movie
.LogGeneratorInstance(MovieSession.MovieController)
.Map();
foreach (var (name, mnemonic0) in columnNames)
foreach ((string name, string mnemonic0, int maxLength) in MnemonicMap())
{
var mnemonic = Emulator.SystemId is VSystemID.Raw.N64 && N64CButtonSuffixes.Any(name.EndsWithOrdinal)
? $"c{mnemonic0.ToUpperInvariant()}" // prepend 'c' to differentiate from L/R buttons -- this only affects the column headers
: mnemonic0;
ColumnType type;
int digits;
if (ControllerType.Axes.TryGetValue(name, out var range))
{
type = ColumnType.Axis;
digits = Math.Max(mnemonic.Length, range.MaxDigits);
}
else
{
type = ColumnType.Boolean;
digits = mnemonic.Length;
}
var type = ControllerType.Axes.ContainsKey(name) ? ColumnType.Axis : ColumnType.Boolean;
TasView.AllColumns.Add(new(
name: name,
widthUnscaled: (digits * 6) + 14, // magic numbers reused in EditBranchTextPopUp() --feos // not since eb63fa5a9 (before 2.3.3) --yoshi
widthUnscaled: (maxLength * 6) + 14, // magic numbers reused in EditBranchTextPopUp() --feos // not since eb63fa5a9 (before 2.3.3) --yoshi
type: type,
text: mnemonic));
}
@ -497,8 +482,7 @@ namespace BizHawk.Client.EmuHawk
{
get
{
var lg = CurrentTasMovie.LogGeneratorInstance(MovieSession.MovieController);
var empty = lg.EmptyEntry;
var empty = Bk2LogEntryGenerator.EmptyEntry(MovieSession.MovieController);
foreach (var row in TasView.SelectedRows)
{
if (CurrentTasMovie[row].LogEntry != empty)
@ -1258,5 +1242,27 @@ namespace BizHawk.Client.EmuHawk
return null;
}
}
private IEnumerable<(string Name, string Mnemonic, int MaxLength)> MnemonicMap()
{
if (MovieSession.MovieController.Definition.MnemonicsCache is null)
throw new InvalidOperationException("Can't build mnemonic map with empty mnemonics cache");
foreach (var playerControls in MovieSession.MovieController.Definition.ControlsOrdered)
{
foreach ((string name, AxisSpec? axisSpec) in playerControls)
{
if (axisSpec.HasValue)
{
string mnemonic = Bk2MnemonicLookup.LookupAxis(name, MovieSession.Movie.SystemID);
yield return (name, mnemonic, Math.Max(mnemonic.Length, axisSpec.Value.MaxDigits));
}
else
{
yield return (name, MovieSession.MovieController.Definition.MnemonicsCache[name].ToString(), 1);
}
}
}
}
}
}

View File

@ -1,6 +1,4 @@
#nullable disable
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.RegularExpressions;
@ -21,38 +19,48 @@ namespace BizHawk.Emulation.Common
private bool _mutable = true;
private IReadOnlyList<IReadOnlyList<string>> _orderedControls = null;
private IReadOnlyList<string> _orderedControlsFlat = null;
private IReadOnlyList<IReadOnlyList<(string, AxisSpec?)>>? _orderedControls;
/// <summary>starts with console buttons, then each player's buttons individually</summary>
public IReadOnlyList<IReadOnlyList<string>> ControlsOrdered
public IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> ControlsOrdered
{
get
{
if (_orderedControls is not null) return _orderedControls;
if (!_mutable) return _orderedControls = GenOrderedControls();
const string ERR_MSG = "this " + nameof(ControllerDefinition) + " has not yet been built and sealed, so it is not safe to enumerate this while it could still be mutated";
const string ERR_MSG = $"this {nameof(ControllerDefinition)} has not yet been built and sealed, so it is not safe to enumerate this while it could still be mutated";
throw new InvalidOperationException(ERR_MSG);
}
}
public readonly string Name;
public IReadOnlyList<string> OrderedControlsFlat => _orderedControlsFlat ??= ControlsOrdered.SelectMany(static s => s).ToList();
private Dictionary<string, char>? _mnemonicsCache;
public IReadOnlyDictionary<string, char>? MnemonicsCache => _mnemonicsCache;
/// <remarks>
/// TODO: this should probably be called in <see cref="MakeImmutable"/>,
/// but the needed Bk2MnemonicsLookup is in Client.Common
/// </remarks>
public void BuildMnemonicsCache(Func<string, char> mnemonicFunc)
{
if (_mutable)
throw new InvalidOperationException($"this {nameof(ControllerDefinition)} has not yet been built and sealed; can't build mnemonics cache");
_mnemonicsCache ??= BoolButtons.ToDictionary(buttonName => buttonName, mnemonicFunc);
}
public ControllerDefinition(string name)
=> Name = name;
public ControllerDefinition(ControllerDefinition copyFrom, string withName = null)
public ControllerDefinition(ControllerDefinition copyFrom, string? withName = null)
: this(withName ?? copyFrom.Name)
{
BoolButtons.AddRange(copyFrom.BoolButtons);
foreach (var kvp in copyFrom.Axes) Axes.Add(kvp);
HapticsChannels.AddRange(copyFrom.HapticsChannels);
CategoryLabels = copyFrom.CategoryLabels;
// Do not clone _orderedControls, as GenOrderedControls may be overridden by the derived class
// _orderedControls = copyFrom._orderedControls;
_mnemonicsCache = copyFrom._mnemonicsCache;
MakeImmutable();
}
@ -105,11 +113,12 @@ namespace BizHawk.Emulation.Common
if (!_mutable) throw new InvalidOperationException(ERR_MSG);
}
protected virtual IReadOnlyList<IReadOnlyList<string>> GenOrderedControls()
protected virtual IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> GenOrderedControls()
{
var ret = new List<string>[PlayerCount + 1];
var ret = new List<(string, AxisSpec?)>[PlayerCount + 1];
for (var i = 0; i < ret.Length; i++) ret[i] = new();
foreach (var btn in Axes.Keys.Concat(BoolButtons)) ret[PlayerNumber(btn)].Add(btn);
foreach ((string buttonName, var axisSpec) in Axes) ret[PlayerNumber(buttonName)].Add((buttonName, axisSpec));
foreach (var btn in BoolButtons) ret[PlayerNumber(btn)].Add((btn, null));
return ret;
}

View File

@ -13,8 +13,6 @@ namespace BizHawk.Emulation.Common
{
public ControllerDefinition Definition { get; } = new ControllerDefinition("Null Controller").MakeImmutable();
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public bool IsPressed(string button) => false;
public int AxisValue(string name) => 0;
@ -23,6 +21,7 @@ namespace BizHawk.Emulation.Common
public void SetHapticChannelStrength(string name, int strength) {}
public static readonly NullController Instance = new NullController();
public static readonly NullController Instance = new();
private NullController() {}
}
}
}

View File

@ -69,8 +69,6 @@ namespace BizHawk.Emulation.Common
private readonly IController _src;
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public DummyController(
IController src,
IReadOnlyDictionary<string, string> buttonAxisRemaps)

View File

@ -11,9 +11,6 @@ namespace BizHawk.Emulation.Common
/// </summary>
ControllerDefinition Definition { get; }
/// <summary>used as cache by frontend; implement as autoprop w/ initial value <see langword="null"/></summary>
IInputDisplayGenerator InputDisplayGenerator { get; set; }
/// <seealso cref="SetHapticChannelStrength"/>
IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot();

View File

@ -1,10 +0,0 @@
namespace BizHawk.Emulation.Common
{
public interface IInputDisplayGenerator
{
/// <summary>
/// Generates a display friendly version of the input log entry
/// </summary>
string Generate();
}
}

View File

@ -15,8 +15,6 @@ namespace BizHawk.Emulation.Common
{
private readonly Dictionary<string, int> _buttons = new();
public IInputDisplayGenerator InputDisplayGenerator { get; set; } = null;
public SaveController()
{
Definition = null;

View File

@ -112,18 +112,18 @@ namespace BizHawk.Emulation.Cores.Libretro
MakeImmutable();
}
protected override IReadOnlyList<IReadOnlyList<string>> GenOrderedControls()
protected override IReadOnlyList<IReadOnlyList<(string Name, AxisSpec? AxisSpec)>> GenOrderedControls()
{
// all this is to remove the keyboard buttons from P0 and put them in P3 so they appear at the end of the input display
var players = base.GenOrderedControls().ToList();
List<string> retroKeyboard = new();
var p0 = (List<string>) players[0];
List<(string, AxisSpec?)> retroKeyboard = new();
var p0 = (List<(string, AxisSpec?)>) players[0];
for (var i = 0; i < p0.Count; /* incremented in body */)
{
var buttonName = p0[i];
if (CategoryLabels.TryGetValue(buttonName, out var v) && v is CAT_KEYBOARD)
(string ButtonName, AxisSpec?) button = p0[i];
if (CategoryLabels.TryGetValue(button.ButtonName, out var v) && v is CAT_KEYBOARD)
{
retroKeyboard.Add(buttonName);
retroKeyboard.Add(button);
p0.RemoveAt(i);
}
else

View File

@ -15,18 +15,19 @@ namespace BizHawk.Tests.Client.Common.Display
public void Initializer()
{
_boolController = new(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "A" } }.MakeImmutable());
_boolController.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.NULL));
_axisController = new(
new ControllerDefinition("Dummy Gamepad")
.AddXYPair("Stick{0}", AxisPairOrientation.RightAndUp, 0.RangeTo(200), MidValue)
.MakeImmutable());
_axisController.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.NULL));
}
[TestMethod]
public void Generate_BoolPressed_GeneratesMnemonic()
{
_boolController["A"] = true;
var displayGenerator = new Bk2InputDisplayGenerator("NES", _boolController);
var actual = displayGenerator.Generate();
var actual = Bk2InputDisplayGenerator.Generate(_boolController);
Assert.AreEqual("A", actual);
}
@ -34,16 +35,14 @@ namespace BizHawk.Tests.Client.Common.Display
public void Generate_BoolUnPressed_GeneratesSpace()
{
_boolController["A"] = false;
var displayGenerator = new Bk2InputDisplayGenerator("NES", _boolController);
var actual = displayGenerator.Generate();
var actual = Bk2InputDisplayGenerator.Generate(_boolController);
Assert.AreEqual(" ", actual);
}
[TestMethod]
public void Generate_Floats()
{
var displayGenerator = new Bk2InputDisplayGenerator("NES", _axisController);
var actual = displayGenerator.Generate();
var actual = Bk2InputDisplayGenerator.Generate(_axisController);
Assert.AreEqual(" 0, 0,", actual);
}
@ -51,8 +50,7 @@ namespace BizHawk.Tests.Client.Common.Display
public void Generate_MidRangeDisplaysEmpty()
{
_axisController.AcceptNewAxis("StickX", MidValue);
var displayGenerator = new Bk2InputDisplayGenerator("NES", _axisController);
var actual = displayGenerator.Generate();
var actual = Bk2InputDisplayGenerator.Generate(_axisController);
Assert.AreEqual(" 0,", actual);
}
}

View File

@ -14,19 +14,21 @@ namespace BizHawk.Tests.Client.Common.Movie
public void Initializer()
{
_boolController = new(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "A" } }.MakeImmutable());
_boolController.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.NES));
_axisController = new(
new ControllerDefinition("Dummy Gamepad")
.AddXYPair("Stick{0}", AxisPairOrientation.RightAndUp, 0.RangeTo(200), 100)
.MakeImmutable());
_axisController.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.NES));
}
[TestMethod]
public void GenerateLogEntry_ExclamationForUnknownButtons()
{
SimpleController controller = new(new ControllerDefinition("Dummy Gamepad") { BoolButtons = { "Unknown Button" } }.MakeImmutable());
var lg = new Bk2LogEntryGenerator("NES", controller);
controller.Definition.BuildMnemonicsCache(Bk2MnemonicLookup.MnemonicFunc(VSystemID.Raw.NES));
controller["Unknown Button"] = true;
var actual = lg.GenerateLogEntry();
var actual = Bk2LogEntryGenerator.GenerateLogEntry(controller);
Assert.AreEqual("|!|", actual);
}
@ -34,8 +36,7 @@ namespace BizHawk.Tests.Client.Common.Movie
public void GenerateLogEntry_BoolPressed_GeneratesMnemonic()
{
_boolController["A"] = true;
var lg = new Bk2LogEntryGenerator("NES", _boolController);
var actual = lg.GenerateLogEntry();
var actual = Bk2LogEntryGenerator.GenerateLogEntry(_boolController);
Assert.AreEqual("|A|", actual);
}
@ -43,17 +44,15 @@ namespace BizHawk.Tests.Client.Common.Movie
public void GenerateLogEntry_BoolUnPressed_GeneratesPeriod()
{
_boolController["A"] = false;
var lg = new Bk2LogEntryGenerator("NES", _boolController);
var actual = lg.GenerateLogEntry();
var actual = Bk2LogEntryGenerator.GenerateLogEntry(_boolController);
Assert.AreEqual("|.|", actual);
}
[TestMethod]
public void GenerateLogEntry_Floats()
{
var lg = new Bk2LogEntryGenerator("NES", _axisController);
var actual = lg.GenerateLogEntry();
var actual = Bk2LogEntryGenerator.GenerateLogEntry(_axisController);
Assert.AreEqual("| 0, 0,|", actual);
}
}
}
}