From d7e3df185b0d11a2a71ea3bcde9afa7de928ede1 Mon Sep 17 00:00:00 2001 From: Vasily Date: Sat, 19 Jan 2019 21:25:15 +0100 Subject: [PATCH] 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. --- advancedSettings.pro | 8 +- src/ivrinput/ivrinput_action.h | 15 ++-- src/ivrinput/ivrinput_action_set.h | 33 ++++++++ src/ivrinput/ivrinput_manifest.h | 44 ++++++++++ src/ivrinput/steamivrinput.cpp | 114 ++++++++++++++++++++++++++ src/ivrinput/steamivrinput.h | 50 +++++++++++ src/overlaycontroller.cpp | 5 +- src/overlaycontroller.h | 4 +- src/overlaycontroller/openvr_init.cpp | 4 + src/steamivrinput.cpp | 103 ----------------------- src/steamivrinput.h | 41 --------- 11 files changed, 264 insertions(+), 157 deletions(-) create mode 100644 src/ivrinput/ivrinput_action_set.h create mode 100644 src/ivrinput/ivrinput_manifest.h create mode 100644 src/ivrinput/steamivrinput.cpp create mode 100644 src/ivrinput/steamivrinput.h delete mode 100644 src/steamivrinput.cpp delete mode 100644 src/steamivrinput.h diff --git a/advancedSettings.pro b/advancedSettings.pro index 20b477a..333b26a 100644 --- a/advancedSettings.pro +++ b/advancedSettings.pro @@ -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 \ diff --git a/src/ivrinput/ivrinput_action.h b/src/ivrinput/ivrinput_action.h index 85d2463..f3fe439 100644 --- a/src/ivrinput/ivrinput_action.h +++ b/src/ivrinput/ivrinput_action.h @@ -2,10 +2,13 @@ #include #include +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 diff --git a/src/ivrinput/ivrinput_action_set.h b/src/ivrinput/ivrinput_action_set.h new file mode 100644 index 0000000..66b35a0 --- /dev/null +++ b/src/ivrinput/ivrinput_action_set.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +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 diff --git a/src/ivrinput/ivrinput_manifest.h b/src/ivrinput/ivrinput_manifest.h new file mode 100644 index 0000000..f00494e --- /dev/null +++ b/src/ivrinput/ivrinput_manifest.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include + +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 diff --git a/src/ivrinput/steamivrinput.cpp b/src/ivrinput/steamivrinput.cpp new file mode 100644 index 0000000..55ba537 --- /dev/null +++ b/src/ivrinput/steamivrinput.cpp @@ -0,0 +1,114 @@ +#include "steamivrinput.h" +#include +#include +#include +#include + +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 diff --git a/src/ivrinput/steamivrinput.h b/src/ivrinput/steamivrinput.h new file mode 100644 index 0000000..158a542 --- /dev/null +++ b/src/ivrinput/steamivrinput.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#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 diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 288fba7..1f94e98 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -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!"; } diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 9442b06..cb7beaa 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -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; diff --git a/src/overlaycontroller/openvr_init.cpp b/src/overlaycontroller/openvr_init.cpp index 5cc678c..b63ba1a 100644 --- a/src/overlaycontroller/openvr_init.cpp +++ b/src/overlaycontroller/openvr_init.cpp @@ -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() diff --git a/src/steamivrinput.cpp b/src/steamivrinput.cpp deleted file mode 100644 index 69edfda..0000000 --- a/src/steamivrinput.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "steamivrinput.h" -#include -#include -#include -#include - -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."; - } -} diff --git a/src/steamivrinput.h b/src/steamivrinput.h deleted file mode 100644 index 06f0adc..0000000 --- a/src/steamivrinput.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#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