Skip to content
Permalink
Browse files

ControllerInterface: DInput: Update force feedback effects in a threa…

…d. This should prevent slowdowns experienced by a handful of users.
  • Loading branch information...
jordan-woyak committed Dec 22, 2018
1 parent d5df56c commit 0f19c4a40f8b49247233ed1f36e3e7836b617014
@@ -12,7 +12,6 @@

namespace MappingCommon
{

constexpr int INPUT_DETECT_TIME = 3000;
constexpr int OUTPUT_DETECT_TIME = 2000;

@@ -150,6 +150,8 @@ Joystick::Joystick(/*const LPCDIDEVICEINSTANCE lpddi, */ const LPDIRECTINPUTDEVI

Joystick::~Joystick()
{
DeInitForceFeedback();

m_device->Unacquire();
m_device->Release();
}
@@ -265,5 +267,5 @@ ControlState Joystick::Hat::GetState() const

return (abs((int)(m_hat / 4500 - m_direction * 2 + 8) % 8 - 4) > 2);
}
}
}
} // namespace DInput
} // namespace ciface
@@ -3,23 +3,25 @@
// Refer to the license.txt file included.

#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"

#include <algorithm>
#include <string>

#include "Common/Thread.h"

namespace ciface
{
namespace ForceFeedback
{
// template instantiation
template class ForceFeedbackDevice::Force<DICONSTANTFORCE>;
template class ForceFeedbackDevice::Force<DIRAMPFORCE>;
template class ForceFeedbackDevice::Force<DIPERIODIC>;
// Template instantiation:
template class ForceFeedbackDevice::TypedForce<DICONSTANTFORCE>;
template class ForceFeedbackDevice::TypedForce<DIRAMPFORCE>;
template class ForceFeedbackDevice::TypedForce<DIPERIODIC>;

struct ForceType
{
GUID guid;
const std::string name;
const char* name;
};

static const ForceType force_type_names[] = {
@@ -36,21 +38,57 @@ static const ForceType force_type_names[] = {
//{GUID_Friction, "Friction"},
};

void ForceFeedbackDevice::DeInitForceFeedback()
{
if (!m_run_thread.TestAndClear())
return;

SignalUpdateThread();
m_update_thread.join();
}

void ForceFeedbackDevice::ThreadFunc()
{
Common::SetCurrentThreadName("ForceFeedback update thread");

while (m_run_thread.IsSet())
{
m_update_event.Wait();

for (auto output : Outputs())
{
auto& force = *static_cast<Force*>(output);
force.UpdateOutput();
}
}

for (auto output : Outputs())
{
auto& force = *static_cast<Force*>(output);
force.Release();
}
}

void ForceFeedbackDevice::SignalUpdateThread()
{
m_update_event.Set();
}

bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, int cAxes)
{
if (cAxes == 0)
return false;

// TODO: check for DIDC_FORCEFEEDBACK in devcaps?

// temporary
// Temporary for creating the effect:
DWORD rgdwAxes[2] = {DIJOFS_X, DIJOFS_Y};
LONG rglDirection[2] = {-200, 0};

DIEFFECT eff;
memset(&eff, 0, sizeof(eff));
DIEFFECT eff{};
eff.dwSize = sizeof(DIEFFECT);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
// Infinite seems to work just fine:
eff.dwDuration = INFINITE; // (4 * DI_SECONDS)
eff.dwSamplePeriod = 0;
eff.dwGain = DI_FFNOMINALMAX;
@@ -60,19 +98,19 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
eff.rgdwAxes = rgdwAxes;
eff.rglDirection = rglDirection;

// initialize parameters
DICONSTANTFORCE diCF = {-10000};
// Initialize parameters.
DICONSTANTFORCE diCF{};
diCF.lMagnitude = DI_FFNOMINALMAX;
DIRAMPFORCE diRF = {0};
DIPERIODIC diPE = {0};
DIRAMPFORCE diRF{};
DIPERIODIC diPE{};

// doesn't seem needed
// This doesn't seem needed:
// DIENVELOPE env;
// eff.lpEnvelope = &env;
// ZeroMemory(&env, sizeof(env));
// env.dwSize = sizeof(env);

for (const ForceType& f : force_type_names)
for (auto& f : force_type_names)
{
if (f.guid == GUID_ConstantForce)
{
@@ -86,7 +124,7 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
}
else
{
// all other forces need periodic parameters
// All other forces need periodic parameters:
eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
eff.lpvTypeSpecificParams = &diPE;
}
@@ -95,15 +133,15 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
if (SUCCEEDED(device->CreateEffect(f.guid, &eff, &pEffect, nullptr)))
{
if (f.guid == GUID_ConstantForce)
AddOutput(new ForceConstant(f.name, pEffect));
AddOutput(new ForceConstant(this, f.name, pEffect));
else if (f.guid == GUID_RampForce)
AddOutput(new ForceRamp(f.name, pEffect));
AddOutput(new ForceRamp(this, f.name, pEffect));
else
AddOutput(new ForcePeriodic(f.name, pEffect));
AddOutput(new ForcePeriodic(this, f.name, pEffect));
}
}

// disable autocentering
// Disable autocentering:
if (Outputs().size())
{
DIPROPDWORD dipdw;
@@ -113,95 +151,114 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = DIPROPAUTOCENTER_OFF;
device->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph);

m_run_thread.Set();
m_update_thread = std::thread(&ForceFeedbackDevice::ThreadFunc, this);
}

return true;
}

template <typename P>
ForceFeedbackDevice::Force<P>::~Force()
void ForceFeedbackDevice::TypedForce<P>::PlayEffect()
{
m_iface->Stop();
m_iface->Unload();
m_iface->Release();
}

template <typename P>
void ForceFeedbackDevice::Force<P>::Update()
{
DIEFFECT eff = {};
DIEFFECT eff{};
eff.dwSize = sizeof(DIEFFECT);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;

eff.cbTypeSpecificParams = sizeof(P);
eff.lpvTypeSpecificParams = &params;

// set params and start effect
m_iface->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START);
eff.cbTypeSpecificParams = sizeof(m_params);
eff.lpvTypeSpecificParams = &m_params;
m_effect->SetParameters(&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START);
}

template <typename P>
void ForceFeedbackDevice::Force<P>::Stop()
void ForceFeedbackDevice::TypedForce<P>::StopEffect()
{
m_iface->Stop();
m_effect->Stop();
}

template <>
void ForceFeedbackDevice::ForceConstant::SetState(const ControlState state)
bool ForceFeedbackDevice::ForceConstant::UpdateParameters(int magnitude)
{
const LONG new_val = LONG(10000 * state);
const auto old_magnitude = m_params.lMagnitude;

if (params.lMagnitude == new_val)
return;
m_params.lMagnitude = magnitude;

params.lMagnitude = new_val;
if (new_val)
Update();
else
Stop();
return old_magnitude != m_params.lMagnitude;
}

template <>
void ForceFeedbackDevice::ForceRamp::SetState(const ControlState state)
bool ForceFeedbackDevice::ForceRamp::UpdateParameters(int magnitude)
{
const LONG new_val = LONG(10000 * state);
const auto old_magnitude = m_params.lStart;

if (params.lStart == new_val)
return;
m_params.lStart = m_params.lEnd = magnitude;

params.lStart = params.lEnd = new_val;
if (new_val)
Update();
else
Stop();
return old_magnitude != m_params.lStart;
}

template <>
void ForceFeedbackDevice::ForcePeriodic::SetState(const ControlState state)
bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters(int magnitude)
{
const DWORD new_val = DWORD(10000 * state);
const auto old_magnitude = m_params.dwMagnitude;

if (params.dwMagnitude == new_val)
return;
m_params.dwMagnitude = magnitude;
// Zero is working fine for me:
// params.dwPeriod = 0;//DWORD(0.05 * DI_SECONDS);

params.dwMagnitude = new_val;
if (new_val)
Update();
else
Stop();
return old_magnitude != m_params.dwMagnitude;
}

template <typename P>
ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface)
: m_name(name), m_iface(iface)
ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, std::string name,
LPDIRECTINPUTEFFECT effect)
: Force(parent, std::move(name), effect), m_params{}
{
memset(&params, 0, sizeof(params));
}

template <typename P>
std::string ForceFeedbackDevice::Force<P>::GetName() const
void ForceFeedbackDevice::TypedForce<P>::UpdateEffect(int magnitude)
{
if (UpdateParameters(magnitude))
{
if (magnitude)
PlayEffect();
else
StopEffect();
}
}

std::string ForceFeedbackDevice::Force::GetName() const
{
return m_name;
}

ForceFeedbackDevice::Force::Force(ForceFeedbackDevice* parent, const std::string name,
LPDIRECTINPUTEFFECT effect)
: m_effect(effect), m_parent(*parent), m_name(std::move(name)), m_desired_magnitude()
{
}

void ForceFeedbackDevice::Force::SetState(ControlState state)
{
const auto new_val = int(DI_FFNOMINALMAX * state);

if (m_desired_magnitude.exchange(new_val) != new_val)
m_parent.SignalUpdateThread();
}

void ForceFeedbackDevice::Force::UpdateOutput()
{
UpdateEffect(m_desired_magnitude);
}

void ForceFeedbackDevice::Force::Release()
{
// This isn't in the destructor because it should happen before the device is released.
m_effect->Stop();
m_effect->Unload();
m_effect->Release();
}

} // namespace ForceFeedback
} // namespace ciface

0 comments on commit 0f19c4a

Please sign in to comment.
You can’t perform that action at this time.