Add haptics support to ControllerDefinition and the Controller stack

still no cores which support it, "Debug" is still hardcoded, still uses holding
Fast Forward hotkey to trigger
no idea how OverrideAdapter, or the IInputAdapters, or the Bk2/BkmController
will work, I've just thrown NotImplementedException from those
This commit is contained in:
YoshiRulz 2021-03-30 10:06:26 +10:00
parent bb3fddcb5f
commit 6f47492d95
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
18 changed files with 152 additions and 37 deletions

View File

@ -17,6 +17,7 @@ namespace BizHawk.Client.Common
_axes[kvp.Key] = kvp.Value.Neutral;
_axisRanges[kvp.Key] = kvp.Value;
}
foreach (var channel in Definition.HapticsChannels) _haptics[channel] = 0;
}
public ControllerDefinition Definition { get; private set; }
@ -25,11 +26,17 @@ namespace BizHawk.Client.Common
public int AxisValue(string name) => _axes[name];
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot()
=> _haptics.Select(kvp => (kvp.Key, kvp.Value)).ToArray();
public void SetHapticChannelStrength(string name, int strength) => _haptics[name] = strength;
private readonly WorkingDictionary<string, List<string>> _bindings = new WorkingDictionary<string, List<string>>();
private readonly WorkingDictionary<string, bool> _buttons = new WorkingDictionary<string, bool>();
private readonly WorkingDictionary<string, int> _axes = new WorkingDictionary<string, int>();
private readonly Dictionary<string, AxisSpec> _axisRanges = new WorkingDictionary<string, AxisSpec>();
private readonly Dictionary<string, AnalogBind> _axisBindings = new Dictionary<string, AnalogBind>();
private readonly Dictionary<string, int> _haptics = new WorkingDictionary<string, int>();
private readonly Dictionary<string, FeedbackBind> _feedbackBindings = new Dictionary<string, FeedbackBind>();
/// <summary>don't do this</summary>
@ -95,11 +102,14 @@ namespace BizHawk.Client.Common
}
}
public void PrepareHapticsForHost(SimpleController finalHostController, int debug)
public void PrepareHapticsForHost(SimpleController finalHostController)
{
foreach (var kvp in _feedbackBindings)
{
finalHostController.SetHapticChannelStrength(kvp.Value.GamepadPrefix + kvp.Value.Channel, (int) ((double) debug * kvp.Value.Prescale));
if (_haptics.TryGetValue(kvp.Key, out var strength))
{
finalHostController.SetHapticChannelStrength(kvp.Value.GamepadPrefix + kvp.Value.Channel, (int) ((double) strength * kvp.Value.Prescale));
}
}
}

View File

@ -44,6 +44,10 @@ namespace BizHawk.Client.Common
throw new NotImplementedException();
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>();
public void SetHapticChannelStrength(string name, int strength) {}
/// <summary>
/// uses the bindings to latch our own logical button state from the source controller's button state (which are assumed to be the physical side of the binding).
/// this will clobber any existing data (use OR_* or other functions to layer in additional input sources)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
@ -18,6 +19,10 @@ namespace BizHawk.Client.Common
public int AxisValue(string name) => 0;
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>();
public void SetHapticChannelStrength(string name, int strength) {}
/// <summary>
/// Call this once per frame to do the timekeeping for the hold and release
/// </summary>

View File

@ -1,4 +1,6 @@
using BizHawk.Emulation.Common;
using System.Collections.Generic;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
@ -20,6 +22,10 @@ namespace BizHawk.Client.Common
// this works in the code because SourceOr is the autofire controller
public int AxisValue(string name) => Source.AxisValue(name);
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
public IController Source { get; set; }
internal IController SourceAnd { get; set; }
}
@ -42,6 +48,10 @@ namespace BizHawk.Client.Common
// this works in the code because SourceOr is the autofire controller
public int AxisValue(string name) => Source.AxisValue(name);
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
public IController Source { get; set; }
internal IController SourceXor { get; set; }
}
@ -60,6 +70,10 @@ namespace BizHawk.Client.Common
// this works in the code because SourceOr is the autofire controller
public int AxisValue(string name) => Source.AxisValue(name);
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
public IController Source { get; set; }
internal IController SourceOr { get; set; }
}

View File

@ -1,4 +1,6 @@
using BizHawk.Emulation.Common;
using System.Collections.Generic;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
@ -13,6 +15,10 @@ namespace BizHawk.Client.Common
public int AxisValue(string name) => Curr.AxisValue(name);
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Curr.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Curr.SetHapticChannelStrength(name, strength);
public IController Source { get; set; }
private IController Curr => Source ?? NullController.Instance;

View File

@ -117,8 +117,7 @@ namespace BizHawk.Client.Common
if (feedbackBinds.TryGetValue(def.Name, out var fBinds))
{
const string channel = "Debug";
// foreach (var channel in def.HapticsChannels)
foreach (var channel in def.HapticsChannels)
{
if (fBinds.TryGetValue(channel, out var bind)) ret.BindFeedbackChannel(channel, bind);
}

View File

@ -33,6 +33,10 @@ namespace BizHawk.Client.Common
? _axisOverrides[name]
: 0;
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi
public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi
public IEnumerable<string> Overrides => _overrides.Select(kvp => kvp.Key);
public IEnumerable<string> AxisOverrides => _axisOverrides.Select(kvp => kvp.Key);

View File

@ -39,6 +39,10 @@ namespace BizHawk.Client.Common
return Source.AxisValue(name);
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
public IController Source { get; set; }
private List<string> _justPressed = new List<string>();
@ -141,6 +145,10 @@ namespace BizHawk.Client.Common
return Source.AxisValue(name);
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
// TODO: Change the AutoHold adapter to be one of these, with an 'Off' value of 0?
// Probably would have slightly lower performance, but it seems weird to have such a similar class that is only used once.
private int _on;

View File

@ -1,4 +1,6 @@
using BizHawk.Common.StringExtensions;
using System.Collections.Generic;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
@ -62,5 +64,9 @@ namespace BizHawk.Client.Common
// The float format implies no U+D and no L+R no matter what, so just passthru
public int AxisValue(string name) => Source.AxisValue(name);
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Source.GetHapticsSnapshot();
public void SetHapticChannelStrength(string name, int strength) => Source.SetHapticChannelStrength(name, strength);
}
}

View File

@ -46,6 +46,10 @@ namespace BizHawk.Client.Common
public bool IsPressed(string button) => _myBoolButtons[button];
public int AxisValue(string name) => _myAxisControls[name];
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi
public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi
public void SetFrom(IController source)
{
foreach (var button in Definition.BoolButtons)

View File

@ -1,4 +1,7 @@
using BizHawk.Common;
using System;
using System.Collections.Generic;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
@ -45,6 +48,10 @@ namespace BizHawk.Client.Common
return _myAxisControls[name];
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => throw new NotImplementedException(); // no idea --yoshi
public void SetHapticChannelStrength(string name, int strength) => throw new NotImplementedException(); // no idea --yoshi
/// <summary>
/// latches all buttons from the supplied mnemonic string
/// </summary>

View File

@ -703,9 +703,8 @@ namespace BizHawk.Client.EmuHawk
// ...but prepare haptics first, those get read in ProcessInput
var finalHostController = (ControllerInputCoalescer) InputManager.ControllerInputCoalescer;
// for now, vibrate the first gamepad when the Fast Forward hotkey is held, using the value from the previous (host) frame
InputManager.ActiveController.PrepareHapticsForHost(
finalHostController,
debug: InputManager.ClientControls.IsPressed("Fast Forward") ? int.MaxValue : 0);
InputManager.ActiveController.SetHapticChannelStrength("Debug", InputManager.ClientControls.IsPressed("Fast Forward") ? int.MaxValue : 0);
InputManager.ActiveController.PrepareHapticsForHost(finalHostController);
ProcessInput(
_hotkeyCoalescer,
finalHostController,

View File

@ -239,7 +239,7 @@ namespace BizHawk.Client.EmuHawk
LoadToPanel(
FeedbacksTab,
_emulator.ControllerDefinition.Name,
/*_emulator.ControllerDefinition.HapticsChannels*/new[] { "Debug" },
_emulator.ControllerDefinition.HapticsChannels,
_emulator.ControllerDefinition.CategoryLabels,
haptics,
new(string.Empty, string.Empty, 1.0f),

View File

@ -10,9 +10,7 @@ namespace BizHawk.Emulation.Common
/// <seealso cref="IEmulator" />
public class ControllerDefinition
{
public ControllerDefinition()
{
}
public ControllerDefinition() => HapticsChannels.Add("Debug");
public ControllerDefinition(ControllerDefinition source)
: this()
@ -35,6 +33,9 @@ namespace BizHawk.Emulation.Common
public readonly AxisDict Axes = new AxisDict();
/// <summary>Contains names of virtual haptic feedback channels, e.g. <c>{ "P1 Mono" }</c>, <c>{ "P2 Left", "P2 Right" }</c>.</summary>
public List<string> HapticsChannels { get; } = new();
/// <summary>
/// Gets the category labels. These labels provide a means of categorizing controls in various controller display and config screens
/// </summary>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Common
{
@ -18,6 +19,10 @@ namespace BizHawk.Emulation.Common
public int AxisValue(string name) => 0;
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>();
public void SetHapticChannelStrength(string name, int strength) {}
public static readonly NullController Instance = new NullController();
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Emulation.Common
{
@ -31,24 +32,32 @@ namespace BizHawk.Emulation.Common
int playerNext = 1;
foreach (var def in controllers)
{
var remaps = new Dictionary<string, string>();
Dictionary<string, string> buttonAxisRemaps = new();
Dictionary<string, string> feedbackRemaps = new();
foreach (string s in def.BoolButtons)
{
string r = Allocate(s, ref plr, ref playerNext);
ret.BoolButtons.Add(r);
remaps[s] = r;
buttonAxisRemaps[s] = r;
}
foreach (var kvp in def.Axes)
{
string r = Allocate(kvp.Key, ref plr, ref playerNext);
ret.Axes.Add(r, kvp.Value);
remaps[kvp.Key] = r;
buttonAxisRemaps[kvp.Key] = r;
}
foreach (var s in def.HapticsChannels)
{
string r = Allocate(s, ref plr, ref playerNext);
ret.HapticsChannels.Add(r);
feedbackRemaps[s] = r;
}
plr = playerNext;
unmergers.Add(new ControlDefUnMerger(remaps));
unmergers.Add(new ControlDefUnMerger(buttonAxisRemaps, feedbackRemaps));
}
return ret;
@ -57,22 +66,24 @@ namespace BizHawk.Emulation.Common
public class ControlDefUnMerger
{
private readonly Dictionary<string, string> _remaps;
public ControlDefUnMerger(Dictionary<string, string> remaps)
{
_remaps = remaps;
}
private class DummyController : IController
{
private readonly IController _src;
private readonly Dictionary<string, string> _remaps;
/// <inheritdoc cref="ControlDefUnMerger._buttonAxisRemaps"/>
private readonly IReadOnlyDictionary<string, string> _buttonAxisRemaps;
public DummyController(IController src, Dictionary<string, string> remaps)
/// <inheritdoc cref="ControlDefUnMerger._buttonAxisRemaps"/>
private readonly IReadOnlyDictionary<string, string> _feedbackRemaps;
private readonly IController _src;
public DummyController(
IController src,
IReadOnlyDictionary<string, string> buttonAxisRemaps,
IReadOnlyDictionary<string, string> feedbackRemaps)
{
_src = src;
_remaps = remaps;
_buttonAxisRemaps = buttonAxisRemaps;
_feedbackRemaps = feedbackRemaps;
}
/// <exception cref="NotImplementedException">always</exception>
@ -80,18 +91,36 @@ namespace BizHawk.Emulation.Common
public bool IsPressed(string button)
{
return _src.IsPressed(_remaps[button]);
return _src.IsPressed(_buttonAxisRemaps[button]);
}
public int AxisValue(string name)
{
return _src.AxisValue(_remaps[name]);
return _src.AxisValue(_buttonAxisRemaps[name]);
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot()
=> _src.GetHapticsSnapshot()
.Select(hapticsEntry => (_feedbackRemaps.First(kvpRemap => kvpRemap.Value == hapticsEntry.Name).Value, hapticsEntry.Strength)) // reverse lookup
.ToArray();
public void SetHapticChannelStrength(string name, int strength) => _src.SetHapticChannelStrength(_feedbackRemaps[name], strength);
}
public IController UnMerge(IController c)
/// <remarks>these need to be separate because it's expected that <c>"P1 Left"</c> will appear in both</remarks>
private readonly IReadOnlyDictionary<string, string> _buttonAxisRemaps;
/// <inheritdoc cref="_buttonAxisRemaps"/>
private readonly IReadOnlyDictionary<string, string> _feedbackRemaps;
public ControlDefUnMerger(
IReadOnlyDictionary<string, string> buttonAxisRemaps,
IReadOnlyDictionary<string, string> feedbackRemaps)
{
return new DummyController(c, _remaps);
_buttonAxisRemaps = buttonAxisRemaps;
_feedbackRemaps = feedbackRemaps;
}
public IController UnMerge(IController c) => new DummyController(c, _buttonAxisRemaps, _feedbackRemaps);
}
}

View File

@ -1,4 +1,6 @@
namespace BizHawk.Emulation.Common
using System.Collections.Generic;
namespace BizHawk.Emulation.Common
{
public interface IController
{
@ -7,6 +9,9 @@
/// </summary>
ControllerDefinition Definition { get; }
/// <seealso cref="SetHapticChannelStrength"/>
IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot();
/// <summary>
/// Returns the current state of a boolean control
/// </summary>
@ -16,5 +21,9 @@
/// Returns the state of an axis control
/// </summary>
int AxisValue(string name);
/// <param name="name">haptic channel name e.g. "P1 Mono", "P2 Left"</param>
/// <param name="strength">0..<see cref="int.MaxValue"/></param>
void SetHapticChannelStrength(string name, int strength);
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -95,5 +96,9 @@ namespace BizHawk.Emulation.Common
{
return _buttons[name];
}
public IReadOnlyCollection<(string Name, int Strength)> GetHapticsSnapshot() => Array.Empty<(string, int)>();
public void SetHapticChannelStrength(string name, int strength) {}
}
}