Refactor IVRInput logic into several classes

Keeping the action name, handle and type together allows for better
error messages and limited run time type checking, to ensure that the
wrong function isn't called.

It is necessary to initialize the manifest in a class, since the Actions
depend on this being set.

Several files have been moved around to be more consistent.
This commit is contained in:
Vasily
2019-01-19 21:25:15 +01:00
parent 8bcab9b2c0
commit d7e3df185b
11 changed files with 264 additions and 157 deletions
+5 -3
View File
@@ -59,7 +59,7 @@ SOURCES += src/main.cpp\
src/tabcontrollers/audiomanager/AudioManagerDummy.cpp \
src/tabcontrollers/keyboardinput/KeyboardInputDummy.cpp \
src/overlaycontroller/openvr_init.cpp \
src/steamivrinput.cpp
src/ivrinput/steamivrinput.cpp
HEADERS += src/overlaycontroller.h \
src/tabcontrollers/AudioTabController.h \
@@ -80,8 +80,10 @@ HEADERS += src/overlaycontroller.h \
src/tabcontrollers/audiomanager/AudioManagerDummy.h \
src/tabcontrollers/keyboardinput/KeyboardInputDummy.h \
src/overlaycontroller/openvr_init.h \
src/steamivrinput.h \
src/ivrinput/ivrinput_action.h
src/ivrinput/ivrinput_action.h \
src/ivrinput/ivrinput_manifest.h \
src/ivrinput/ivrinput_action_set.h \
src/ivrinput/steamivrinput.h
win32 {
SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \
+10 -5
View File
@@ -2,10 +2,13 @@
#include <openvr.h>
#include <easylogging++.h>
namespace input
{
enum class ActionType
{
Undefined,
Digital,
Analog,
};
class Action
@@ -22,21 +25,23 @@ public:
<< "'. OpenVR Error: " << error;
}
}
vr::VRActionHandle_t handle()
vr::VRActionHandle_t handle() const noexcept
{
return m_handle;
}
const char* name()
std::string name() const noexcept
{
return m_name;
}
ActionType type()
ActionType type() const noexcept
{
return m_type;
}
private:
vr::VRActionHandle_t m_handle = 0;
const char* m_name;
ActionType m_type = ActionType::Undefined;
const std::string m_name;
const ActionType m_type = ActionType::Undefined;
};
} // namespace input
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <openvr.h>
#include <easylogging++.h>
namespace input
{
class ActionSet
{
public:
ActionSet( const char* setName ) : m_name( setName )
{
auto error = vr::VRInput()->GetActionSetHandle( setName, &m_handle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting handle for '" << setName
<< "'. OpenVR Error: " << error;
}
}
std::string name()
{
return m_name;
}
vr::VRActionSetHandle_t handle()
{
return m_handle;
}
private:
const std::string m_name;
vr::VRActionSetHandle_t m_handle = 0;
};
} // namespace input
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <QStandardPaths>
#include <openvr.h>
#include <easylogging++.h>
namespace input
{
/*!
Initializes IVRInput to point at the correct manifest. Can't be done in the
constructor body since objects being initialized require this to be set.
*/
class Manifest
{
public:
Manifest()
{
QString actionManifestPath = QStandardPaths::locate(
QStandardPaths::AppDataLocation,
QStringLiteral( "action_manifest.json" ) );
auto error = vr::VRInput()->SetActionManifestPath(
actionManifestPath.toStdString().c_str() );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error setting action manifest path: "
<< actionManifestPath << ". OpenVR Error: " << error;
}
}
// Explicitly deleted because this object should do nothing but run the
// constructor during initialization.
// Destructor left blank on purpose since there are no terminating calls.
~Manifest() {}
// Copy constructor
Manifest( const Manifest& ) = delete;
// Copy assignment
Manifest& operator=( const Manifest& ) = delete;
// Move constructor
Manifest( const Manifest&& ) = delete;
// Move assignment
Manifest& operator=( const Manifest&& ) = delete;
};
} // namespace input
+114
View File
@@ -0,0 +1,114 @@
#include "steamivrinput.h"
#include <openvr.h>
#include <iostream>
#include <QStandardPaths>
#include <easylogging++.h>
namespace input
{
vr::InputDigitalActionData_t getDigitalActionData( Action& action )
{
if ( action.type() != ActionType::Digital )
{
LOG( ERROR )
<< "Action was passed to IVRInput getDigitalActionData without "
"being a digital type. Action: "
<< action.name();
throw std::runtime_error(
"Action was passed to IVRInput getDigitalActionData without being "
"a digital type. See log for details." );
}
vr::InputDigitalActionData_t handleData = {};
const auto error = vr::VRInput()->GetDigitalActionData(
action.handle(),
&handleData,
sizeof( handleData ),
vr::k_ulInvalidInputValueHandle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting IVRInput Digital Action Data for handle "
<< action.name() << ". SteamVR Error: " << error;
}
return handleData;
}
vr::InputAnalogActionData_t getAnalogActionData( Action& action )
{
if ( action.type() != ActionType::Analog )
{
LOG( ERROR )
<< "Action was passed to IVRInput getAnalogActionData without "
"being an analog type. Action: "
<< action.name();
throw std::runtime_error(
"Action was passed to IVRInput getAnalogActionData without being "
"an analog type. See log for details." );
}
vr::InputAnalogActionData_t handleData = {};
const auto error
= vr::VRInput()->GetAnalogActionData( action.handle(),
&handleData,
sizeof( handleData ),
vr::k_ulInvalidInputValueHandle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting IVRInput Digital Action Data for handle "
<< action.name() << ". SteamVR Error: " << error;
}
return handleData;
}
bool isDigitalActionActivatedOnce( Action& action )
{
const auto handleData = getDigitalActionData( action );
return handleData.bState && handleData.bChanged;
}
bool isDigitalActionActivatedConstant( Action& action )
{
const auto handleData = getDigitalActionData( action );
return handleData.bState;
}
SteamIVRInput::SteamIVRInput()
: m_manifest(), m_mainSet( input_strings::k_setMain ),
m_nextTrack( input_strings::k_actionNextTrack, ActionType::Digital )
{
m_activeActionSet.ulActionSet = m_mainSet.handle();
m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle;
m_activeActionSet.nPriority = 0;
}
bool SteamIVRInput::nextSong()
{
return isDigitalActionActivatedOnce( m_nextTrack );
}
void SteamIVRInput::Loop()
{
constexpr auto numberOfSets = 1;
const auto error = vr::VRInput()->UpdateActionState(
&m_activeActionSet, sizeof( m_activeActionSet ), numberOfSets );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR )
<< "Error during IVRInput action state update. OpenVR Error: "
<< error;
}
}
} // namespace input
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include <openvr.h>
#include "ivrinput_action.h"
#include "ivrinput_manifest.h"
#include "ivrinput_action_set.h"
namespace input
{
class SteamIVRInput
{
public:
SteamIVRInput();
void Loop();
bool nextSong();
// Destructor. There are no terminating calls for the IVRInput API, so it
// is left blank.
~SteamIVRInput() {}
// These have been explicitly deleted to make sure there are no attempts at
// copying the class in weird ways. It is not worth defining what should
// happen on copy because it simply shouldn't be done.
// They are declared public because compilers will give wrong error messages
// if they are private.
// Copy constructor
SteamIVRInput( const SteamIVRInput& ) = delete;
// Copy assignment
SteamIVRInput& operator=( const SteamIVRInput& ) = delete;
// Move constructor
SteamIVRInput( SteamIVRInput&& ) = delete;
// Move assignment
SteamIVRInput& operator=( const SteamIVRInput&& ) = delete;
private:
Manifest m_manifest;
ActionSet m_mainSet;
vr::VRActiveActionSet_t m_activeActionSet = {};
Action m_nextTrack;
};
namespace input_strings
{
constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack";
constexpr auto k_setMain = "/actions/main";
} // namespace input_strings
} // namespace input
+2 -3
View File
@@ -32,7 +32,7 @@ OverlayController::OverlayController( bool desktopMode,
bool noSound,
QQmlEngine& qmlEngine )
: QObject(), m_desktopMode( desktopMode ), m_noSound( noSound ),
m_openVrInit()
m_openVrInit(), m_actions()
{
// Despite arguably being OpenVR init code, the call is still here because
// the TabController uses this directly. Offering it through OpenVR_Init
@@ -118,7 +118,6 @@ OverlayController::OverlayController( bool desktopMode,
nullptr, "OpenVR Advanced Settings Overlay", "Is OpenVR running?" );
throw std::runtime_error( std::string( "No Overlay interface" ) );
}
m_actions.Init();
// Init controllers
m_steamVRTabController.initStage1();
m_chaperoneTabController.initStage1();
@@ -479,7 +478,7 @@ void OverlayController::OnTimeoutPumpEvents()
m_actions.Loop();
if ( m_actions.nextSongSet() )
if ( m_actions.nextSong() )
{
LOG( INFO ) << "NEXT SONG SET!";
}
+2 -2
View File
@@ -39,7 +39,7 @@
#include "tabcontrollers/UtilitiesTabController.h"
#include "tabcontrollers/AccessibilityTabController.h"
#include "steamivrinput.h"
#include "ivrinput/steamivrinput.h"
// application namespace
namespace advsettings
@@ -92,7 +92,7 @@ private:
public: // I know it's an ugly hack to make them public to enable external
// access, but I am too lazy to implement getters.
SteamIVRInput m_actions;
input::SteamIVRInput m_actions;
SteamVRTabController m_steamVRTabController;
ChaperoneTabController m_chaperoneTabController;
MoveCenterTabController m_moveCenterTabController;
+4
View File
@@ -70,6 +70,10 @@ OpenVR_Init::OpenVR_Init()
{
reportVersionError( vr::IVRNotifications_Version );
}
else if ( !vr::VR_IsInterfaceVersionValid( vr::IVRInput_Version ) )
{
reportVersionError( vr::IVRInput_Version );
}
}
OpenVR_Init::~OpenVR_Init()
-103
View File
@@ -1,103 +0,0 @@
#include "steamivrinput.h"
#include <openvr.h>
#include <iostream>
#include <QStandardPaths>
#include <easylogging++.h>
void setActionManifestPath( const QString actionManifestPath )
{
auto error = vr::VRInput()->SetActionManifestPath(
actionManifestPath.toStdString().c_str() );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error setting action manifest path: "
<< actionManifestPath << ". OpenVR Error: " << error;
}
}
vr::VRActionHandle_t getActionHandle( const char* const action )
{
vr::VRActionHandle_t actionHandle = 0;
auto error = vr::VRInput()->GetActionHandle( action, &actionHandle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting handle for '" << action
<< "'. OpenVR Error: " << error;
}
return actionHandle;
}
vr::VRActionSetHandle_t getActionSetHandle( const char* const set )
{
vr::VRActionSetHandle_t setHandle = 0;
auto error = vr::VRInput()->GetActionSetHandle( set, &setHandle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting handle for '" << set
<< "'. OpenVR Error: " << error;
}
return setHandle;
}
vr::InputDigitalActionData_t getDigitalActionData( vr::VRActionHandle_t handle )
{
vr::InputDigitalActionData_t handleData = {};
auto error = vr::VRInput()->GetDigitalActionData(
handle,
&handleData,
sizeof( handleData ),
vr::k_ulInvalidInputValueHandle );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Error getting Digital Action Data for handle "
<< handle << ". SteamVR Error: " << error;
}
return handleData;
}
bool isActionActivatedOnce( vr::VRActionHandle_t handle )
{
auto handleData = getDigitalActionData( handle );
return handleData.bState && handleData.bChanged;
}
void SteamIVRInput::Init()
{
QString actionManifestPath
= QStandardPaths::locate( QStandardPaths::AppDataLocation,
QStringLiteral( "action_manifest.json" ) );
setActionManifestPath( actionManifestPath );
m_nextSongHandler = getActionHandle( input_strings::k_actionNextTrack );
m_mainSetHandler = getActionSetHandle( input_strings::k_setMain );
m_activeActionSet.ulActionSet = m_mainSetHandler;
m_activeActionSet.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle;
m_activeActionSet.nPriority = 0;
}
bool SteamIVRInput::nextSongSet()
{
return isActionActivatedOnce( m_nextSongHandler );
}
void SteamIVRInput::Loop()
{
auto error = vr::VRInput()->UpdateActionState(
&m_activeActionSet, sizeof( m_activeActionSet ), 1 );
if ( error != vr::EVRInputError::VRInputError_None )
{
LOG( ERROR ) << "Loop error.";
}
}
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include <openvr.h>
#include "ivrinput/ivrinput_action.h"
class SteamIVRInput
{
public:
// SteamIVRInput();
void Init();
void Loop();
bool nextSongSet();
// These have been explicitly deleted to make sure there are no attempts at
// copying the class in weird ways. It is not worth defining what should
// happen on copy because it simply shouldn't be done.
// Destructor. There are not terminating calls for the IVRInput API, so it
// is unneeded.
//~SteamIVRInput() = delete;
//// Copy constructor
// SteamIVRInput( const SteamIVRInput& ) = delete;
//// Copy assignment
// SteamIVRInput& operator=( const SteamIVRInput& ) = delete;
//// Move constructor
// SteamIVRInput( SteamIVRInput&& ) = delete;
//// Move assignment
// SteamIVRInput& operator=( const SteamIVRInput&& ) = delete;
private:
vr::VRActionHandle_t m_nextSongHandler = {};
vr::VRActionSetHandle_t m_mainSetHandler = {};
vr::VRActiveActionSet_t m_activeActionSet = {};
vr::InputDigitalActionData_t m_nextSongData = {};
};
namespace input_strings
{
constexpr auto k_actionNextTrack = "/actions/main/in/PlayNextTrack";
constexpr auto k_setMain = "/actions/main";
} // namespace input_strings