Vibrate without calling Sleep
Great work, thank you for this project.
If I want to make the controller vibrate, I use something like this
if(Gamepad.IsConnected) { if(!Gamepad.IsVibrationEnabled) Gamepad.IsVibrationEnabled = true;
if(Gamepad.Device.SetMotorsSpeed(leftMotor, rightMotor) == false)
{
log.Error("Failed to set vibration motors speed on gamepad.");
}
Sleep(vibrateTime);
}
Vibration wont happen unless the sleep is there. I assume that the sleep allows the update timer to get some cycles in. Is there a clean way to allow for the update to happen? I want to have a way to vibrate the controller without sleeping the UI thread.
Neil
In the example you provided, you are using the XGamepad class. This class provides you two different ways of setting vibration.
- The easier and preferred way is using its
LeftMotorSpeedand/orRightMotorSpeedproperties to set the speed of each motor. This speed is then applied the next time you call the class'sUpdate()method. - The more low level way is by using its
Deviceproperty to get the underlyingXInputDeviceclass, which exposes theSetMotorSpeed(float, float)method, which is the approach you are using in your example. This method applies the vibration immediately when you call it. It returnsfalseif the controller is not connected or if there is some Win32 error.
In your example code, as long as the SetMotorSpeed method is returning true, you should be getting vibration immediately on your gamepad, because that method doesn't wait for an update.
However, the problem you described (the vibration not being applied immediately) sounds like it should be coming from some place else in the code. Since you mentioned an update timer, I should mention that XInputium is not thread safe, in case you are using one of .NET's built-in thread based timers.
In case you are updating the gamepad using a different thread, then that might be the issue.
If you could provide me a bit more context about how/where you are calling the Update() method, I may be able to give you a more precise solution. :)
Thank you for the detailed response. I created a method to start, delay, stop the vibration. The method fires off a task that has the motor speed setting and delay within the task. The Vibrate and StopVibrate methods change the LeftMotorSpeed and RightMotorSpeed properties invoked on the UI thread.
This package is much appreciated. The application is for a ROG Ally X based controller for a pyrotechnics firing system. The special FX people want haptic feedback on some features so they do not have to take eyes off of performers while controlling the FX. Arming/Disarming and Firing for example. I hope hearing about how your package is used provides some satisfaction.
In case anyone is interested, here is the code.
public override void FireShake()
{
if(Gamepad.IsConnected)
{
Task.Run(() =>
{
Vibrate(FullMotorSpeed, MotorOffSpeed);
Task.Delay(250).Wait();
StopVibrate();
});
}
}
public override void Vibrate(float leftMotor, float rightMotor)
{
Dispatcher.Invoke(() =>
{
if(Gamepad.IsConnected)
{
if(!Gamepad.IsVibrationEnabled)
Gamepad.IsVibrationEnabled = true;
Gamepad.LeftMotorSpeed = leftMotor;
Gamepad.RightMotorSpeed = rightMotor;
}
});
}
void StopVibrate()
{
Dispatcher.Invoke(() =>
{
if(Gamepad.IsConnected)
{
if(Gamepad.IsVibrationEnabled)
Gamepad.IsVibrationEnabled = false;
Gamepad.LeftMotorSpeed = 0f;
Gamepad.RightMotorSpeed = 0f;
}
});
}
It feels great to know that you're using XInputium for such a project! Thank you very much! :)
Just to confirm, with the code you just shared, are you still having the problem you mentioned before?
Even though the code may be working, I would like to kindly make some suggestions, if it's okay to you. I'm not familiar with the API you're working with in your application, so I will assume a few things. Apologies if my assumptions prove to be incorrect.
Inside the FireShake() method, you are using Task.Run() to run a new task, which has a call to Task.Delay() to wait a bit while vibrating. If the FireShake() method is called again in less than 250ms, you will have more than one task running, but since you use Dispatcher.Invoke() to access the gamepad, you don't have concurrency access problems. However, you might run out of threads in the thread pool if the FireShake() method is called many times very fast, and you might also have some unwanted delays due to the tasks not getting run immediately.
I would suggest a different approach that doesn't use threads, which uses the concept of the input loop. The input loop gets ticked once per each time you call Gamepad.Update(), wherever you are calling it, and counts the elapsed time between each tick, which you can get by using Gamepad.FrameTime property. If you keep track of the elapsed time between each tick, you can create a delay just like the one from Task.Delay() without using threads. This is the recommended approach.
To make it easier to use the input loop for waiting, you could write an extension method, something like this:
public static class XGamepadExtensions
{
// NOTE: This implementation doesn't support cancelling the timeout.
public static void Timeout(this XGamepad gamepad, TimeSpan duration, Action? onStarted, Action? onFinished)
{
TimeSpan remaining = duration;
EventHandler updatedHandler = (sender, e) =>
{
if (remaining <= TimeSpan.Zero)
{
gamepad.Updated -= updatedHandler;
onFinished?.Invoke();
}
remaining -= gamepad.FrameTime;
}
onStarted?.Invoke();
if (duration > TimeSpan.Zero)
gamepad.Updated += updatedHandler;
else
onFinished?.Invoke();
}
}
Then, you could use it like this:
public override void FireShake()
{
Gamepad.Timeout(TimeSpan.FromMilliseconds(250),
() =>
{
Gamepad.LeftMotorSpeed = FullMotorSpeed;
Gamepad.RightMotorSpeed = MotorOffSpeed;
},
() =>
{
Gamepad.LeftMotorSpeed = 0f;
Gamepad.RightMotorSpeed = 0f;
}
);
}
I didn't test the code I wrote above, but it should give you an idea of how you could implement it. Notice that the Timeout extension method does nothing to cancel currently running timeouts, and that might give you a bug if the FireShake() method is called more than once within 250 milliseconds — the early calls end first and stop the motors first, ignoring the latter calls' right to run for 250ms. This also happens with your current task based implementation. To solve that, you would need to implement the Timeout method differently, reusing the same remaining variable, or implement a timeout logic exclusively for the FireShake method.
The following alternative implementation might be simpler:
private TimeSpan _fireShakeRemaining = TimeSpan.Zero;
// Put this somewhere in your class's initialization logic (for example, the constructor)
Gamepad.Updated += (sender, e) => OnFireShakeUpdate();
public override void FireShake()
{
_fireShakeRemaining = TimeSpan.FromMilliseconds(250);
// Start vibrating
Gamepad.LeftMotorSpeed = FullMotorSpeed;
Gamepad.RightMotorSpeed = MotorOffSpeed;
}
private void OnFireShakeUpdate()
{
if (_fireShakeRemaining > TimeSpan.Zero)
{
_fireShakeRemaining -= Gamepad.FrameTime;
if (_fireShakeRemaining <= TimeSpan.Zero)
{
// Stop vibrating
Gamepad.LeftMotorSpeed = 0f;
Gamepad.RightMotorSpeed = 0f;
}
}
}
This alternative example above, may be simpler to implement, as it is more contained within your class's logic.
Alternatively, if you're using WPF (assumed from your use of Dispatcher.Invoke()), you might leverage WPF's animation API to animate the gamepad motors. I believe you would need to create a dependency property for each motor to be able to animate the properties, but it would bind the motors to WPF's rich animation system.
I hope my answer can give you one more tool for your toolbox and for anyone who might read it. These are only suggestions, and you are the one who knows your project and your use case better. :)