Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix recently discovered input bugs #66

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Core/Key2Joy.Contracts/Mapping/Triggers/AbstractTriggerListener.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Key2Joy.Contracts.Mapping.Triggers;

public abstract class AbstractTriggerListener : MarshalByRefObject
{
/// <summary>
/// Set this to true if you want the listener to get WndProc messages.
/// Sent from the main form.
/// </summary>
public virtual bool HasWndProcHandle { get; } = false;

/// <summary>
/// WndProc handler for the listener. Only called if <see cref="HasWndProcHandle"/>
/// </summary>
/// <param name="m"></param>
public virtual void WndProc(ref Message m) { }

/// <summary>
/// Called when a trigger is about to activate. Listeners can modify which
/// mapped option candidates will be executed.
Expand Down
10 changes: 9 additions & 1 deletion Core/Key2Joy.Contracts/Util/TypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ public static object ConvertToType(object value, Type desiredType)
}
else if (desiredType.IsEnum)
{
value = Enum.Parse(desiredType, value.ToString());
try
{
value = Enum.Parse(desiredType, value.ToString());
}
catch (ArgumentException)
{
Output.WriteLine($"Could not parse value '{value}' to enum '{desiredType}'. Using first value instead.");
value = Enum.GetValues(desiredType).GetValue(0);
}
}
else if (desiredType == typeof(DateTime))
{
Expand Down
6 changes: 5 additions & 1 deletion Core/Key2Joy.Core/IKey2JoyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ public interface IKey2JoyManager

/// <summary>
/// Arms the mapping options so the triggers cause the actions to be executed.
/// Optionally don't arm explicit trigger listeners (e.g. for testing). Explicit trigger listeners
/// are always loaded, regardless if they're mapped. That allows scripts to ask them if they know
/// if input is triggered.
/// </summary>
/// <param name="profile"></param>
/// <param name="withExplicitTriggerListeners"></param>
/// <exception cref="MappingArmingFailedException">Occurs when an illegal configuration can't be started</exception>
void ArmMappings(MappingProfile profile);
void ArmMappings(MappingProfile profile, bool withExplicitTriggerListeners = true);

bool GetIsArmed(MappingProfile profile = null);

Expand Down
3 changes: 2 additions & 1 deletion Core/Key2Joy.Core/Key2Joy.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

<ItemGroup>
<PackageReference Include="CommonServiceLocator" Version="2.0.7" />
<PackageReference Include="Jint" Version="3.0.0-preview-488" />
<PackageReference Include="Jint" Version="3.1.4" />
<PackageReference Include="KeraLua">
<Version>1.3.4</Version>
</PackageReference>
Expand All @@ -76,6 +76,7 @@
<PackageReference Include="NLua">
<Version>1.6.3</Version>
</PackageReference>
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
<PackageReference Include="SimWinGamePad">
<Version>1.1.1</Version>
</PackageReference>
Expand Down
64 changes: 42 additions & 22 deletions Core/Key2Joy.Core/Key2JoyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using CommonServiceLocator;
using Key2Joy.Config;
using Key2Joy.Contracts.Mapping.Actions;
Expand All @@ -14,6 +15,8 @@
using Key2Joy.LowLevelInput.XInput;
using Key2Joy.Mapping;
using Key2Joy.Mapping.Actions.Logic;
using Key2Joy.Mapping.Triggers;
using Key2Joy.Mapping.Triggers.GamePad;
using Key2Joy.Mapping.Triggers.Keyboard;
using Key2Joy.Mapping.Triggers.Mouse;
using Key2Joy.Plugins;
Expand Down Expand Up @@ -47,17 +50,14 @@ public static Key2JoyManager Instance
}
}

/// <summary>
/// Trigger listeners that should explicitly loaded. This ensures that they're available for scripts
/// even if no mapping option is mapped to be triggered by it.
/// </summary>
public IList<AbstractTriggerListener> ExplicitTriggerListeners { get; set; }

private const string READY_MESSAGE = "Key2Joy is ready";
private static AppCommandRunner commandRunner;
private MappingProfile armedProfile;
private List<AbstractTriggerListener> armedListeners;
private IHaveHandleAndInvoke handleAndInvoker;

private List<AbstractTriggerListener> wndProcListeners = new List<AbstractTriggerListener>();

private Key2JoyManager()
{ }

Expand All @@ -73,18 +73,6 @@ public static void InitSafely(AppCommandRunner commandRunner, Action<PluginSet>
var serviceLocator = new DependencyServiceLocator();
ServiceLocator.SetLocatorProvider(() => serviceLocator);

instance = new Key2JoyManager
{
ExplicitTriggerListeners = new List<AbstractTriggerListener>()
{
// Always add these listeners so scripts can ask them if stuff has happened.
KeyboardTriggerListener.Instance,
MouseButtonTriggerListener.Instance,
MouseMoveTriggerListener.Instance
}
};
serviceLocator.Register<IKey2JoyManager>(instance);

#pragma warning disable IDE0001 // Simplify Names
serviceLocator.Register<IConfigManager>(configManager ??= new ConfigManager());
#pragma warning restore IDE0001 // Simplify Names
Expand All @@ -98,6 +86,9 @@ public static void InitSafely(AppCommandRunner commandRunner, Action<PluginSet>
var commandRepository = new CommandRepository();
serviceLocator.Register<ICommandRepository>(commandRepository);

instance = new Key2JoyManager();
serviceLocator.Register<IKey2JoyManager>(instance);

// Load plugins
var pluginDirectoriesPaths = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
pluginDirectoriesPaths = Path.Combine(pluginDirectoriesPaths, PluginsDirectory);
Expand Down Expand Up @@ -159,12 +150,26 @@ public bool GetIsArmed(MappingProfile profile = null)
}

/// <inheritdoc/>
public void ArmMappings(MappingProfile profile)
public void ArmMappings(MappingProfile profile, bool withExplicitTriggerListeners = true)
{
this.armedProfile = profile;

var allListeners = new List<AbstractTriggerListener>();
allListeners.AddRange(this.ExplicitTriggerListeners);
var allListeners = this.armedListeners = new();

if (withExplicitTriggerListeners)
{
allListeners.AddRange([
// Trigger listeners that should explicitly loaded. This ensures that they're available for scripts
// even if no mapping option is mapped to be triggered by it.
// Always add these listeners so scripts can ask them if stuff has happened.
KeyboardTriggerListener.NewInstance(),
MouseButtonTriggerListener.NewInstance(),
MouseMoveTriggerListener.NewInstance(),
GamePadButtonTriggerListener.NewInstance(),
GamePadStickTriggerListener.NewInstance(),
GamePadTriggerTriggerListener.NewInstance(),
]);
}

var allActions = (IList<AbstractAction>)profile.MappedOptions.Select(m => m.Action).ToList();

Expand All @@ -190,6 +195,11 @@ public void ArmMappings(MappingProfile profile)
allListeners.Add(listener);
}

if (listener.HasWndProcHandle)
{
wndProcListeners.Add(listener);
}

mappedOption.Action.OnStartListening(listener, ref allActions);
listener.AddMappedOption(mappedOption);
}
Expand All @@ -214,9 +224,17 @@ public void ArmMappings(MappingProfile profile)
}
}

public void CallWndProc(ref Message m)
{
foreach (var wndProcListener in wndProcListeners)
{
wndProcListener.WndProc(ref m);
}
}

public void DisarmMappings()
{
var listeners = this.ExplicitTriggerListeners;
var listeners = this.armedListeners;

// Clear all intervals
IdPool.CancelAll();
Expand All @@ -237,6 +255,8 @@ public void DisarmMappings()
}
}

wndProcListeners.Clear();

foreach (var listener in listeners)
{
listener.StopListening();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
using System;
using SimWinInput;

namespace Key2Joy.LowLevelInput.SimulatedGamePad;

/// <summary>
/// Result for the <see cref="ISimulatedGamePad.AccessState"/> method.
/// </summary>
public enum StateAccessorResult
{
/// <summary>
/// The accessor didn't change the state, and we don't have to do anything.
/// </summary>
Unchanged = 0,

/// <summary>
/// The state was changed by the accessor, the state needs to be updated.
/// </summary>
Changed = 1,
}

/// <summary>
/// Represents a simulated gamepad device.
/// </summary>
Expand Down Expand Up @@ -48,21 +65,19 @@ public interface ISimulatedGamePad
void ReleaseControl(GamePadControl control);

/// <summary>
/// Get the raw input state from the GamePad
/// Access the raw input state from the GamePad with this callback.
/// If the callback returns true the state will be updated to whatever
/// it was mutated to.
/// Mutation should be locked to one thread at a time.
/// </summary>
/// <returns>The raw input state of the gamepad.</returns>
SimulatedGamePadState GetState();
/// <param name="stateAccessor"></param>
void AccessState(Func<SimulatedGamePadState, StateAccessorResult> stateAccessor);

/// <summary>
/// Resets the GamePad state to the natural at-rest stat
/// </summary>
void ResetState();

/// <summary>
/// Update any changes made to the state to be reflected in the gamepad
/// </summary>
void Update();

/// <summary>
/// Returns the gamepad info on this device
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using SimWinInput;

namespace Key2Joy.LowLevelInput.SimulatedGamePad;
Expand All @@ -15,6 +16,8 @@ public class SimulatedGamePad : ISimulatedGamePad
private bool isPluggedIn = false;
private readonly IGamePadInfo gamePadInfo;

private object stateLock = new();

public SimulatedGamePad(int index)
{
this.Index = index;
Expand All @@ -32,7 +35,6 @@ public void PlugIn()

// Ensure the state starts reset fixes problem where other (real) gamepad may get button stuck
this.ResetState();
this.Update();
}

/// <inheritdoc />
Expand Down Expand Up @@ -68,20 +70,31 @@ public void ReleaseControl(GamePadControl control)
}

/// <inheritdoc />
public SimulatedGamePadState GetState()
=> SimGamePad.Instance.State[this.Index];

/// <inheritdoc />
public void ResetState()
public void AccessState(Func<SimulatedGamePadState, StateAccessorResult> stateAccessor)
{
SimGamePad.Instance.State[this.Index].Reset();
this.gamePadInfo.OnActivityOccurred();
// Commented, because as expected this freezes the app up when
// everyone has to wait for the lock to be released. And it didn't
// fix #61 like I hoped it would.
//lock (this.stateLock)
{
var state = SimGamePad.Instance.State[this.Index];

if (stateAccessor(state) == StateAccessorResult.Unchanged)
{
return;
}

SimGamePad.Instance.Update(this.Index);
this.gamePadInfo.OnActivityOccurred();
}
}

/// <inheritdoc />
public void Update()
{
SimGamePad.Instance.Update(this.Index);
this.gamePadInfo.OnActivityOccurred();
}
public void ResetState()
=> this.AccessState((state) =>
{
state.Reset();

return StateAccessorResult.Changed;
});
}
9 changes: 3 additions & 6 deletions Core/Key2Joy.Core/Mapping/Actions/Input/GamePadResetAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,15 @@ public async void ExecuteForScript(int gamepadIndex = 0)
gamePad.PlugIn();
}

var state = gamePad.GetState();
state.Reset();
gamePad.Update();
gamePad.ResetState();
}

public override async Task Execute(AbstractInputBag inputBag = null)
{
var gamePadService = ServiceLocator.Current.GetInstance<ISimulatedGamePadService>();
var gamePad = gamePadService.GetGamePad(this.GamePadIndex);
var state = gamePad.GetState();
state.Reset();
gamePad.Update();

gamePad.ResetState();
}

public override string GetNameDisplay() => this.Name.Replace("{0}", this.GamePadIndex.ToString());
Expand Down
Loading
Loading