From 03ef571bde322cedffd2d6406333ae5f17b48049 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 22 Mar 2023 02:24:44 +0300 Subject: [PATCH 01/24] Implement async IPC --- CMakeLists.txt | 5 + src/Logger.cpp | 18 ++ src/Logger.hpp | 7 + src/TrackerDevice.cpp | 6 +- src/TrackerDevice.hpp | 41 ++- src/VRDriver.cpp | 296 +++++++++--------- src/VRDriver.hpp | 18 +- src/bridge/BridgeClient.cpp | 187 ++++++++++++ src/bridge/BridgeClient.hpp | 97 ++++++ src/bridge/CircularBuffer.hpp | 97 ++++++ src/bridge/bridge-unix-sockets.cpp | 180 ----------- src/bridge/bridge-windows-pipes.cpp | 137 --------- src/bridge/bridge.hpp | 45 --- src/bridge/unix-sockets.hpp | 446 ---------------------------- vcpkg.json | 4 +- 15 files changed, 596 insertions(+), 988 deletions(-) create mode 100644 src/Logger.cpp create mode 100644 src/Logger.hpp create mode 100644 src/bridge/BridgeClient.cpp create mode 100644 src/bridge/BridgeClient.hpp create mode 100644 src/bridge/CircularBuffer.hpp delete mode 100644 src/bridge/bridge-unix-sockets.cpp delete mode 100644 src/bridge/bridge-windows-pipes.cpp delete mode 100644 src/bridge/bridge.hpp delete mode 100644 src/bridge/unix-sockets.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0873831..bd4df7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,10 @@ SET_SOURCE_FILES_PROPERTIES(${PROTO_SRC} ${PROTO_INCL} PROPERTIES GENERATED TRUE find_package(simdjson CONFIG REQUIRED) +# libuv +find_package(libuv REQUIRED) +find_package(uvw REQUIRED) + # Project file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") @@ -77,6 +81,7 @@ target_include_directories("${PROJECT_NAME}" PUBLIC "${OPENVR_INCLUDE_DIR}") target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libraries/linalg") target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/") target_link_libraries("${PROJECT_NAME}" PUBLIC "${OPENVR_LIB}" protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite simdjson::simdjson) +target_link_libraries("${PROJECT_NAME}" PUBLIC $,uv_a,uv>) # libuv set_property(TARGET "${PROJECT_NAME}" PROPERTY CXX_STANDARD 17) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..5cd6e3e --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,18 @@ +#include "Logger.hpp" + +void Log(const char* format, ...) { + va_list args; + va_start(args, format); + va_list args_copy; + va_copy(args_copy, args); + size_t len = std::vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + + std::vector buf(len + 1); + std::vsnprintf(buf.data(), buf.size(), format, args); + va_end(args); + + std::ostringstream ss; + ss << buf.data() << std::endl; + vr::VRDriverLog()->Log(ss.str().c_str()); +} \ No newline at end of file diff --git a/src/Logger.hpp b/src/Logger.hpp new file mode 100644 index 0000000..eafdd43 --- /dev/null +++ b/src/Logger.hpp @@ -0,0 +1,7 @@ +#pragma once +#include +#include +#include +#include + +void Log(const char* format, ...); \ No newline at end of file diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index b74064c..53d4743 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -48,7 +48,7 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) // Setup pose for this frame auto pose = this->last_pose_; //send the new position and rotation from the pipe to the tracker object - if(position.has_x()) { + if (position.has_x()) { pose.vecPosition[0] = position.x(); pose.vecPosition[1] = position.y(); pose.vecPosition[2] = position.z(); @@ -153,11 +153,11 @@ vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) // Automatically select vive tracker roles and set hints for games that need it (Beat Saber avatar mod, for example) auto roleHint = getViveRoleHint(trackerRole); - if(roleHint != "") + if (roleHint != "") GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, roleHint.c_str()); auto role = getViveRole(trackerRole); - if(role != "") + if (role != "") vr::VRSettings()->SetString(vr::k_pch_Trackers_Section, ("/devices/slimevr/" + this->serial_).c_str(), role.c_str()); return vr::EVRInitError::VRInitError_None; diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp index c894684..932627e 100644 --- a/src/TrackerDevice.hpp +++ b/src/TrackerDevice.hpp @@ -12,31 +12,30 @@ #include #include #include -#include "bridge/bridge.hpp" #include "TrackerRole.hpp" namespace SlimeVRDriver { class TrackerDevice : public IVRDevice { - public: - - TrackerDevice(std::string serial, int deviceId, TrackerRole trackerRole); - ~TrackerDevice() = default; - - // Inherited via IVRDevice - virtual std::string GetSerial() override; - virtual void Update() override; - virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override; - virtual DeviceType GetDeviceType() override; - - virtual vr::EVRInitError Activate(uint32_t unObjectId) override; - virtual void Deactivate() override; - virtual void EnterStandby() override; - virtual void* GetComponent(const char* pchComponentNameAndVersion) override; - virtual void DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) override; - virtual vr::DriverPose_t GetPose() override; - virtual int getDeviceId() override; - virtual void PositionMessage(messages::Position &position) override; - virtual void StatusMessage(messages::TrackerStatus &status) override; + public: + + TrackerDevice(std::string serial, int deviceId, TrackerRole trackerRole); + ~TrackerDevice() = default; + + // Inherited via IVRDevice + virtual std::string GetSerial() override; + virtual void Update() override; + virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override; + virtual DeviceType GetDeviceType() override; + + virtual vr::EVRInitError Activate(uint32_t unObjectId) override; + virtual void Deactivate() override; + virtual void EnterStandby() override; + virtual void* GetComponent(const char* pchComponentNameAndVersion) override; + virtual void DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) override; + virtual vr::DriverPose_t GetPose() override; + virtual int getDeviceId() override; + virtual void PositionMessage(messages::Position &position) override; + virtual void StatusMessage(messages::TrackerStatus &status) override; private: vr::TrackedDeviceIndex_t device_index_ = vr::k_unTrackedDeviceIndexInvalid; std::string serial_; diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 24b4533..223d7cb 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -1,14 +1,11 @@ #include "VRDriver.hpp" #include -#include "bridge/bridge.hpp" #include "TrackerRole.hpp" #include #include #include "VRPaths_openvr.hpp" - -vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverContext) -{ +vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverContext) { // Perform driver context initialisation if (vr::EVRInitError init_error = vr::InitServerDriverContext(pDriverContext); init_error != vr::EVRInitError::VRInitError_None) { return init_error; @@ -20,25 +17,26 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont auto json = simdjson::padded_string::load(GetVRPathRegistryFilename()); // load VR Path Registry simdjson::ondemand::document doc = json_parser.iterate(json); auto path = std::string { doc.get_object()["config"].at(0).get_string().value() }; - // Log(path); default_chap_path_ = GetDefaultChaperoneFromConfigPath(path); } catch (simdjson::simdjson_error& e) { - std::stringstream ss; - ss << "Error getting VR Config path, continuing: " << e.error(); - Log(ss.str()); + Log("Error getting VR Config path, continuing: %s", e.error()); } Log("SlimeVR Driver Loaded Successfully"); + bridge = std::make_shared( + std::bind(&SlimeVRDriver::VRDriver::OnBridgeMessage, this, std::placeholders::_1) + ); + bridge->start(); + return vr::VRInitError_None; } -void SlimeVRDriver::VRDriver::Cleanup() -{ +void SlimeVRDriver::VRDriver::Cleanup() { + bridge->stop(); } -void SlimeVRDriver::VRDriver::RunFrame() -{ +void SlimeVRDriver::VRDriver::RunFrame() { google::protobuf::Arena arena; // Collect events @@ -56,154 +54,156 @@ void SlimeVRDriver::VRDriver::RunFrame() this->last_frame_time_ = now; // Update devices - for(auto& device : this->devices_) - device->Update(); - - BridgeStatus status = runBridgeFrame(*this); - if(status == BRIDGE_CONNECTED) { - messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); - // Read all messages from the bridge - while(getNextBridgeMessage(*message, *this)) { - if(message->has_tracker_added()) { - messages::TrackerAdded ta = message->tracker_added(); - switch(getDeviceType(static_cast(ta.tracker_role()))) { - case DeviceType::TRACKER: - this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); - Log("New tracker device added " + ta.tracker_serial() + " (id " + std::to_string(ta.tracker_id()) + ")"); - break; - } - } else if(message->has_position()) { - messages::Position pos = message->position(); - auto device = this->devices_by_id.find(pos.tracker_id()); - if(device != this->devices_by_id.end()) { - device->second->PositionMessage(pos); - } - } else if(message->has_tracker_status()) { - messages::TrackerStatus status = message->tracker_status(); - auto device = this->devices_by_id.find(status.tracker_id()); - if (device != this->devices_by_id.end()) { - device->second->StatusMessage(status); - } - } - } + { + std::lock_guard lock(devices_mutex_); + for (auto& device : this->devices_) + device->Update(); + } - if(!sentHmdAddMessage) { - // Send add message for HMD - messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_added(trackerAdded); - trackerAdded->set_tracker_id(0); - trackerAdded->set_tracker_role(TrackerRole::HMD); - trackerAdded->set_tracker_serial("HMD"); - trackerAdded->set_tracker_name("HMD"); - sendBridgeMessage(*message, *this); - - messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_status(trackerStatus); - trackerStatus->set_tracker_id(0); - trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - sendBridgeMessage(*message, *this); - - sentHmdAddMessage = true; - Log("Sent HMD hello message"); - } + if (!bridge->isConnected()) { + // If bridge not connected, assume we need to resend hmd tracker add message + sentHmdAddMessage = false; + return; + } - uint64_t universe = vr::VRProperties()->GetUint64Property(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_CurrentUniverseId_Uint64); - if (!current_universe.has_value() || current_universe.value().first != universe) { - auto res = search_universes(universe); - if (res.has_value()) { - current_universe.emplace(universe, res.value()); - } else { - Log("Failed to find current universe!"); - } - } + messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); + + if (!sentHmdAddMessage) { + // Send add message for HMD + messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_added(trackerAdded); + trackerAdded->set_tracker_id(0); + trackerAdded->set_tracker_role(TrackerRole::HMD); + trackerAdded->set_tracker_serial("HMD"); + trackerAdded->set_tracker_name("HMD"); + bridge->sendBridgeMessage(*message); + + messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_status(trackerStatus); + trackerStatus->set_tracker_id(0); + trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + bridge->sendBridgeMessage(*message); + + sentHmdAddMessage = true; + Log("Sent HMD hello message"); + } - vr::TrackedDevicePose_t hmd_pose[10]; - vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); - - vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); - vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); - - if (current_universe.has_value()) { - auto trans = current_universe.value().second; - pos.v[0] += trans.translation.v[0]; - pos.v[1] += trans.translation.v[1]; - pos.v[2] += trans.translation.v[2]; - - // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0 - auto tmp_w = cos(-trans.yaw / 2); - auto tmp_y = sin(-trans.yaw / 2); - auto new_w = tmp_w * q.w - tmp_y * q.y; - auto new_x = tmp_w * q.x + tmp_y * q.z; - auto new_y = tmp_w * q.y + tmp_y * q.w; - auto new_z = tmp_w * q.z - tmp_y * q.x; - - q.w = new_w; - q.x = new_x; - q.y = new_y; - q.z = new_z; - - // rotate point on the xz plane by -trans.yaw radians - // this is equivilant to the quaternion multiplication, after applying the double angle formula. - float tmp_sin = sin(-trans.yaw); - float tmp_cos = cos(-trans.yaw); - auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin; - auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos; - - pos.v[0] = pos_x; - pos.v[2] = pos_z; + vr::PropertyContainerHandle_t hmdPropContainer = + vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd); + + uint64_t universe = vr::VRProperties()->GetUint64Property(hmdPropContainer, vr::Prop_CurrentUniverseId_Uint64); + if (!current_universe.has_value() || current_universe.value().first != universe) { + auto res = search_universes(universe); + if (res.has_value()) { + current_universe.emplace(universe, res.value()); + } else { + Log("Failed to find current universe!"); } + } - messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_position(hmdPosition); + vr::TrackedDevicePose_t hmd_pose[10]; + vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); - hmdPosition->set_tracker_id(0); - hmdPosition->set_data_source(messages::Position_DataSource_FULL); - hmdPosition->set_x(pos.v[0]); - hmdPosition->set_y(pos.v[1]); - hmdPosition->set_z(pos.v[2]); - hmdPosition->set_qx((float) q.x); - hmdPosition->set_qy((float) q.y); - hmdPosition->set_qz((float) q.z); - hmdPosition->set_qw((float) q.w); + vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); + vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); - sendBridgeMessage(*message, *this); - } else { - // If bridge not connected, assume we need to resend hmd tracker add message - sentHmdAddMessage = false; + if (current_universe.has_value()) { + auto trans = current_universe.value().second; + pos.v[0] += trans.translation.v[0]; + pos.v[1] += trans.translation.v[1]; + pos.v[2] += trans.translation.v[2]; + + // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0 + auto tmp_w = cos(-trans.yaw / 2); + auto tmp_y = sin(-trans.yaw / 2); + auto new_w = tmp_w * q.w - tmp_y * q.y; + auto new_x = tmp_w * q.x + tmp_y * q.z; + auto new_y = tmp_w * q.y + tmp_y * q.w; + auto new_z = tmp_w * q.z - tmp_y * q.x; + + q.w = new_w; + q.x = new_x; + q.y = new_y; + q.z = new_z; + + // rotate point on the xz plane by -trans.yaw radians + // this is equivilant to the quaternion multiplication, after applying the double angle formula. + float tmp_sin = sin(-trans.yaw); + float tmp_cos = cos(-trans.yaw); + auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin; + auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos; + + pos.v[0] = pos_x; + pos.v[2] = pos_z; + } + + messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_position(hmdPosition); + hmdPosition->set_tracker_id(0); + hmdPosition->set_data_source(messages::Position_DataSource_FULL); + hmdPosition->set_x(pos.v[0]); + hmdPosition->set_y(pos.v[1]); + hmdPosition->set_z(pos.v[2]); + hmdPosition->set_qx((float) q.x); + hmdPosition->set_qy((float) q.y); + hmdPosition->set_qz((float) q.z); + hmdPosition->set_qw((float) q.w); + + bridge->sendBridgeMessage(*message); +} +void SlimeVRDriver::VRDriver::OnBridgeMessage(messages::ProtobufMessage& message) { + // note: called from bridge thread; + // driver sample says that TrackedDevicePoseUpdated should happen from "some pose tracking thread", + // thus we assume the functions that "notify" (as described in docs) + // like TrackedDevicePoseUpdated and TrackedDeviceAdded are safe to call here + + std::lock_guard lock(devices_mutex_); + if (message.has_tracker_added()) { + messages::TrackerAdded ta = message.tracker_added(); + switch(getDeviceType(static_cast(ta.tracker_role()))) { + case DeviceType::TRACKER: + this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); + Log("New tracker device added " + ta.tracker_serial() + " (id " + std::to_string(ta.tracker_id()) + ")"); + break; + } + } else if (message.has_position()) { + messages::Position pos = message.position(); + auto device = this->devices_by_id.find(pos.tracker_id()); + if (device != this->devices_by_id.end()) { + device->second->PositionMessage(pos); + } + } else if (message.has_tracker_status()) { + messages::TrackerStatus status = message.tracker_status(); + auto device = this->devices_by_id.find(status.tracker_id()); + if (device != this->devices_by_id.end()) { + device->second->StatusMessage(status); + } } } -bool SlimeVRDriver::VRDriver::ShouldBlockStandbyMode() -{ +bool SlimeVRDriver::VRDriver::ShouldBlockStandbyMode() { return false; } -void SlimeVRDriver::VRDriver::EnterStandby() -{ -} +void SlimeVRDriver::VRDriver::EnterStandby() { } -void SlimeVRDriver::VRDriver::LeaveStandby() -{ -} +void SlimeVRDriver::VRDriver::LeaveStandby() { } -std::vector> SlimeVRDriver::VRDriver::GetDevices() -{ +std::vector> SlimeVRDriver::VRDriver::GetDevices() { + std::lock_guard lock(devices_mutex_); return this->devices_; } -std::vector SlimeVRDriver::VRDriver::GetOpenVREvents() -{ +std::vector SlimeVRDriver::VRDriver::GetOpenVREvents() { return this->openvr_events_; } -std::chrono::milliseconds SlimeVRDriver::VRDriver::GetLastFrameTime() -{ +std::chrono::milliseconds SlimeVRDriver::VRDriver::GetLastFrameTime() { return this->frame_timing_; } -bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) -{ +bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { vr::ETrackedDeviceClass openvr_device_class; // Remember to update this switch when new device types are added switch (device->GetDeviceType()) { @@ -223,13 +223,13 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) return false; } bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get()); - if(result) { + if (result) { this->devices_.push_back(device); this->devices_by_id[device->getDeviceId()] = device; this->devices_by_serial[device->GetSerial()] = device; } else { std::shared_ptr oldDevice = this->devices_by_serial[device->GetSerial()]; - if(oldDevice->getDeviceId() != device->getDeviceId()) { + if (oldDevice->getDeviceId() != device->getDeviceId()) { this->devices_by_id[device->getDeviceId()] = oldDevice; Log("Device overridden from id " + std::to_string(oldDevice->getDeviceId()) + " to " + std::to_string(device->getDeviceId()) + " for serial " + device->GetSerial()); } else { @@ -239,8 +239,7 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) return result; } -SlimeVRDriver::SettingsValue SlimeVRDriver::VRDriver::GetSettingsValue(std::string key) -{ +SlimeVRDriver::SettingsValue SlimeVRDriver::VRDriver::GetSettingsValue(std::string key) { vr::EVRSettingsError err = vr::EVRSettingsError::VRSettingsError_None; int int_value = vr::VRSettings()->GetInt32(settings_key_.c_str(), key.c_str(), &err); if (err == vr::EVRSettingsError::VRSettingsError_None) { @@ -267,24 +266,15 @@ SlimeVRDriver::SettingsValue SlimeVRDriver::VRDriver::GetSettingsValue(std::stri return SettingsValue(); } -void SlimeVRDriver::VRDriver::Log(std::string message) -{ - std::string message_endl = message + "\n"; - vr::VRDriverLog()->Log(message_endl.c_str()); -} - -vr::IVRDriverInput* SlimeVRDriver::VRDriver::GetInput() -{ +vr::IVRDriverInput* SlimeVRDriver::VRDriver::GetInput() { return vr::VRDriverInput(); } -vr::CVRPropertyHelpers* SlimeVRDriver::VRDriver::GetProperties() -{ +vr::CVRPropertyHelpers* SlimeVRDriver::VRDriver::GetProperties() { return vr::VRProperties(); } -vr::IVRServerDriverHost* SlimeVRDriver::VRDriver::GetDriverHost() -{ +vr::IVRServerDriverHost* SlimeVRDriver::VRDriver::GetDriverHost() { return vr::VRServerDriverHost(); } @@ -358,9 +348,7 @@ std::optional SlimeVRDriver::VRDriver::searc } } } catch (simdjson::simdjson_error& e) { - std::stringstream ss; - ss << "Error getting universes from \"" << path << "\": " << e.error(); - Log(ss.str()); + Log("Error getting universes from %s: %s", path, e.error()); return std::nullopt; } diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index b8b5a47..8603ef0 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -12,6 +12,9 @@ #include +#include "bridge/BridgeClient.hpp" +#include "Logger.hpp" + namespace SlimeVRDriver { class VRDriver : public IVRDriver { public: @@ -22,7 +25,15 @@ namespace SlimeVRDriver { virtual std::chrono::milliseconds GetLastFrameTime() override; virtual bool AddDevice(std::shared_ptr device) override; virtual SettingsValue GetSettingsValue(std::string key) override; - virtual void Log(std::string message) override; + virtual void Log(std::string message) override { + ::Log("%s", message.c_str()); + }; + void Log(const char* format, ...) { + va_list args; + va_start(args, format); + ::Log(format, args); + va_end(args); + }; virtual vr::IVRDriverInput* GetInput() override; virtual vr::CVRPropertyHelpers* GetProperties() override; @@ -37,9 +48,12 @@ namespace SlimeVRDriver { virtual void LeaveStandby() override; virtual ~VRDriver() = default; + void OnBridgeMessage(messages::ProtobufMessage& message); + virtual std::optional GetCurrentUniverse() override; private: + std::mutex devices_mutex_; std::vector> devices_; std::vector openvr_events_; std::map> devices_by_id; @@ -61,5 +75,7 @@ namespace SlimeVRDriver { std::optional search_universe(std::string path, uint64_t target); std::optional search_universes(uint64_t target); + + std::shared_ptr bridge; }; }; \ No newline at end of file diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp new file mode 100644 index 0000000..52dd456 --- /dev/null +++ b/src/bridge/BridgeClient.cpp @@ -0,0 +1,187 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#include "BridgeClient.hpp" +#include + +namespace fs = std::filesystem; +using namespace std::literals::chrono_literals; + +#define WINDOWS_PIPE_NAME "\\\\.\\pipe\\SlimeVRDriver" +#define UNIX_TMP_DIR "/tmp" +#define UNIX_SOCKET_NAME "SlimeVRDriver" + +std::string BridgeClient::makeBridgePath() { +#ifdef __linux__ + if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { + const fs::path xdg_runtime = ptr; + return (xdg_runtime / UNIX_SOCKET_NAME).string(); + } else { + return (fs::path(UNIX_TMP_DIR) / UNIX_SOCKET_NAME).string(); + } +#else + return WINDOWS_PIPE_NAME; +#endif +} + +void BridgeClient::start() { + if (running) return; + running = true; + thread = std::thread(&BridgeClient::runThread, this); +} + +void BridgeClient::stop() { + if (!running) return; + Log("Bridge: stopping"); + running = false; + stopHandle->send(); + thread.join(); +} + +void BridgeClient::runThread() { + Log("Bridge: thread started"); + stopHandle = uvw::Loop::getDefault()->resource(); + writeHandle = uvw::Loop::getDefault()->resource(); + + stopHandle->on([&](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + Log("Bridge: closing handles"); + pipeHandle->close(); + writeHandle->close(); + handle.close(); + }); + + writeHandle->on([&](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + sendWrites(); + }); + + connect(); + uvw::Loop::getDefault()->run(); + Log("Bridge: thread stopped"); +} + +void BridgeClient::connect() { + Log("Bridge: connecting"); + recvBuf.clear(); + sendBuf.clear(); + + /* ipc = false -> pipe will be used for handle passing between processes? no */ + pipeHandle = uvw::Loop::getDefault()->resource(false); + + pipeHandle->on([&](const uvw::ConnectEvent &, uvw::PipeHandle &) { + pipeHandle->read(); + Log("Bridge: connecting"); + connected = true; + }); + + pipeHandle->on([&](const uvw::EndEvent &, uvw::PipeHandle &) { + Log("Bridge: disconnected"); + disconnect(); + }); + + pipeHandle->on([&](const uvw::DataEvent &event, uvw::PipeHandle &) { + onRecv(event); + }); + + pipeHandle->on([&](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + Log("Bridge: Pipe error: %s", event.what()); + disconnect(); + }); + + pipeHandle->connect(path); +} + +void BridgeClient::disconnect() { + pipeHandle->close(); + connected = false; + std::this_thread::sleep_for(1000ms); + connect(); +} + +void BridgeClient::onRecv(const uvw::DataEvent &event) { + if (!recvBuf.push(event.data.get(), event.length)) { + Log("recvBuf.push %i failed", event.length); + disconnect(); + return; + } + + size_t available; + while (available = recvBuf.bytes_available()) { + if (available < 4) return; + + char lenBuf[4]; + recvBuf.peek(lenBuf, 4); + uint32_t size = LE32_TO_NATIVE(*reinterpret_cast(lenBuf)); + + if (size > VRBRIDGE_MAX_MESSAGE_SIZE) { + Log("Message size overflow"); + disconnect(); + return; + } + + auto unwrappedSize = size - 4; + if (available < unwrappedSize) return; + + auto messageBuf = std::make_unique(size); + if (!recvBuf.skip(4) || !recvBuf.pop(messageBuf.get(), unwrappedSize)) { + Log("recvBuf.pop %i failed", size); + disconnect(); + return; + } + + messages::ProtobufMessage receivedMessage; + if (receivedMessage.ParseFromArray(messageBuf.get(), unwrappedSize)) { + messageCallback(receivedMessage); + } else { + Log("receivedMessage.ParseFromArray failed"); + disconnect(); + return; + } + } +} + +void BridgeClient::sendBridgeMessage(messages::ProtobufMessage &message) { + if (!isConnected()) return; + + uint32_t size = static_cast(message.ByteSizeLong()); + uint32_t wrappedSize = size + 4; + + auto messageBuf = std::make_unique(wrappedSize); + *reinterpret_cast(messageBuf.get()) = NATIVE_TO_LE32(wrappedSize); + message.SerializeToArray(messageBuf.get() + 4, size); + if (!sendBuf.push(messageBuf.get(), wrappedSize)) { + disconnect(); + return; + } + + writeHandle->send(); +} + +void BridgeClient::sendWrites() { + if (!isConnected()) return; + + auto available = sendBuf.bytes_available(); + if (!available) return; + + auto writeBuf = std::make_unique(available); + sendBuf.pop(writeBuf.get(), available); + pipeHandle->write(writeBuf.get(), static_cast(available)); +} \ No newline at end of file diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp new file mode 100644 index 0000000..b84f885 --- /dev/null +++ b/src/bridge/BridgeClient.hpp @@ -0,0 +1,97 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Logger.hpp" +#include "CircularBuffer.hpp" +#include "ProtobufMessages.pb.h" + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define LE32_TO_NATIVE(x) (x) +#else + #define LE32_TO_NATIVE(x) ( \ + ((uint32_t)(x) << 24) | \ + (((uint32_t)(x) << 8) & 0x00FF0000) | \ + (((uint32_t)(x) >> 8) & 0x0000FF00) | \ + ((uint32_t)(x) >> 24) \ + ) +#endif +#define NATIVE_TO_LE32 LE32_TO_NATIVE + +#define VRBRIDGE_MAX_MESSAGE_SIZE 1024 + +/** + * @brief Cross-platform bridge client. + * + * Starts a thread with an event loop, which handles IPC. + */ +class BridgeClient { +public: + /** + * @param onMessageReceived Gets called from the event loop thread when a message is received and parsed. + */ + BridgeClient(std::function onMessageReceived) : + messageCallback(onMessageReceived), + path(makeBridgePath()), + sendBuf(8192), + recvBuf(8192) + { } + + ~BridgeClient() { + stop(); + } + + void start(); + void stop(); + void sendBridgeMessage(messages::ProtobufMessage &message); + bool isConnected() { + return connected; + }; + +private: + static std::string makeBridgePath(); + void runThread(); + void connect(); + void onRecv(const uvw::DataEvent &event); + void disconnect(); + void sendWrites(); + + CircularBuffer sendBuf; + CircularBuffer recvBuf; + std::string path; + std::shared_ptr pipeHandle; + std::shared_ptr stopHandle; + std::shared_ptr writeHandle; + std::thread thread; + std::function messageCallback; + std::queue messageQueue; + std::atomic running = false; + std::atomic connected = false; +}; \ No newline at end of file diff --git a/src/bridge/CircularBuffer.hpp b/src/bridge/CircularBuffer.hpp new file mode 100644 index 0000000..63dfbe7 --- /dev/null +++ b/src/bridge/CircularBuffer.hpp @@ -0,0 +1,97 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#pragma once + +#include +#include +#include +#include + +class CircularBuffer { +public: + CircularBuffer(size_t size) : + size_(size), + buffer_(std::make_unique(size)), + head_(0), tail_(0), count_(0) + { } + ~CircularBuffer() = default; + + bool push(const char* data, size_t size) { + if (size > bytes_free()) return false; + size_t size1 = std::min(size, size_ - (head_ % size_)); + size_t size2 = size - size1; + std::memcpy(buffer_.get() + (head_ % size_), data, size1); + std::memcpy(buffer_.get(), data + size1, size2); + head_ += size; + count_ += size; + return true; + } + + bool pop(char* data, size_t size) { + if (size > bytes_available()) return false; + size_t size1 = std::min(size, size_ - (tail_ % size_)); + size_t size2 = size - size1; + std::memcpy(data, buffer_.get() + (tail_ % size_ ), size1); + std::memcpy(data + size1, buffer_.get(), size2); + tail_ += size; + count_ -= size; + return true; + } + + size_t peek(char* data, size_t size) { + size_t available = bytes_available(); + size_t size1 = std::min(size, size_ - (tail_ % size_)); + size_t size2 = std::min(size - size1, available - size1); + std::memcpy(data, buffer_.get() + (tail_ % size_), size1); + std::memcpy(data + size1, buffer_.get(), size2); + return size1 + size2; + } + + bool skip(size_t n) { + if (n > bytes_available()) return false; + tail_ += n; + count_ -= n; + return true; + } + + void clear() { + head_ = 0; + tail_ = 0; + count_ = 0; + } + + size_t bytes_available() const { + return count_; + } + + size_t bytes_free() const { + return size_ - bytes_available(); + } + +private: + const size_t size_; + std::unique_ptr buffer_; + std::atomic head_; + std::atomic tail_; + std::atomic count_; +}; \ No newline at end of file diff --git a/src/bridge/bridge-unix-sockets.cpp b/src/bridge/bridge-unix-sockets.cpp deleted file mode 100644 index e53e721..0000000 --- a/src/bridge/bridge-unix-sockets.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - SlimeVR Code is placed under the MIT license - Copyright (c) 2021 Eiren Rain - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ -/** - * Linux specific IPC between SteamVR driver/app and SlimeVR server based - * on unix sockets - */ -#include "bridge.hpp" -#ifdef __linux__ -#include "unix-sockets.hpp" -#include -#include -#include -#include - -#define TMP_DIR "/tmp" -#define SOCKET_NAME "SlimeVRDriver" - -namespace fs = std::filesystem; -namespace { - -inline constexpr int HEADER_SIZE = 4; -/// @return iterator after header -template -std::optional WriteHeader(TBufIt bufBegin, int bufSize, int msgSize) { - const int totalSize = msgSize + HEADER_SIZE; // include header bytes in total size - if (bufSize < totalSize) return std::nullopt; // header won't fit - - const auto size = static_cast(totalSize); - TBufIt it = bufBegin; - *(it++) = static_cast(size); - *(it++) = static_cast(size >> 8U); - *(it++) = static_cast(size >> 16U); - *(it++) = static_cast(size >> 24U); - return it; -} - -/// @return iterator after header -template -std::optional ReadHeader(TBufIt bufBegin, int numBytesRecv, int& outMsgSize) { - if (numBytesRecv < HEADER_SIZE) return std::nullopt; // header won't fit - - uint32_t size = 0; - TBufIt it = bufBegin; - size = static_cast(*(it++)); - size |= static_cast(*(it++)) << 8U; - size |= static_cast(*(it++)) << 16U; - size |= static_cast(*(it++)) << 24U; - - const auto totalSize = static_cast(size); - if (totalSize < HEADER_SIZE) return std::nullopt; - outMsgSize = totalSize - HEADER_SIZE; - return it; -} - -BasicLocalClient client{}; - -inline constexpr int BUFFER_SIZE = 1024; -using ByteBuffer = std::array; -ByteBuffer byteBuffer; - -} - -bool getNextBridgeMessage(messages::ProtobufMessage& message, SlimeVRDriver::VRDriver& driver) { - if (!client.IsOpen()) return false; - - int bytesRecv = 0; - try { - bytesRecv = client.RecvOnce(byteBuffer.begin(), HEADER_SIZE); - } catch (const std::exception& e) { - client.Close(); - driver.Log("bridge send error: " + std::string(e.what())); - return false; - } - if (bytesRecv == 0) return false; // no message waiting - - int msgSize = 0; - const std::optional msgBeginIt = ReadHeader(byteBuffer.begin(), bytesRecv, msgSize); - if (!msgBeginIt) { - driver.Log("bridge recv error: invalid message header or size"); - return false; - } - if (msgSize <= 0) { - driver.Log("bridge recv error: empty message"); - return false; - } - try { - if (!client.RecvAll(*msgBeginIt, msgSize)) { - driver.Log("bridge recv error: client closed"); - return false; - } - } catch (const std::exception& e) { - client.Close(); - driver.Log("bridge send error: " + std::string(e.what())); - return false; - } - if (!message.ParseFromArray(&(**msgBeginIt), msgSize)) { - driver.Log("bridge recv error: failed to parse"); - return false; - } - - return true; -} - -bool sendBridgeMessage(messages::ProtobufMessage& message, SlimeVRDriver::VRDriver& driver) { - if (!client.IsOpen()) return false; - const auto bufBegin = byteBuffer.begin(); - const auto bufferSize = static_cast(std::distance(bufBegin, byteBuffer.end())); - const auto msgSize = static_cast(message.ByteSizeLong()); - const std::optional msgBeginIt = WriteHeader(bufBegin, bufferSize, msgSize); - if (!msgBeginIt) { - driver.Log("bridge send error: failed to write header"); - return false; - } - if (!message.SerializeToArray(&(**msgBeginIt), msgSize)) { - driver.Log("bridge send error: failed to serialize"); - return false; - } - int bytesToSend = static_cast(std::distance(bufBegin, *msgBeginIt + msgSize)); - if (bytesToSend <= 0) { - driver.Log("bridge send error: empty message"); - return false; - } - if (bytesToSend > bufferSize) { - driver.Log("bridge send error: message too big"); - return false; - } - try { - return client.Send(bufBegin, bytesToSend); - } catch (const std::exception& e) { - client.Close(); - driver.Log("bridge send error: " + std::string(e.what())); - return false; - } -} - -BridgeStatus runBridgeFrame(SlimeVRDriver::VRDriver& driver) { - try { - if (!client.IsOpen()) { - // TODO: do this once in the constructor or something - if(const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { - const fs::path xdg_runtime = ptr; - client.Open((xdg_runtime / SOCKET_NAME).native()); - } else { - client.Open((fs::path(TMP_DIR) / SOCKET_NAME).native()); - } - } - client.UpdateOnce(); - - if (!client.IsOpen()) { - return BRIDGE_DISCONNECTED; - } - return BRIDGE_CONNECTED; - } catch (const std::exception& e) { - client.Close(); - driver.Log("bridge error: " + std::string(e.what())); - return BRIDGE_ERROR; - } -} - -#endif // linux diff --git a/src/bridge/bridge-windows-pipes.cpp b/src/bridge/bridge-windows-pipes.cpp deleted file mode 100644 index ef73846..0000000 --- a/src/bridge/bridge-windows-pipes.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - SlimeVR Code is placed under the MIT license - Copyright (c) 2021 Eiren Rain - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ -/** - * Windows specific IPC between SteamVR driver/app and SlimeVR server based - * on named pipes - */ -#include "bridge.hpp" -#if defined(WIN32) && defined(BRIDGE_USE_PIPES) -#include - -#define PIPE_NAME "\\\\.\\pipe\\SlimeVRDriver" - -unsigned long lastReconnectFrame = 0; - -HANDLE pipe = INVALID_HANDLE_VALUE; -BridgeStatus currentBridgeStatus = BRIDGE_DISCONNECTED; -char buffer[1024]; - -void updatePipe(SlimeVRDriver::VRDriver &driver); -void resetPipe(SlimeVRDriver::VRDriver &driver); -void attemptPipeConnect(SlimeVRDriver::VRDriver &driver); - -BridgeStatus runBridgeFrame(SlimeVRDriver::VRDriver &driver) { - switch(currentBridgeStatus) { - case BRIDGE_DISCONNECTED: - attemptPipeConnect(driver); - break; - case BRIDGE_ERROR: - resetPipe(driver); - break; - case BRIDGE_CONNECTED: - updatePipe(driver); - break; - } - - return currentBridgeStatus; -} - -bool getNextBridgeMessage(messages::ProtobufMessage &message, SlimeVRDriver::VRDriver &driver) { - DWORD dwRead; - DWORD dwAvailable; - if(currentBridgeStatus == BRIDGE_CONNECTED) { - if(PeekNamedPipe(pipe, buffer, 4, &dwRead, &dwAvailable, NULL)) { - if(dwRead == 4) { - uint32_t messageLength = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; - if(messageLength > 1024) { - // TODO Buffer overflow - } - if(dwAvailable >= messageLength) { - if(ReadFile(pipe, buffer, messageLength, &dwRead, NULL)) { - if(message.ParseFromArray(buffer + 4, messageLength - 4)) - return true; - } else { - currentBridgeStatus = BRIDGE_ERROR; - driver.Log("Bridge error: " + std::to_string(GetLastError())); - } - } - } - } else { - currentBridgeStatus = BRIDGE_ERROR; - driver.Log("Bridge error: " + std::to_string(GetLastError())); - } - } - return false; -} - -bool sendBridgeMessage(messages::ProtobufMessage &message, SlimeVRDriver::VRDriver &driver) { - if(currentBridgeStatus == BRIDGE_CONNECTED) { - uint32_t size = (uint32_t) message.ByteSizeLong(); - if(size > 1020) { - driver.Log("Message too big"); - return false; - } - message.SerializeToArray(buffer + 4, size); - size += 4; - buffer[0] = size & 0xFF; - buffer[1] = (size >> 8) & 0xFF; - buffer[2] = (size >> 16) & 0xFF; - buffer[3] = (size >> 24) & 0xFF; - if(WriteFile(pipe, buffer, size, NULL, NULL)) { - return true; - } - currentBridgeStatus = BRIDGE_ERROR; - driver.Log("Bridge error: " + std::to_string(GetLastError())); - } - return false; -} - -void updatePipe(SlimeVRDriver::VRDriver &driver) { -} - -void resetPipe(SlimeVRDriver::VRDriver &driver) { - if(pipe != INVALID_HANDLE_VALUE) { - CloseHandle(pipe); - pipe = INVALID_HANDLE_VALUE; - currentBridgeStatus = BRIDGE_DISCONNECTED; - driver.Log("Pipe was reset"); - } -} - -void attemptPipeConnect(SlimeVRDriver::VRDriver &driver) { - pipe = CreateFileA(PIPE_NAME, - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - 0, // TODO : Overlapped - NULL); - if(pipe != INVALID_HANDLE_VALUE) { - currentBridgeStatus = BRIDGE_CONNECTED; - driver.Log("Pipe was connected"); - return; - } -} - - -#endif // PLATFORM_WINDOWS && BRIDGE_USE_PIPES \ No newline at end of file diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp deleted file mode 100644 index fb1e1bf..0000000 --- a/src/bridge/bridge.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - SlimeVR Code is placed under the MIT license - Copyright (c) 2021 Eiren Rain - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ -/** - * Header file for cross-platform handling of IPC between SteamVR driver/app - * and SlimeVR server - */ -#pragma once - -#define BRIDGE_USE_PIPES 1 -#include "ProtobufMessages.pb.h" -#include -#include -#include "../VRDriver.hpp" - -enum BridgeStatus { - BRIDGE_DISCONNECTED = 0, - BRIDGE_CONNECTED = 1, - BRIDGE_ERROR = 2 -}; - -BridgeStatus runBridgeFrame(SlimeVRDriver::VRDriver &driver); - -bool getNextBridgeMessage(messages::ProtobufMessage &message, SlimeVRDriver::VRDriver &driver); - -bool sendBridgeMessage(messages::ProtobufMessage &message, SlimeVRDriver::VRDriver &driver); \ No newline at end of file diff --git a/src/bridge/unix-sockets.hpp b/src/bridge/unix-sockets.hpp deleted file mode 100644 index 3dd6249..0000000 --- a/src/bridge/unix-sockets.hpp +++ /dev/null @@ -1,446 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -/// AF_UNIX / local socket specific address -using sockaddr_un_t = struct sockaddr_un; -/// generic address, usually pointer argument -using sockaddr_t = struct sockaddr; -/// used as list for poll() -using pollfd_t = struct pollfd; -/// file descriptor -using Descriptor = int; - -/// std::errc or int -/// Unwrap will either return the int, or throw the errc as a system_error -class SysReturn { - static constexpr std::errc sNotAnError = std::errc(); -public: - constexpr explicit SysReturn(int value) noexcept : mCode(sNotAnError), mValue(value) {} - constexpr explicit SysReturn(std::errc code) noexcept : mCode(code), mValue() {} - constexpr bool IsError() const { return mCode != sNotAnError; } - [[noreturn]] void ThrowCode() const { throw std::system_error(std::make_error_code(mCode)); } - constexpr int Unwrap() const { if (IsError()) ThrowCode(); return mValue; } - constexpr std::errc GetCode() const { return mCode; } -private: - std::errc mCode; - int mValue; -}; - -/// call a system function and wrap the errno or int result in a SysReturn -template -[[nodiscard]] inline SysReturn SysCall(Fn&& func, Args&&... args) noexcept { - const int result = static_cast(func(std::forward(args)...)); - if (result != -1) return SysReturn(result); - return SysReturn(std::errc(errno)); -} - -/// wrap a blocking syscall and return nullopt if it would block -template -[[nodiscard]] inline std::optional SysCallBlocking(Fn&& func, Args&&... args) noexcept { - const int result = static_cast(func(std::forward(args)...)); - if (result != -1) return std::optional(result); - const auto code = static_cast(errno); - if (code == std::errc::operation_would_block || code == std::errc::resource_unavailable_try_again) { - return std::nullopt; - } - return std::optional(code); -} - -namespace event { - -enum class SockMode { - Acceptor, - Connector -}; - -/// bitmask for which events to return -using Mask = short; -inline constexpr Mask Readable = POLLIN; /// enable Readable events -inline constexpr Mask Priority = POLLPRI; /// enable Priority events -inline constexpr Mask Writable = POLLOUT; /// enable Writable events - -class Result { -public: - explicit Result(short events) : v(events) {} - bool IsReadable() const { return (v & POLLIN) != 0; } /// without blocking, connector can call read or acceptor can call accept - bool IsPriority() const { return (v & POLLPRI) != 0; } /// some exceptional condition, for tcp this is OOB data - bool IsWritable() const { return (v & POLLOUT) != 0; } /// can call write without blocking - bool IsErrored() const { return (v & POLLERR) != 0; } /// error to be checked with Socket::GetError(), or write pipe's target read pipe was closed - bool IsClosed() const { return (v & POLLHUP) != 0; } /// socket closed, however for connector, subsequent reads must be called until returns 0 - bool IsInvalid() const { return (v & POLLNVAL) != 0; } /// not an open descriptor and shouldn't be polled -private: - short v; -}; -/// poll an acceptor and its connections -class Poller { - static constexpr Mask mConnectorMask = Readable | Writable; - static constexpr Mask mAcceptorMask = Readable; -public: - void Poll(int timeoutMs) { - SysCall(::poll, mPollList.data(), mPollList.size(), timeoutMs).Unwrap(); - } - /// @tparam TPred (Descriptor, event::Result, event::SockMode) -> void - template - void Poll(int timeoutMs, TPred&& pred) { - Poll(timeoutMs); - for (const pollfd_t& elem : mPollList) { - SockMode mode = (elem.events == mAcceptorMask) ? SockMode::Acceptor : SockMode::Connector; - pred(elem.fd, Result(elem.revents), mode); - } - } - void AddConnector(Descriptor descriptor) { - mPollList.push_back({descriptor, mConnectorMask, 0}); - } - void AddAcceptor(Descriptor descriptor) { - mPollList.push_back({descriptor, mAcceptorMask, 0}); - } - Result At(int idx) const { - return Result(mPollList.at(idx).revents); - } - bool Remove(Descriptor descriptor) { - auto it = std::find_if(mPollList.begin(), mPollList.end(), - [&](const pollfd_t& elem){ return elem.fd == descriptor; }); - if (it == mPollList.end()) return false; - mPollList.erase(it); - return true; - } - void Clear() { mPollList.clear(); } - int GetSize() const { return static_cast(mPollList.size()); } -private: - std::vector mPollList{}; -}; - -} - -/// owned socket file descriptor -class Socket { - static constexpr Descriptor sInvalidSocket = -1; -public: - /// open a new socket - Socket(int domain, int type, int protocol) : mDescriptor(SysCall(::socket, domain, type, protocol).Unwrap()) { - SetNonBlocking(); - } - /// using file descriptor returned from system call - explicit Socket(Descriptor descriptor) : mDescriptor(descriptor) { - if (descriptor == sInvalidSocket) throw std::invalid_argument("invalid socket descriptor"); - SetNonBlocking(); // accepted from non-blocking socket still needs to be set - } - ~Socket() { - // owns resource and must close it, descriptor will be set invalid if moved from - // discard any errors thrown by close, most mean it never owned it or didn't exist - if (mDescriptor != sInvalidSocket) (void)SysCall(::close, mDescriptor); - } - // manage descriptor like never null unique_ptr - Socket(Socket&& other) noexcept : - mDescriptor(other.mDescriptor), - mIsReadable(other.mIsReadable), - mIsWritable(other.mIsWritable), - mIsNonBlocking(other.mIsNonBlocking) { - other.mDescriptor = sInvalidSocket; - } - Socket& operator=(Socket&& rhs) noexcept { - std::swap(mDescriptor, rhs.mDescriptor); - mIsReadable = rhs.mIsReadable; - mIsWritable = rhs.mIsWritable; - mIsNonBlocking = rhs.mIsNonBlocking; - return *this; - } - Socket(const Socket&) = delete; - Socket& operator=(const Socket&) = delete; - /// get underlying file descriptor - Descriptor GetDescriptor() const { return mDescriptor; } - /// get an error on the socket, indicated by errored poll event - std::errc GetError() const { - return static_cast(GetSockOpt(SOL_SOCKET, SO_ERROR).first); - } - void SetBlocking() { mIsNonBlocking = false; SetStatusFlags(GetStatusFlags() & ~(O_NONBLOCK)); } - void SetNonBlocking() { mIsNonBlocking = true; SetStatusFlags(GetStatusFlags() | O_NONBLOCK); } - // only applies to non blocking, and set from Update (poll), always return true if blocking - bool GetAndResetIsReadable() { const bool temp = mIsReadable; mIsReadable = false; return temp || !mIsNonBlocking; } - bool GetAndResetIsWritable() { const bool temp = mIsWritable; mIsWritable = false; return temp || !mIsNonBlocking; } - /// @return false if socket should close - bool Update(event::Result res) { - if (res.IsErrored()) { - throw std::system_error(std::make_error_code(GetError())); - } - if (res.IsInvalid() || res.IsClosed()) { - // TODO: technically could still have bytes waiting to be read on the close event - return false; - } - if (res.IsReadable()) { - mIsReadable = true; - } - if (res.IsWritable()) { - mIsWritable = true; - } - return true; - } - -private: - int GetStatusFlags() const { return SysCall(::fcntl, mDescriptor, F_GETFL, 0).Unwrap(); } - void SetStatusFlags(int flags) { SysCall(::fcntl, mDescriptor, F_SETFL, flags).Unwrap(); } - - /// get or set socket option, most are ints, non default length is only for strings - template - std::pair GetSockOpt(int level, int optname, T inputValue = T(), socklen_t inputSize = sizeof(T)) const { - T outValue = inputValue; - socklen_t outSize = inputSize; - SysCall(::getsockopt, mDescriptor, level, optname, &outValue, &outSize).Unwrap(); - return std::make_pair(outValue, outSize); - } - template - void SetSockOpt(int level, int optname, const T& inputValue, socklen_t inputSize = sizeof(T)) { - SysCall(::setsockopt, level, optname, &inputValue, inputSize).Unwrap(); - } - - Descriptor mDescriptor; - bool mIsReadable = false; - bool mIsWritable = false; - bool mIsNonBlocking = false; -}; - -/// address for unix sockets -class LocalAddress { - static constexpr sa_family_t sFamily = AF_UNIX; // always AF_UNIX - /// max returned by Size() - static constexpr socklen_t sMaxSize = sizeof(sockaddr_un_t); - /// offset of sun_path within the address object, sun_path is a char array - static constexpr socklen_t sPathOffset = sMaxSize - sizeof(sockaddr_un_t::sun_path); -public: - /// empty address - LocalAddress() noexcept : mSize(sMaxSize) {} - /// almost always bind before use - explicit LocalAddress(std::string_view path) - : mSize(sPathOffset + path.size() + 1) { // beginning of object + length of path + null terminator - if (path.empty()) throw std::invalid_argument("path empty"); - if (mSize > sMaxSize) throw std::length_error("path too long"); - // copy and null terminate path - std::strncpy(&mAddress.sun_path[0], path.data(), path.size()); - mAddress.sun_path[path.size()] = '\0'; - mAddress.sun_family = sFamily; - } - - /// deletes the path on the filesystem, usually called before bind - void Unlink() const { - // TODO: keep important errors (permissions, logic, ...) - (void)SysCall(::unlink, GetPath()); - } - - /// system calls with address - const sockaddr_t* GetPtr() const { return reinterpret_cast(&mAddress); } - /// system calls with address out - sockaddr_t* GetPtr() { return reinterpret_cast(&mAddress); } - /// system calls with addrLen - socklen_t GetSize() const { return mSize; } - /// system calls with addrLen out - socklen_t* GetSizePtr() { - mSize = sMaxSize; - return &mSize; - } - const char* GetPath() const { return &mAddress.sun_path[0]; } - bool IsValid() const { - return mAddress.sun_family == sFamily; // not used with wrong socket type - } -private: - socklen_t mSize; - sockaddr_un_t mAddress{}; -}; - -class LocalSocket : public Socket { - static constexpr int sDomain = AF_UNIX, // unix domain socket - sType = SOCK_STREAM, // connection oriented, no message boundaries - sProtocol = 0; // auto selected -public: - explicit LocalSocket(std::string_view path) : Socket(sDomain, sType, sProtocol), mAddress(path) {} - LocalSocket(Descriptor descriptor, LocalAddress address) : Socket(descriptor), mAddress(address) { - if (!mAddress.IsValid()) throw std::invalid_argument("invalid local socket address"); - } - -protected: - void UnlinkAddress() const { mAddress.Unlink(); } - void Bind() const { SysCall(::bind, GetDescriptor(), mAddress.GetPtr(), mAddress.GetSize()).Unwrap(); } - void Listen(int backlog) const { SysCall(::listen, GetDescriptor(), backlog).Unwrap(); } - void Connect() const { SysCall(::connect, GetDescriptor(), mAddress.GetPtr(), mAddress.GetSize()).Unwrap(); } - -private: - LocalAddress mAddress; -}; - -/// connector manages a connection, can send/recv with -class LocalConnectorSocket : public LocalSocket { -public: - /// open as outbound connector to path - explicit LocalConnectorSocket(std::string_view path) : LocalSocket(path) { - Connect(); - } - /// open as inbound connector from accept - LocalConnectorSocket(Descriptor descriptor, LocalAddress address) : LocalSocket(descriptor, address) {} - /// send a byte buffer - /// @tparam TBufIt iterator to contiguous memory - /// @return number of bytes sent or nullopt if blocking - template - std::optional TrySend(TBufIt bufBegin, int bytesToSend) { - if (!GetAndResetIsWritable()) return std::nullopt; - constexpr int flags = 0; - if (auto bytesSent = SysCallBlocking(::send, GetDescriptor(), &(*bufBegin), bytesToSend, flags)) { - return (*bytesSent).Unwrap(); - } - return std::nullopt; - } - /// receive a byte buffer - /// @tparam TBufIt iterator to contiguous memory - /// @return number of bytes written to buffer or nullopt if blocking - template - std::optional TryRecv(TBufIt bufBegin, int bufSize) { - if (!GetAndResetIsReadable()) return std::nullopt; - constexpr int flags = 0; - if (auto bytesRecv = SysCallBlocking(::recv, GetDescriptor(), &(*bufBegin), bufSize, flags)) { - return (*bytesRecv).Unwrap(); - } - return std::nullopt; - } -}; - -/// aka listener/passive socket, accepts connectors -class LocalAcceptorSocket : public LocalSocket { -public: - /// open as acceptor on path, backlog is accept queue size - LocalAcceptorSocket(std::string_view path, int backlog) : LocalSocket(path) { - UnlinkAddress(); - Bind(); - Listen(backlog); - } - /// accept an inbound connector or nullopt if blocking - std::optional Accept() { - if (!GetAndResetIsReadable()) return std::nullopt; - LocalAddress address; - if (auto desc = SysCallBlocking(::accept, GetDescriptor(), address.GetPtr(), address.GetSizePtr())) { - return LocalConnectorSocket((*desc).Unwrap(), address); - } - return std::nullopt; - } -}; - -/// manage a single outbound connector -class BasicLocalClient { -public: - void Open(std::string_view path) { - if (IsOpen()) throw std::runtime_error("connection already open"); - mConnector = LocalConnectorSocket(path); - mPoller.AddConnector(mConnector->GetDescriptor()); - assert(mPoller.GetSize() == 1); - } - void Close() { - mConnector.reset(); - mPoller.Clear(); - } - - /// default timeout returns immediately - void UpdateOnce(int timeoutMs = 0) { - if (!IsOpen()) throw std::runtime_error("connection not open"); - mPoller.Poll(timeoutMs); - - if (!mConnector->Update(mPoller.At(0))) { - Close(); - } - } - - /// send a byte buffer, continuously updates until entire message is sent - /// @tparam TBufIt iterator to contiguous memory - /// @return false if the send fails (connection closed) - template - bool Send(TBufIt bufBegin, int bufSize) { - TBufIt msgIt = bufBegin; - int bytesToSend = bufSize; - int maxIter = 100; - while (--maxIter && bytesToSend > 0) { - if (!IsOpen()) return false; - std::optional bytesSent = mConnector->TrySend(msgIt, bytesToSend); - if (!bytesSent) { - // blocking, poll and try again - UpdateOnce(); - } else if (*bytesSent <= 0) { - // returning 0 is very unlikely given the small amount of data, something about filling up the internal buffer? - // handle it the same as a would block error, and hope eventually it'll resolve itself - UpdateOnce(20); // 20ms timeout to give the buffer time to be emptied - } else if (*bytesSent > bytesToSend) { - // probably guaranteed to not happen, but just in case - throw std::runtime_error("bytes sent > bytes to send"); - } else { - // SOCK_STREAM allows partial sends, but almost guaranteed to not happen on local sockets - bytesToSend -= *bytesSent; - msgIt += *bytesSent; - } - } - if (maxIter == 0) { - throw std::runtime_error("send stuck in infinite loop"); - } - return true; - } - - /// receive into byte buffer - /// @tparam TBufIt iterator to contiguous memory - /// @return number of bytes written to buffer, 0 indicating there is no message waiting - template - int RecvOnce(TBufIt bufBegin, int bytesToRead) { - std::optional bytesRecv = mConnector->TryRecv(bufBegin, bytesToRead); - // if the user is doing while(messageReceived) { } to empty the message queue - // then need to poll once before the next iteration, but only if there were bytes received - if (bytesRecv && *bytesRecv > 0) UpdateOnce(); - return bytesRecv.value_or(0); - } - - /// receive into byte buffer, continously updates until all bytes are read - /// @tparam TBufIt iterator to contiguous memory - /// @return true if bytesToRead bytes were written to buffer - template - bool RecvAll(const TBufIt bufBegin, int bytesToRead) { - int maxIter = 100; - auto bufIt = bufBegin; - while (--maxIter && bytesToRead > 0) { - if (!IsOpen()) return false; - std::optional bytesRecv = mConnector->TryRecv(bufIt, bytesToRead); - if (!bytesRecv || *bytesRecv == 0) { - // try again - } else if (*bytesRecv < 0 || *bytesRecv > bytesToRead) { - // should not be possible - throw std::length_error("bytesRecv"); - } else { - // read some or all of the message - bytesToRead -= *bytesRecv; - bufIt += *bytesRecv; - } - // set readable for next bytes, or a future call to Recv - UpdateOnce(); - } - if (maxIter == 0) { - throw std::runtime_error("recv stuck in infinite loop"); - } - return true; - } - - bool IsOpen() const { return mConnector.has_value(); } - -private: - std::optional mConnector{}; - event::Poller mPoller{}; // index 0 is connector if open -}; diff --git a/vcpkg.json b/vcpkg.json index 8272723..788010f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,8 @@ "version": "0.2.0", "dependencies": [ "protobuf", - "simdjson" + "simdjson", + "libuv", + "uvw" ] } \ No newline at end of file From a37f410592d5b83ae8a5026b0a8062ce6ce5255b Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Mon, 27 Mar 2023 00:42:16 +0300 Subject: [PATCH 02/24] Refactor and tests --- CMakeLists.txt | 54 +++++-- src/Logger.cpp | 33 ++-- src/Logger.hpp | 31 +++- src/VRDriver.cpp | 23 +-- src/VRDriver.hpp | 13 +- src/bridge/BridgeClient.cpp | 175 ++++------------------ src/bridge/BridgeClient.hpp | 82 +++------- src/bridge/BridgeTransport.cpp | 138 +++++++++++++++++ src/bridge/BridgeTransport.hpp | 161 ++++++++++++++++++++ src/bridge/CircularBuffer.hpp | 5 +- test/BridgeServerMock.cpp | 72 +++++++++ test/BridgeServerMock.hpp | 40 +++++ test/TestBridgeClientMock.cpp | 93 ++++++++++++ test/TestCircularBuffer.cpp | 34 +++++ test/common/TestBridgeClient.cpp | 134 +++++++++++++++++ test/common/TestBridgeClient.hpp | 11 ++ test/integration/TestBridgeClientReal.cpp | 9 ++ 17 files changed, 859 insertions(+), 249 deletions(-) create mode 100644 src/bridge/BridgeTransport.cpp create mode 100644 src/bridge/BridgeTransport.hpp create mode 100644 test/BridgeServerMock.cpp create mode 100644 test/BridgeServerMock.hpp create mode 100644 test/TestBridgeClientMock.cpp create mode 100644 test/TestCircularBuffer.cpp create mode 100644 test/common/TestBridgeClient.cpp create mode 100644 test/common/TestBridgeClient.hpp create mode 100644 test/integration/TestBridgeClientReal.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd4df7d..ccd2220 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,11 @@ elseif(APPLE) set(PLATFORM_NAME "osx") endif() +if(UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") +endif() + find_library(OPENVR_LIB openvr_api HINTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/openvr/lib/${PLATFORM_NAME}${PROCESSOR_ARCH}/" NO_DEFAULT_PATH ) # Protobuf @@ -73,18 +78,49 @@ find_package(simdjson CONFIG REQUIRED) find_package(libuv REQUIRED) find_package(uvw REQUIRED) +# Catch2 +find_package(Catch2 3 REQUIRED) + # Project -file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") +set(DEPS_INCLUDES + "${OPENVR_INCLUDE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/libraries/linalg" + "${CMAKE_CURRENT_SOURCE_DIR}/src/" +) +set(DEPS_LIBS + "${OPENVR_LIB}" + protobuf::libprotoc + protobuf::libprotobuf + protobuf::libprotobuf-lite + simdjson::simdjson + $,uv_a,uv> # libuv +) + +# compile into a static lib file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") -add_library("${PROJECT_NAME}" SHARED "${HEADERS}" "${SOURCES}" ${PROTO_HEADER} ${PROTO_SRC}) -target_include_directories("${PROJECT_NAME}" PUBLIC "${OPENVR_INCLUDE_DIR}") -target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libraries/linalg") -target_include_directories("${PROJECT_NAME}" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src/") -target_link_libraries("${PROJECT_NAME}" PUBLIC "${OPENVR_LIB}" protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite simdjson::simdjson) -target_link_libraries("${PROJECT_NAME}" PUBLIC $,uv_a,uv>) # libuv +file(GLOB_RECURSE HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") +add_library("${PROJECT_NAME}_static" STATIC ${SOURCES} ${PROTO_HEADER} ${PROTO_SRC}) +target_link_libraries("${PROJECT_NAME}_static" PUBLIC ${DEPS_LIBS}) +set_property(TARGET "${PROJECT_NAME}_static" PROPERTY CXX_STANDARD 17) +include_directories("${PROJECT_NAME}_static" PUBLIC ${DEPS_INCLUDES} ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) + +# compile driver +file(GLOB_RECURSE DRIVER_MAIN "${CMAKE_CURRENT_SOURCE_DIR}/src/DriverFactory.cpp") +add_library("${PROJECT_NAME}" SHARED ${DRIVER_MAIN} ${HEADERS} ${PROTO_HEADER}) +target_link_libraries("${PROJECT_NAME}" PRIVATE "${PROJECT_NAME}_static") set_property(TARGET "${PROJECT_NAME}" PROPERTY CXX_STANDARD 17) -include_directories(${Protobuf_INCLUDE_DIRS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# compile tests +function(build_tests target_name test_dir) + file(GLOB TESTS "${CMAKE_CURRENT_SOURCE_DIR}/${test_dir}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/${test_dir}/*.hpp") + file(GLOB TESTS_COMMON "${CMAKE_CURRENT_SOURCE_DIR}/test/common/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test/common/*.hpp") + add_executable(${target_name} ${TESTS} ${TESTS_COMMON} ${HEADERS} ${PROTO_HEADER}) + target_link_libraries(${target_name} PUBLIC "${PROJECT_NAME}_static" Catch2::Catch2WithMain) + set_property(TARGET ${target_name} PROPERTY CXX_STANDARD 17) +endfunction() +build_tests(tests "test") +build_tests(tests_integration "test/integration") +add_test(NAME tests COMMAND "tests") # IDE Config source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" PREFIX "Header Files" FILES ${HEADERS}) diff --git a/src/Logger.cpp b/src/Logger.cpp index 5cd6e3e..f665af3 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -1,18 +1,33 @@ #include "Logger.hpp" +#include + +void Logger::Log(const char* format, ...) { + auto prefixedFormat = std::string(format); + if (!name_.empty()) { + std::ostringstream ss; + ss << name_ << ": " << format; + prefixedFormat = ss.str(); + } -void Log(const char* format, ...) { va_list args; va_start(args, format); - va_list args_copy; - va_copy(args_copy, args); - size_t len = std::vsnprintf(nullptr, 0, format, args_copy); - va_end(args_copy); + va_list args2; + va_copy(args2, args); + size_t len = std::vsnprintf(nullptr, 0, prefixedFormat.data(), args2); + va_end(args2); std::vector buf(len + 1); - std::vsnprintf(buf.data(), buf.size(), format, args); + std::vsnprintf(buf.data(), buf.size(), prefixedFormat.data(), args); va_end(args); - std::ostringstream ss; - ss << buf.data() << std::endl; - vr::VRDriverLog()->Log(ss.str().c_str()); + std::lock_guard lock(mutex_); + LogImpl(buf.data()); +} + +void ConsoleLogger::LogImpl(const char* message) { + std::cout << message << '\n' << std::flush; +} + +void VRLogger::LogImpl(const char* message) { + vr::VRDriverLog()->Log(message); } \ No newline at end of file diff --git a/src/Logger.hpp b/src/Logger.hpp index eafdd43..ff5c186 100644 --- a/src/Logger.hpp +++ b/src/Logger.hpp @@ -1,7 +1,36 @@ #pragma once +#include +#include #include #include #include #include -void Log(const char* format, ...); \ No newline at end of file +class Logger { +public: + Logger() : name_("") { } + Logger(const char* name) : name_(name) { } + void Log(const char* format, ...); +protected: + virtual void LogImpl(const char* string) = 0; + std::string name_; + std::mutex mutex_; +}; + +class NullLogger: public Logger { + using Logger::Logger; +protected: + void LogImpl(const char* message) override {}; +}; + +class ConsoleLogger: public Logger { + using Logger::Logger; +protected: + void LogImpl(const char* message) override; +}; + +class VRLogger: public Logger { + using Logger::Logger; +protected: + void LogImpl(const char* message) override; +}; \ No newline at end of file diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 223d7cb..9b3f856 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -11,7 +11,7 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont return init_error; } - Log("Activating SlimeVR Driver..."); + logger->Log("Activating SlimeVR Driver..."); try { auto json = simdjson::padded_string::load(GetVRPathRegistryFilename()); // load VR Path Registry @@ -19,12 +19,13 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont auto path = std::string { doc.get_object()["config"].at(0).get_string().value() }; default_chap_path_ = GetDefaultChaperoneFromConfigPath(path); } catch (simdjson::simdjson_error& e) { - Log("Error getting VR Config path, continuing: %s", e.error()); + logger->Log("Error getting VR Config path, continuing: %s", e.error()); } - Log("SlimeVR Driver Loaded Successfully"); + logger->Log("SlimeVR Driver Loaded Successfully"); bridge = std::make_shared( + std::static_pointer_cast(std::make_shared("Bridge")), std::bind(&SlimeVRDriver::VRDriver::OnBridgeMessage, this, std::placeholders::_1) ); bridge->start(); @@ -49,7 +50,7 @@ void SlimeVRDriver::VRDriver::RunFrame() { this->openvr_events_ = std::move(events); // Update frame timing - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); this->frame_timing_ = std::chrono::duration_cast(now - this->last_frame_time_); this->last_frame_time_ = now; @@ -85,7 +86,7 @@ void SlimeVRDriver::VRDriver::RunFrame() { bridge->sendBridgeMessage(*message); sentHmdAddMessage = true; - Log("Sent HMD hello message"); + logger->Log("Sent HMD hello message"); } vr::PropertyContainerHandle_t hmdPropContainer = @@ -97,7 +98,7 @@ void SlimeVRDriver::VRDriver::RunFrame() { if (res.has_value()) { current_universe.emplace(universe, res.value()); } else { - Log("Failed to find current universe!"); + logger->Log("Failed to find current universe!"); } } @@ -152,7 +153,7 @@ void SlimeVRDriver::VRDriver::RunFrame() { bridge->sendBridgeMessage(*message); } -void SlimeVRDriver::VRDriver::OnBridgeMessage(messages::ProtobufMessage& message) { +void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& message) { // note: called from bridge thread; // driver sample says that TrackedDevicePoseUpdated should happen from "some pose tracking thread", // thus we assume the functions that "notify" (as described in docs) @@ -164,7 +165,7 @@ void SlimeVRDriver::VRDriver::OnBridgeMessage(messages::ProtobufMessage& message switch(getDeviceType(static_cast(ta.tracker_role()))) { case DeviceType::TRACKER: this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); - Log("New tracker device added " + ta.tracker_serial() + " (id " + std::to_string(ta.tracker_id()) + ")"); + logger->Log("New tracker device added %s (id %i)", ta.tracker_serial().c_str(), ta.tracker_id()); break; } } else if (message.has_position()) { @@ -231,9 +232,9 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { std::shared_ptr oldDevice = this->devices_by_serial[device->GetSerial()]; if (oldDevice->getDeviceId() != device->getDeviceId()) { this->devices_by_id[device->getDeviceId()] = oldDevice; - Log("Device overridden from id " + std::to_string(oldDevice->getDeviceId()) + " to " + std::to_string(device->getDeviceId()) + " for serial " + device->GetSerial()); + logger->Log("Device overridden from id %i to %i for serial %s", oldDevice->getDeviceId(), device->getDeviceId(), device->GetSerial()); } else { - Log("Device readded id " + std::to_string(device->getDeviceId()) + ", serial " + device->GetSerial()); + logger->Log("Device readded id %i, serial %s", device->getDeviceId(), device->GetSerial().c_str()); } } return result; @@ -348,7 +349,7 @@ std::optional SlimeVRDriver::VRDriver::searc } } } catch (simdjson::simdjson_error& e) { - Log("Error getting universes from %s: %s", path, e.error()); + logger->Log("Error getting universes from %s: %s", path.c_str(), e.what()); return std::nullopt; } diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index 8603ef0..c380fb6 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -26,13 +26,7 @@ namespace SlimeVRDriver { virtual bool AddDevice(std::shared_ptr device) override; virtual SettingsValue GetSettingsValue(std::string key) override; virtual void Log(std::string message) override { - ::Log("%s", message.c_str()); - }; - void Log(const char* format, ...) { - va_list args; - va_start(args, format); - ::Log(format, args); - va_end(args); + logger->Log("%s", message.c_str()); }; virtual vr::IVRDriverInput* GetInput() override; @@ -48,18 +42,19 @@ namespace SlimeVRDriver { virtual void LeaveStandby() override; virtual ~VRDriver() = default; - void OnBridgeMessage(messages::ProtobufMessage& message); + void OnBridgeMessage(const messages::ProtobufMessage& message); virtual std::optional GetCurrentUniverse() override; private: + std::shared_ptr logger = std::make_shared(); std::mutex devices_mutex_; std::vector> devices_; std::vector openvr_events_; std::map> devices_by_id; std::map> devices_by_serial; std::chrono::milliseconds frame_timing_ = std::chrono::milliseconds(16); - std::chrono::system_clock::time_point last_frame_time_ = std::chrono::system_clock::now(); + std::chrono::steady_clock::time_point last_frame_time_ = std::chrono::steady_clock::now(); std::string settings_key_ = "driver_slimevr"; vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t &matrix); diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index 52dd456..b8f2fe6 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -1,6 +1,6 @@ /* SlimeVR Code is placed under the MIT license - Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + Copyright (c) 2022 SlimeVR Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,167 +21,52 @@ THE SOFTWARE. */ #include "BridgeClient.hpp" -#include -namespace fs = std::filesystem; using namespace std::literals::chrono_literals; -#define WINDOWS_PIPE_NAME "\\\\.\\pipe\\SlimeVRDriver" -#define UNIX_TMP_DIR "/tmp" -#define UNIX_SOCKET_NAME "SlimeVRDriver" - -std::string BridgeClient::makeBridgePath() { -#ifdef __linux__ - if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { - const fs::path xdg_runtime = ptr; - return (xdg_runtime / UNIX_SOCKET_NAME).string(); - } else { - return (fs::path(UNIX_TMP_DIR) / UNIX_SOCKET_NAME).string(); - } -#else - return WINDOWS_PIPE_NAME; -#endif -} - -void BridgeClient::start() { - if (running) return; - running = true; - thread = std::thread(&BridgeClient::runThread, this); -} - -void BridgeClient::stop() { - if (!running) return; - Log("Bridge: stopping"); - running = false; - stopHandle->send(); - thread.join(); -} - -void BridgeClient::runThread() { - Log("Bridge: thread started"); - stopHandle = uvw::Loop::getDefault()->resource(); - writeHandle = uvw::Loop::getDefault()->resource(); - - stopHandle->on([&](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { - Log("Bridge: closing handles"); - pipeHandle->close(); - writeHandle->close(); - handle.close(); - }); - - writeHandle->on([&](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { - sendWrites(); - }); - - connect(); - uvw::Loop::getDefault()->run(); - Log("Bridge: thread stopped"); -} - -void BridgeClient::connect() { - Log("Bridge: connecting"); - recvBuf.clear(); - sendBuf.clear(); +void BridgeClient::createConnection() { + logger->Log("connecting"); + resetBuffers(); /* ipc = false -> pipe will be used for handle passing between processes? no */ - pipeHandle = uvw::Loop::getDefault()->resource(false); + connectionHandle = getLoop()->resource(false); - pipeHandle->on([&](const uvw::ConnectEvent &, uvw::PipeHandle &) { - pipeHandle->read(); - Log("Bridge: connecting"); + connectionHandle->on([this](const uvw::ConnectEvent&, uvw::PipeHandle&) { + connectionHandle->read(); + logger->Log("connected"); connected = true; }); - - pipeHandle->on([&](const uvw::EndEvent &, uvw::PipeHandle &) { - Log("Bridge: disconnected"); - disconnect(); + connectionHandle->on([this](const uvw::EndEvent&, uvw::PipeHandle&) { + logger->Log("disconnected"); + reconnect(); }); - - pipeHandle->on([&](const uvw::DataEvent &event, uvw::PipeHandle &) { + connectionHandle->on([this](const uvw::DataEvent& event, uvw::PipeHandle&) { onRecv(event); }); - - pipeHandle->on([&](const uvw::ErrorEvent &event, uvw::PipeHandle &) { - Log("Bridge: Pipe error: %s", event.what()); - disconnect(); + connectionHandle->on([this](const uvw::ErrorEvent& event, uvw::PipeHandle&) { + logger->Log("Pipe error: %s", event.what()); + reconnect(); }); - pipeHandle->connect(path); + connectionHandle->connect(path); } -void BridgeClient::disconnect() { - pipeHandle->close(); - connected = false; - std::this_thread::sleep_for(1000ms); - connect(); +void BridgeClient::resetConnection() { + reconnect(); } -void BridgeClient::onRecv(const uvw::DataEvent &event) { - if (!recvBuf.push(event.data.get(), event.length)) { - Log("recvBuf.push %i failed", event.length); - disconnect(); - return; - } - - size_t available; - while (available = recvBuf.bytes_available()) { - if (available < 4) return; - - char lenBuf[4]; - recvBuf.peek(lenBuf, 4); - uint32_t size = LE32_TO_NATIVE(*reinterpret_cast(lenBuf)); - - if (size > VRBRIDGE_MAX_MESSAGE_SIZE) { - Log("Message size overflow"); - disconnect(); - return; - } - - auto unwrappedSize = size - 4; - if (available < unwrappedSize) return; - - auto messageBuf = std::make_unique(size); - if (!recvBuf.skip(4) || !recvBuf.pop(messageBuf.get(), unwrappedSize)) { - Log("recvBuf.pop %i failed", size); - disconnect(); - return; - } - - messages::ProtobufMessage receivedMessage; - if (receivedMessage.ParseFromArray(messageBuf.get(), unwrappedSize)) { - messageCallback(receivedMessage); - } else { - Log("receivedMessage.ParseFromArray failed"); - disconnect(); - return; - } - } +void BridgeClient::reconnect() { + closeConnectionHandles(); + reconnectTimeout = getLoop()->resource(); + reconnectTimeout->start(1000ms, 0ms); + reconnectTimeout->once([this](const uvw::TimerEvent&, uvw::TimerHandle& handle) { + createConnection(); + handle.close(); + }); } -void BridgeClient::sendBridgeMessage(messages::ProtobufMessage &message) { - if (!isConnected()) return; - - uint32_t size = static_cast(message.ByteSizeLong()); - uint32_t wrappedSize = size + 4; - - auto messageBuf = std::make_unique(wrappedSize); - *reinterpret_cast(messageBuf.get()) = NATIVE_TO_LE32(wrappedSize); - message.SerializeToArray(messageBuf.get() + 4, size); - if (!sendBuf.push(messageBuf.get(), wrappedSize)) { - disconnect(); - return; - } - - writeHandle->send(); +void BridgeClient::closeConnectionHandles() { + if (connectionHandle) connectionHandle->close(); + if (reconnectTimeout) reconnectTimeout->close(); + connected = false; } - -void BridgeClient::sendWrites() { - if (!isConnected()) return; - - auto available = sendBuf.bytes_available(); - if (!available) return; - - auto writeBuf = std::make_unique(available); - sendBuf.pop(writeBuf.get(), available); - pipeHandle->write(writeBuf.get(), static_cast(available)); -} \ No newline at end of file diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp index b84f885..020d923 100644 --- a/src/bridge/BridgeClient.hpp +++ b/src/bridge/BridgeClient.hpp @@ -1,6 +1,6 @@ /* SlimeVR Code is placed under the MIT license - Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + Copyright (c) 2022 SlimeVR Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,76 +22,32 @@ */ #pragma once -#include #include -#include -#include #include -#include -#include "Logger.hpp" -#include "CircularBuffer.hpp" -#include "ProtobufMessages.pb.h" - -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - #define LE32_TO_NATIVE(x) (x) -#else - #define LE32_TO_NATIVE(x) ( \ - ((uint32_t)(x) << 24) | \ - (((uint32_t)(x) << 8) & 0x00FF0000) | \ - (((uint32_t)(x) >> 8) & 0x0000FF00) | \ - ((uint32_t)(x) >> 24) \ - ) -#endif -#define NATIVE_TO_LE32 LE32_TO_NATIVE - -#define VRBRIDGE_MAX_MESSAGE_SIZE 1024 +#include "BridgeTransport.hpp" /** - * @brief Cross-platform bridge client. - * - * Starts a thread with an event loop, which handles IPC. + * @brief Client implementation for communication with SlimeVR Server using pipes. + * + * This class provides a set of methods to start, stop an IO thread, send messages over a named pipe or unix socket + * and is abstracted through `libuv`. + * + * When a message is received and parsed from the pipe, the messageCallback function passed in the constructor is called + * from the event loop thread with the message as a parameter. + * + * @param logger A shared pointer to an Logger object to log messages from the transport. + * @param onMessageReceived A function to be called from event loop thread when a message is received and parsed from the pipe. */ -class BridgeClient { +class BridgeClient: public BridgeTransport { public: - /** - * @param onMessageReceived Gets called from the event loop thread when a message is received and parsed. - */ - BridgeClient(std::function onMessageReceived) : - messageCallback(onMessageReceived), - path(makeBridgePath()), - sendBuf(8192), - recvBuf(8192) - { } - - ~BridgeClient() { - stop(); - } - - void start(); - void stop(); - void sendBridgeMessage(messages::ProtobufMessage &message); - bool isConnected() { - return connected; - }; + using BridgeTransport::BridgeTransport; private: - static std::string makeBridgePath(); - void runThread(); - void connect(); - void onRecv(const uvw::DataEvent &event); - void disconnect(); - void sendWrites(); + void createConnection() override; + void resetConnection() override; + void closeConnectionHandles() override; + void reconnect(); - CircularBuffer sendBuf; - CircularBuffer recvBuf; - std::string path; - std::shared_ptr pipeHandle; - std::shared_ptr stopHandle; - std::shared_ptr writeHandle; - std::thread thread; - std::function messageCallback; - std::queue messageQueue; - std::atomic running = false; - std::atomic connected = false; + std::shared_ptr reconnectTimeout; }; \ No newline at end of file diff --git a/src/bridge/BridgeTransport.cpp b/src/bridge/BridgeTransport.cpp new file mode 100644 index 0000000..a7ec620 --- /dev/null +++ b/src/bridge/BridgeTransport.cpp @@ -0,0 +1,138 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#include "BridgeTransport.hpp" + +void BridgeTransport::start() { + thread = std::make_unique(&BridgeTransport::runThread, this); +} + +void BridgeTransport::stop() { + if (!thread || !thread->joinable()) return; + stopAsync(); + logger->Log("stopping"); + thread->join(); + thread.reset(); +} + +void BridgeTransport::stopAsync() { + if (!stopSignalHandle || stopSignalHandle->closing()) return; + stopSignalHandle->send(); +} + +void BridgeTransport::runThread() { + logger->Log("thread started"); + loop = uvw::Loop::create(); + stopSignalHandle = getLoop()->resource(); + writeSignalHandle = getLoop()->resource(); + + stopSignalHandle->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + logger->Log("closing handles"); + closeConnectionHandles(); + writeSignalHandle->close(); + stopSignalHandle->close(); + }); + + writeSignalHandle->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + sendWrites(); + }); + + createConnection(); + getLoop()->run(); + getLoop()->close(); + logger->Log("thread exited"); +} + +void BridgeTransport::resetBuffers() { + recvBuf.clear(); + sendBuf.clear(); +} + +void BridgeTransport::onRecv(const uvw::DataEvent& event) { + if (!recvBuf.push(event.data.get(), event.length)) { + logger->Log("recvBuf.push %i failed", event.length); + resetConnection(); + return; + } + + size_t available; + while (available = recvBuf.bytes_available()) { + if (available < 4) return; + + char lenBuf[4]; + recvBuf.peek(lenBuf, 4); + uint32_t size = LE32_TO_NATIVE(*reinterpret_cast(lenBuf)); + + if (size > VRBRIDGE_MAX_MESSAGE_SIZE) { + logger->Log("message size overflow"); + resetConnection(); + return; + } + + auto unwrappedSize = size - 4; + if (available < unwrappedSize) return; + + auto messageBuf = std::make_unique(size); + if (!recvBuf.skip(4) || !recvBuf.pop(messageBuf.get(), unwrappedSize)) { + logger->Log("recvBuf.pop %i failed", size); + resetConnection(); + return; + } + + messages::ProtobufMessage receivedMessage; + if (receivedMessage.ParseFromArray(messageBuf.get(), unwrappedSize)) { + messageCallback(receivedMessage); + } else { + logger->Log("receivedMessage.ParseFromArray failed"); + resetConnection(); + return; + } + } +} + +void BridgeTransport::sendBridgeMessage(const messages::ProtobufMessage& message) { + if (!isConnected()) return; + + uint32_t size = static_cast(message.ByteSizeLong()); + uint32_t wrappedSize = size + 4; + + auto messageBuf = std::make_unique(wrappedSize); + *reinterpret_cast(messageBuf.get()) = NATIVE_TO_LE32(wrappedSize); + message.SerializeToArray(messageBuf.get() + 4, size); + if (!sendBuf.push(messageBuf.get(), wrappedSize)) { + resetConnection(); + return; + } + + writeSignalHandle->send(); +} + +void BridgeTransport::sendWrites() { + if (!isConnected()) return; + + auto available = sendBuf.bytes_available(); + if (!available) return; + + auto writeBuf = std::make_unique(available); + sendBuf.pop(writeBuf.get(), available); + connectionHandle->write(writeBuf.get(), static_cast(available)); +} \ No newline at end of file diff --git a/src/bridge/BridgeTransport.hpp b/src/bridge/BridgeTransport.hpp new file mode 100644 index 0000000..4b8056b --- /dev/null +++ b/src/bridge/BridgeTransport.hpp @@ -0,0 +1,161 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#pragma once + +#include +#include +#include +#include + +#include "Logger.hpp" +#include "CircularBuffer.hpp" +#include "ProtobufMessages.pb.h" + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define LE32_TO_NATIVE(x) (x) +#else + #define LE32_TO_NATIVE(x) ( \ + ((uint32_t)(x) << 24) | \ + (((uint32_t)(x) << 8) & 0x00FF0000) | \ + (((uint32_t)(x) >> 8) & 0x0000FF00) | \ + ((uint32_t)(x) >> 24) \ + ) +#endif +#define NATIVE_TO_LE32 LE32_TO_NATIVE + +#define VRBRIDGE_MAX_MESSAGE_SIZE 1024 +#define VRBRIDGE_BUFFERS_SIZE 8192 + +namespace fs = std::filesystem; + +#define WINDOWS_PIPE_NAME "\\\\.\\pipe\\SlimeVRDriver" +#define UNIX_TMP_DIR "/tmp" +#define UNIX_SOCKET_NAME "SlimeVRDriver" + +static std::string getBridgePath() { +#ifdef __linux__ + if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { + const fs::path xdg_runtime = ptr; + return (xdg_runtime / UNIX_SOCKET_NAME).string(); + } else { + return (fs::path(UNIX_TMP_DIR) / UNIX_SOCKET_NAME).string(); + } +#else + return WINDOWS_PIPE_NAME; +#endif +} + +/** + * @brief Abstract implementation for passing messages between SlimeVR Server and SteamVR Driver using pipes. + * + * Client or Server connection handling is implemented by extending this class. + * + * This class provides a set of methods to start, stop an IO thread, send messages over a named pipe or unix socket + * and is abstracted through `libuv`. + * + * When a message is received and parsed from the pipe, the messageCallback function passed in the constructor is called + * from the event loop thread with the message as a parameter. + * + * @param logger A shared pointer to an Logger object to log messages from the transport. + * @param onMessageReceived A function to be called from event loop thread when a message is received and parsed from the pipe. + */ +class BridgeTransport { +public: + BridgeTransport(std::shared_ptr _logger, std::function onMessageReceived) : + logger(_logger), + messageCallback(onMessageReceived), + path(getBridgePath()), + sendBuf(VRBRIDGE_BUFFERS_SIZE), + recvBuf(VRBRIDGE_BUFFERS_SIZE) + { } + + ~BridgeTransport() { + stop(); + } + + /** + * @brief Starts the channel by creating a thread with an libuv event loop. + * + * Connects and automatic reconnects with a timeout are implemented internally. + */ + void start(); + + /** + * @brief Stops the channel by stopping the libuv event loop and closing the connection handles. + * + * Blocks until the event loop is stopped and the connection handles are closed. + */ + void stop(); + + /** + * @brief Stops the channel asynchronously by sending a signal to the libuv event loop to stop and returning immediately. + * + * The `stop()` function calls this method. + */ + void stopAsync(); + + /** + * @brief Sends a message over the channel. + * + * This method queues the message in the send buffer to be sent over the pipe. + * + * @param message The message to send. + */ + void sendBridgeMessage(const messages::ProtobufMessage& message); + + /** + * @brief Checks if the channel is connected. + * + * @return true if the channel is connected, false otherwise. + */ + bool isConnected() { + return connected; + }; + +protected: + virtual void createConnection() = 0; + virtual void resetConnection() = 0; + virtual void closeConnectionHandles() = 0; + void resetBuffers(); + void onRecv(const uvw::DataEvent& event); + auto getLoop() { + return loop; + } + + std::shared_ptr connectionHandle = nullptr; + std::shared_ptr logger; + const std::string path; + std::atomic connected = false; + +private: + void runThread(); + void sendWrites(); + + CircularBuffer sendBuf; + CircularBuffer recvBuf; + std::shared_ptr stopSignalHandle = nullptr; + std::shared_ptr writeSignalHandle = nullptr; + std::unique_ptr thread = nullptr; + std::shared_ptr loop = nullptr; + const std::function messageCallback; +}; \ No newline at end of file diff --git a/src/bridge/CircularBuffer.hpp b/src/bridge/CircularBuffer.hpp index 63dfbe7..b3dcb78 100644 --- a/src/bridge/CircularBuffer.hpp +++ b/src/bridge/CircularBuffer.hpp @@ -1,6 +1,6 @@ /* SlimeVR Code is placed under the MIT license - Copyright (c) 2022 Eiren Rain and SlimeVR Contributors + Copyright (c) 2022 SlimeVR Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -48,7 +48,7 @@ class CircularBuffer { } bool pop(char* data, size_t size) { - if (size > bytes_available()) return false; + if (size > bytes_available()) return false; size_t size1 = std::min(size, size_ - (tail_ % size_)); size_t size2 = size - size1; std::memcpy(data, buffer_.get() + (tail_ % size_ ), size1); @@ -59,6 +59,7 @@ class CircularBuffer { } size_t peek(char* data, size_t size) { + if (size > bytes_available()) return false; size_t available = bytes_available(); size_t size1 = std::min(size, size_ - (tail_ % size_)); size_t size2 = std::min(size - size1, available - size1); diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp new file mode 100644 index 0000000..c0c6b56 --- /dev/null +++ b/test/BridgeServerMock.cpp @@ -0,0 +1,72 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#include "BridgeServerMock.hpp" + +using namespace std::literals::chrono_literals; + +void BridgeServerMock::createConnection() { + logger->Log("listening"); + + serverHandle = getLoop()->resource(false); + serverHandle->once([this](const uvw::ListenEvent &event, uvw::PipeHandle &) { + logger->Log("new client"); + resetBuffers(); + + /* ipc = false -> pipe will be used for handle passing between processes? no */ + connectionHandle = getLoop()->resource(false); + + connectionHandle->on([this](const uvw::EndEvent &, uvw::PipeHandle &) { + logger->Log("disconnected"); + stopAsync(); + }); + connectionHandle->on([this](const uvw::DataEvent &event, uvw::PipeHandle &) { + onRecv(event); + }); + connectionHandle->on([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + logger->Log("Pipe error: %s", event.what()); + stopAsync(); + }); + + serverHandle->accept(*connectionHandle); + connectionHandle->read(); + logger->Log("connected"); + connected = true; + }); + serverHandle->once([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + logger->Log("Bind '%s' error: %s", path, event.what()); + stopAsync(); + }); + + serverHandle->bind(path); + serverHandle->listen(); +} + +void BridgeServerMock::resetConnection() { + closeConnectionHandles(); +} + +void BridgeServerMock::closeConnectionHandles() { + if (serverHandle) serverHandle->close(); + if (connectionHandle) connectionHandle->close(); + connected = false; +} diff --git a/test/BridgeServerMock.hpp b/test/BridgeServerMock.hpp new file mode 100644 index 0000000..3154858 --- /dev/null +++ b/test/BridgeServerMock.hpp @@ -0,0 +1,40 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2022 SlimeVR Contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#pragma once + +#include +#include + +#include "bridge/BridgeTransport.hpp" + +class BridgeServerMock: public BridgeTransport { +public: + using BridgeTransport::BridgeTransport; + +private: + void createConnection() override; + void resetConnection() override; + void closeConnectionHandles() override; + + std::shared_ptr serverHandle = nullptr; +}; \ No newline at end of file diff --git a/test/TestBridgeClientMock.cpp b/test/TestBridgeClientMock.cpp new file mode 100644 index 0000000..0f64bf9 --- /dev/null +++ b/test/TestBridgeClientMock.cpp @@ -0,0 +1,93 @@ +#include + +#include "common/TestBridgeClient.hpp" +#include "BridgeServerMock.hpp" + +TEST_CASE("IO with a mock server", "[Bridge]") { + using namespace std::chrono; + + int positions = 0; + int invalidMessages = 0; + + bool lastLoggedPosition = false; + bool trackersSent = false; + + google::protobuf::Arena arena; + + auto logger = std::static_pointer_cast(std::make_shared("ServerMock")); + + std::shared_ptr serverMock; + serverMock = std::make_shared( + logger, + [&](const messages::ProtobufMessage& message) { + if (message.has_tracker_added()) { + testLogTrackerAdded(logger, message); + } else if (message.has_tracker_status()) { + testLogTrackerStatus(logger, message); + } else if (message.has_position()) { + messages::Position pos = message.position(); + if (!lastLoggedPosition) logger->Log("... tracker positions response"); + lastLoggedPosition = true; + positions++; + + messages::ProtobufMessage* serverMessage = google::protobuf::Arena::CreateMessage(&arena); + + if (!trackersSent) { + std::map> serials = { + { 3, {TrackerRole::WAIST, "human://WAIST"} }, + { 4, {TrackerRole::LEFT_KNEE, "human://LEFT_KNEE"} }, + { 5, {TrackerRole::RIGHT_KNEE, "human://RIGHT_KNEE"} }, + { 6, {TrackerRole::LEFT_FOOT, "human://LEFT_FOOT"} }, + { 7, {TrackerRole::RIGHT_FOOT, "human://RIGHT_FOOT"} }, + }; + + for (int32_t id = 3; id <= 7; id++) { + messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); + serverMessage->set_allocated_tracker_added(trackerAdded); + trackerAdded->set_tracker_id(id); + trackerAdded->set_tracker_role(serials[id].first); + trackerAdded->set_tracker_serial(serials[id].second); + trackerAdded->set_tracker_name(serials[id].second); + serverMock->sendBridgeMessage(*serverMessage); + + messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); + serverMessage->set_allocated_tracker_status(trackerStatus); + trackerStatus->set_tracker_id(id); + trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + serverMock->sendBridgeMessage(*serverMessage); + } + + trackersSent = true; + } + + for (int32_t id = 3; id <= 7; id++) { + messages::Position* trackerPosition = google::protobuf::Arena::CreateMessage(&arena); + serverMessage->set_allocated_position(trackerPosition); + trackerPosition->set_tracker_id(id); + trackerPosition->set_data_source(messages::Position_DataSource_FULL); + trackerPosition->set_x(0); + trackerPosition->set_y(0); + trackerPosition->set_z(0); + trackerPosition->set_qx(0); + trackerPosition->set_qy(0); + trackerPosition->set_qz(0); + trackerPosition->set_qw(0); + serverMock->sendBridgeMessage(*serverMessage); + } + } else { + invalidMessages++; + } + + if (!message.has_position()) { + lastLoggedPosition = false; + } + } + ); + + serverMock->start(); + std::this_thread::sleep_for(10ms); + testBridgeClient(); + serverMock->stop(); + + if (invalidMessages) FAIL("Invalid messages received"); +} \ No newline at end of file diff --git a/test/TestCircularBuffer.cpp b/test/TestCircularBuffer.cpp new file mode 100644 index 0000000..77e4965 --- /dev/null +++ b/test/TestCircularBuffer.cpp @@ -0,0 +1,34 @@ +#include + +#include "bridge/CircularBuffer.hpp" + +TEST_CASE("push/pop", "[CircularBuffer]") { + CircularBuffer buffer(4); + char data[4]; + + REQUIRE(buffer.push("1234", 4)); // [1234] + REQUIRE(buffer.pop(data, 2)); // [34] + REQUIRE(std::string(data, 2) == "12"); + + // test wraparound + REQUIRE(buffer.push("56", 2)); // [3456] + REQUIRE_FALSE(buffer.push("78", 2)); // [3456] buffer full + REQUIRE(buffer.pop(data, 4)); // [] + REQUIRE(std::string(data, 4) == "3456"); + REQUIRE_FALSE(buffer.pop(data, 4)); // [] buffer empty +} + +TEST_CASE("peek/skip", "[CircularBuffer]") { + CircularBuffer buffer(4); + char data[4]; + + REQUIRE_FALSE(buffer.peek(data, 2)); // [] nothing to peek + REQUIRE_FALSE(buffer.skip(2)); // [] nothing to skip + + REQUIRE(buffer.push("1234", 4)); // [1234] + REQUIRE(buffer.peek(data, 2) == 2); // [1234] + REQUIRE(std::string(data, 2) == "12"); + REQUIRE(buffer.skip(2)); // [34] + REQUIRE(buffer.peek(data, 1) == 1); // [34] + REQUIRE(std::string(data, 1) == "3"); +} \ No newline at end of file diff --git a/test/common/TestBridgeClient.cpp b/test/common/TestBridgeClient.cpp new file mode 100644 index 0000000..e7e4a7f --- /dev/null +++ b/test/common/TestBridgeClient.cpp @@ -0,0 +1,134 @@ +#include "TestBridgeClient.hpp" + +void testLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message) { + if (!message.has_tracker_added()) return; + messages::TrackerAdded ta = message.tracker_added(); + logger->Log("tracker added id %i name %s role %i serial %s", + ta.tracker_id(), + ta.tracker_name().c_str(), + ta.tracker_role(), + ta.tracker_serial().c_str() + ); +} + +void testLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message) { + if (!message.has_tracker_status()) return; + messages::TrackerStatus status = message.tracker_status(); + if (status.status() == messages::TrackerStatus_Status_OK) { + logger->Log("tracker status id %i status %s", status.tracker_id(), "OK"); + } else if (status.status() == messages::TrackerStatus_Status_DISCONNECTED) { + logger->Log("tracker status id %i status %s", status.tracker_id(), "DISCONNECTED"); + } else if (status.status() == messages::TrackerStatus_Status_ERROR) { + logger->Log("tracker status id %i status %s", status.tracker_id(), "ERROR"); + } else if (status.status() == messages::TrackerStatus_Status_BUSY) { + logger->Log("tracker status id %i status %s", status.tracker_id(), "BUSY"); + } +} + +void testBridgeClient() { + using namespace std::chrono; + + std::atomic readyToBench = false; + std::atomic positionRequestedAt = steady_clock::now(); + std::map latencyNanosSum; + std::map latencyNanosCount; + + int invalidMessages = 0; + int trackers = 0; + int positions = 0; + + bool lastLoggedPosition = false; + + auto logger = std::static_pointer_cast(std::make_shared("Bridge")); + auto bridge = std::make_shared( + logger, + [&](const messages::ProtobufMessage& message) { + if (message.has_tracker_added()) { + trackers++; + testLogTrackerAdded(logger, message); + } else if (message.has_tracker_status()) { + testLogTrackerStatus(logger, message); + } else if (message.has_position()) { + messages::Position pos = message.position(); + if (!lastLoggedPosition) logger->Log("... tracker positions"); + lastLoggedPosition = true; + positions++; + + if (!readyToBench) return; + + auto id = pos.tracker_id(); + auto dt = duration_cast(steady_clock::now() - positionRequestedAt.load()); + latencyNanosCount[id]++; + latencyNanosSum[id] += dt.count(); + } else { + invalidMessages++; + } + + if (!message.has_position()) { + lastLoggedPosition = false; + } + } + ); + + bridge->start(); + + for (int i = 0; i < 20; i++) { + if (bridge->isConnected()) break; + std::this_thread::sleep_for(milliseconds(100)); + } + + if (!bridge->isConnected()) { + FAIL("Connection attempt timed out"); + bridge->stop(); + return; + } + + google::protobuf::Arena arena; + messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); + + messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_added(trackerAdded); + trackerAdded->set_tracker_id(0); + trackerAdded->set_tracker_role(TrackerRole::HMD); + trackerAdded->set_tracker_serial("HMD"); + trackerAdded->set_tracker_name("HMD"); + bridge->sendBridgeMessage(*message); + + messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_status(trackerStatus); + trackerStatus->set_tracker_id(0); + trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + bridge->sendBridgeMessage(*message); + + readyToBench = true; + + for (int i = 0; i < 50; i++) { + messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_position(hmdPosition); + hmdPosition->set_tracker_id(0); + hmdPosition->set_data_source(messages::Position_DataSource_FULL); + hmdPosition->set_x(0); + hmdPosition->set_y(0); + hmdPosition->set_z(0); + hmdPosition->set_qx(0); + hmdPosition->set_qy(0); + hmdPosition->set_qz(0); + hmdPosition->set_qw(0); + + positionRequestedAt = steady_clock::now(); + bridge->sendBridgeMessage(*message); + std::this_thread::sleep_for(milliseconds(10)); + } + + bridge->stop(); + + for (const auto& [id, sum] : latencyNanosSum) { + auto avgLatencyNanos = static_cast(latencyNanosCount[id] ? sum / latencyNanosCount[id] : -1); + auto avgLatencyMs = duration_cast>(nanoseconds(avgLatencyNanos)); + logger->Log("Avg latency for tracker %i: %.3fms", id, avgLatencyMs.count()); + } + + if (invalidMessages) FAIL("Invalid messages received"); + if (!trackers) FAIL("No trackers received"); + if (!positions) FAIL("No tracker positions received"); +} diff --git a/test/common/TestBridgeClient.hpp b/test/common/TestBridgeClient.hpp new file mode 100644 index 0000000..89fa716 --- /dev/null +++ b/test/common/TestBridgeClient.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "DriverFactory.hpp" +#include "bridge/BridgeClient.hpp" +#include "TrackerRole.hpp" + +void testLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message); +void testLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message); +void testBridgeClient(); diff --git a/test/integration/TestBridgeClientReal.cpp b/test/integration/TestBridgeClientReal.cpp new file mode 100644 index 0000000..faa1c4b --- /dev/null +++ b/test/integration/TestBridgeClientReal.cpp @@ -0,0 +1,9 @@ +#include + +#include "DriverFactory.hpp" +#include "TrackerRole.hpp" +#include "../common/TestBridgeClient.hpp" + +TEST_CASE("IO with a real server", "[Bridge]") { + testBridgeClient(); +} \ No newline at end of file From c9d499d41cb5f10f67d29d63b067f602b4d3004c Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Mon, 27 Mar 2023 00:43:41 +0300 Subject: [PATCH 03/24] Pin vcpkg registry --- .gitmodules | 3 +++ README.md | 10 +++++++++- vcpkg | 1 + vcpkg.json | 18 +++++++++--------- 4 files changed, 22 insertions(+), 10 deletions(-) create mode 160000 vcpkg diff --git a/.gitmodules b/.gitmodules index bf5392b..df7de3c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "libraries/linalg"] path = libraries/linalg url = https://github.com/sgorsten/linalg.git +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/Microsoft/vcpkg.git diff --git a/README.md b/README.md index 66420aa..09d36b5 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,16 @@ okay with this and that you are authorized to provide the above licenses. ### Building -To build the project with VSCode you need to install two things: [vcpkg](https://vcpkg.io/en/getting-started.html) and [VS Build Tools](https://visualstudio.microsoft.com/downloads/). +Clone the repo with `git clone --recurse-submodules https://github.com/SlimeVR/SlimeVR-OpenVR-Driver` to clone with all libraries and [vcpkg](https://vcpkg.io/en/getting-started.html) registry. + +To build the project with VSCode on Windows you need to install [VS Build Tools](https://visualstudio.microsoft.com/downloads/). + +Run the bootstrap script to build vcpkg binary `.\vcpkg\bootstrap-vcpkg.bat` or `./vcpkg/bootstrap-vcpkg.sh`. After installing vcpkg if you're on Windows, you need to run `vcpkg integrate install` command from the vcpkg folder to integrate it for VSCode. For other systems and IDEs instructions are not available as of now, contributions are welcome. + +### Updating vcpkg packages + +To update vcpkg packages set the vcpkg registry submodule to a newer commit. \ No newline at end of file diff --git a/vcpkg b/vcpkg new file mode 160000 index 0000000..b81bc3a --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit b81bc3a83fdbdffe80325eeabb2ec735a1f3c29d diff --git a/vcpkg.json b/vcpkg.json index 788010f..2cc1da5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,10 +1,10 @@ { - "name": "slimevr-openvr-driver", - "version": "0.2.0", - "dependencies": [ - "protobuf", - "simdjson", - "libuv", - "uvw" - ] -} \ No newline at end of file + "name": "slimevr-openvr-driver", + "version": "0.2.0", + "dependencies": [ + "protobuf", + "simdjson", + "uvw", + "catch2" + ] +} From b03f54063653a039376a4a17c955c5e105d449ac Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Mon, 27 Mar 2023 00:44:38 +0300 Subject: [PATCH 04/24] ci: Fix cache key, add tests --- .github/workflows/c-cpp.yml | 64 ++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 2ab02e2..b7ecb4c 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -1,6 +1,6 @@ name: SlimeVR OpenVR Driver -on: [ push, pull_request ] +on: [ push, pull_request, workflow_dispatch ] jobs: build: @@ -15,11 +15,11 @@ jobs: - os: windows-latest triplet: x64-windows-static-md target: ALL_BUILD - release_dir: Release/driver # dir of driver binaries within env.CMAKE_BUILD_DIR, VS multi-config uses / subfolder + release_dir: Release # dir of driver binaries within env.CMAKE_BUILD_DIR, VS multi-config uses / subfolder - os: ubuntu-latest triplet: x64-linux target: all - release_dir: driver # makefile single config won't have subfolder + release_dir: "" # makefile single config won't have subfolder env: # Indicates the CMake build directory where project files and binaries are being produced. CMAKE_BUILD_DIR: ${{ github.workspace }}/build @@ -30,15 +30,12 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - + - uses: lukka/get-cmake@latest - - - name: Clone vcpkg - uses: actions/checkout@v2 - with: - repository: microsoft/vcpkg - path: ${{ env.VCPKG_ROOT }} - submodules: true + + - name: Get submodule commit hashes + id: submodule_hashes + run: git submodule foreach --recursive git rev-parse HEAD > submodule_hashes.txt - name: Restore vcpkg and its artifacts uses: actions/cache@v2 @@ -52,12 +49,11 @@ jobs: !${{ env.VCPKG_ROOT }}/buildtrees !${{ env.VCPKG_ROOT }}/packages !${{ env.VCPKG_ROOT }}/downloads - # The key is composed in a way that it gets properly invalidated: this must happen whenever vcpkg's Git commit id changes, or the list of packages changes. In this case a cache miss must happen and a new entry with a new key with be pushed to GitHub the cache service. + # The key is composed in a way that it gets properly invalidated: this must happen whenever vcpkg/submodule Git commit id changes, or the list of packages changes. In this case a cache miss must happen and a new entry with a new key with be pushed to GitHub the cache service. # The key includes: hash of the vcpkg.json file, the hash of the vcpkg Git commit id, and the used vcpkg's triplet. The vcpkg's commit id would suffice, but computing an hash out it does not harm. # Note: given a key, the cache content is immutable. If a cache entry has been created improperly, in order the recreate the right content the key must be changed as well, and it must be brand new (i.e. not existing already). - key: | - ${{ hashFiles( 'vcpkg_manifest/vcpkg.json' ) }}-${{ hashFiles( '.git/modules/vcpkg/HEAD' )}}-${{ hashFiles( '${{ env.VCPKG_ROOT }}/.git/HEAD' )}}-${{ matrix.triplet }}-invalidate - + key: ${{ matrix.triplet }}-${{ hashFiles( '**/vcpkg.json' ) }}-${{ hashFiles( 'submodule_hashes.txt' )}} + - if: matrix.os == 'windows-latest' name: Set up vcpkg for Windows run: ${{ env.VCPKG_ROOT }}/bootstrap-vcpkg.bat @@ -80,16 +76,46 @@ jobs: # A file, directory or wildcard pattern that describes what to upload # Using wildcards so that only the driver directory gets included (if you specify it, then it won't be included) path: | - ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/* + ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/driver/* + ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/tests* - name: Zip if: startsWith(github.ref, 'refs/tags/') - working-directory: ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }} - run: cmake -E tar "cf" "${{env.CMAKE_BUILD_DIR}}/slimevr-openvr-driver-${{ matrix.triplet }}.zip" --format=zip -- ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/slimevr + working-directory: ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/driver + run: cmake -E tar "cf" "${{env.CMAKE_BUILD_DIR}}/slimevr-openvr-driver-${{ matrix.triplet }}.zip" --format=zip -- ${{env.CMAKE_BUILD_DIR}}/${{ matrix.release_dir }}/driver/slimevr - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: "${{env.CMAKE_BUILD_DIR}}/slimevr-openvr-driver-${{ matrix.triplet }}.zip" - + + test: + name: Run tests + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] + include: + - os: windows-latest + triplet: x64-windows-static-md + target: RUN_TESTS + - os: ubuntu-latest + triplet: x64-linux + target: test + steps: + - name: Download build artifact + uses: actions/download-artifact@v2 + with: + name: slimevr-openvr-driver-${{ matrix.triplet }} + path: ${{ github.workspace }} + - if: matrix.os == 'windows-latest' + name: Run tests on Windows + working-directory: ${{ github.workspace }}/ + run: .\tests.exe + - if: matrix.os != 'windows-latest' + name: Run tests on Unix + working-directory: ${{ github.workspace }}/ + run: chmod +x ./tests && ./tests From 8cc160b345a822dd79f43361828a3ddc1107aa36 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:56:40 +0300 Subject: [PATCH 05/24] Fix code style --- src/DriverFactory.cpp | 3 +- src/IVRDevice.hpp | 66 +++++---- src/IVRDriver.hpp | 93 +++++++------ src/Logger.cpp | 8 +- src/Logger.hpp | 2 +- src/TrackerDevice.cpp | 101 ++++++-------- src/TrackerDevice.hpp | 22 ++- src/TrackerRole.cpp | 15 +- src/TrackerRole.hpp | 6 +- src/VRDriver.cpp | 158 +++++++++++----------- src/VRDriver.hpp | 23 ++-- src/bridge/BridgeClient.cpp | 58 ++++---- src/bridge/BridgeClient.hpp | 12 +- src/bridge/BridgeTransport.cpp | 134 +++++++++--------- src/bridge/BridgeTransport.hpp | 74 +++++----- src/bridge/CircularBuffer.hpp | 88 +++++++++--- test/BridgeServerMock.cpp | 60 ++++---- test/BridgeServerMock.hpp | 8 +- test/TestBridgeClientMock.cpp | 56 ++++---- test/TestCircularBuffer.cpp | 40 ++++-- test/common/TestBridgeClient.cpp | 122 ++++++++--------- test/common/TestBridgeClient.hpp | 6 +- test/integration/TestBridgeClientReal.cpp | 2 +- 23 files changed, 610 insertions(+), 547 deletions(-) diff --git a/src/DriverFactory.cpp b/src/DriverFactory.cpp index 8dc0e52..3ff0809 100644 --- a/src/DriverFactory.cpp +++ b/src/DriverFactory.cpp @@ -15,8 +15,9 @@ void* HmdDriverFactory(const char* interface_name, int* return_code) { return driver.get(); } - if (return_code) + if (return_code) { *return_code = vr::VRInitError_Init_InterfaceNotFound; + } return nullptr; } diff --git a/src/IVRDevice.hpp b/src/IVRDevice.hpp index 0ed9829..8feb6a9 100644 --- a/src/IVRDevice.hpp +++ b/src/IVRDevice.hpp @@ -6,38 +6,41 @@ #include "ProtobufMessages.pb.h" namespace SlimeVRDriver { - class IVRDevice : public vr::ITrackedDeviceServerDriver { public: - /// - /// Returns the serial string for this device - /// - /// Device serial + /** + * Returns the serial string for this device. + * + * @return Device serial. + */ virtual std::string GetSerial() = 0; - /// - /// Runs any update logic for this device. - /// Called once per frame - /// + /** + * Runs any update logic for this device. + * Called once per frame. + */ virtual void Update() = 0; - /// - /// Returns the OpenVR device index - /// This should be 0 for HMDs - /// - /// OpenVR device index + /** + * Returns the OpenVR device index. + * This should be 0 for HMDs. + * + * @returns OpenVR device index. + */ virtual vr::TrackedDeviceIndex_t GetDeviceIndex() = 0; - /// - /// Returns which type of device this device is - /// - /// The type of device + /** + * Returns which type of device this device is. + * + * @returns The type of device. + */ virtual DeviceType GetDeviceType() = 0; - /// - /// Makes a default device pose - /// - /// Default initialised pose + /** + * Makes a default device pose. + * + * @returns Default initialised pose. + */ static inline vr::DriverPose_t MakeDefaultPose(bool connected = true, bool tracking = true) { vr::DriverPose_t out_pose = { 0 }; @@ -51,6 +54,21 @@ namespace SlimeVRDriver { return out_pose; } + /** + * Returns the device id. + */ + virtual int GetDeviceId() = 0; + + /** + * Updates device position from a received message. + */ + virtual void PositionMessage(messages::Position& position) = 0; + + /** + * Updates device status from a received message. + */ + virtual void StatusMessage(messages::TrackerStatus& status) = 0; + // Inherited via ITrackedDeviceServerDriver virtual vr::EVRInitError Activate(uint32_t unObjectId) = 0; virtual void Deactivate() = 0; @@ -59,10 +77,6 @@ namespace SlimeVRDriver { virtual void DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) = 0; virtual vr::DriverPose_t GetPose() = 0; - virtual int getDeviceId() = 0; - virtual void PositionMessage(messages::Position& position) = 0; - virtual void StatusMessage(messages::TrackerStatus& status) = 0; - ~IVRDevice() = default; }; }; \ No newline at end of file diff --git a/src/IVRDriver.hpp b/src/IVRDriver.hpp index d47e6b6..819823f 100644 --- a/src/IVRDriver.hpp +++ b/src/IVRDriver.hpp @@ -22,66 +22,74 @@ namespace SlimeVRDriver { class IVRDriver : protected vr::IServerTrackedDeviceProvider { public: - - /// - /// Returns all devices being managed by this driver - /// - /// All managed devices + /** + * Returns all devices being managed by this driver. + * + * @return A vector of shared pointers to all managed devices. + */ virtual std::vector> GetDevices() = 0; - /// - /// Returns all OpenVR events that happened on the current frame - /// - /// Current frame's OpenVR events + /** + * Returns all OpenVR events that happened on the current frame. + * + * @return A vector of current frame's OpenVR events. + */ virtual std::vector GetOpenVREvents() = 0; - /// - /// Returns the milliseconds between last frame and this frame - /// - /// MS between last frame and this frame + /** + * Returns the milliseconds between last frame and this frame. + * + * @return Milliseconds between last frame and this frame. + */ virtual std::chrono::milliseconds GetLastFrameTime() = 0; - /// - /// Adds a device to the driver - /// - /// Device instance - /// True on success, false on failure + /** + * Adds a device to the driver. + * + * @param device A shared pointer to the device to be added. + * @return True on success, false on failure. + */ virtual bool AddDevice(std::shared_ptr device) = 0; - /// - /// Returns the value of a settings key - /// - /// The settings key - /// Value of the key, std::monostate if the value is malformed or missing + /** + * Returns the value of a settings key. + * + * @param key The settings key + * @return Value of the key, std::monostate if the value is malformed or missing. + */ virtual SettingsValue GetSettingsValue(std::string key) = 0; - /// - /// Gets the OpenVR VRDriverInput pointer - /// - /// OpenVR VRDriverInput pointer + /** + * Gets the OpenVR VRDriverInput pointer. + * + * @return OpenVR VRDriverInput pointer. + */ virtual vr::IVRDriverInput* GetInput() = 0; - /// - /// Gets the OpenVR VRDriverProperties pointer - /// - /// OpenVR VRDriverProperties pointer + /** + * Gets the OpenVR VRDriverProperties pointer. + * + * @return OpenVR VRDriverProperties pointer. + */ virtual vr::CVRPropertyHelpers* GetProperties() = 0; - /// - /// Gets the OpenVR VRServerDriverHost pointer - /// - /// OpenVR VRServerDriverHost pointer + /** + * Gets the OpenVR VRServerDriverHost pointer. + * + * @return OpenVR VRServerDriverHost pointer. + */ virtual vr::IVRServerDriverHost* GetDriverHost() = 0; - /// - /// Gets the current UniverseTranslation - /// + /** + * Gets the current UniverseTranslation. + */ virtual std::optional GetCurrentUniverse() = 0; - /// - /// Writes a log message - /// - /// Message to log + /** + * Writes a log message. + * + * @param message Message string to log. + */ virtual void Log(std::string message) = 0; virtual inline const char* const* GetInterfaceVersions() override { @@ -89,6 +97,5 @@ namespace SlimeVRDriver { }; virtual ~IVRDriver() {} - }; } \ No newline at end of file diff --git a/src/Logger.cpp b/src/Logger.cpp index f665af3..cd5efb7 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -2,22 +2,22 @@ #include void Logger::Log(const char* format, ...) { - auto prefixedFormat = std::string(format); + auto prefixed_format = std::string(format); if (!name_.empty()) { std::ostringstream ss; ss << name_ << ": " << format; - prefixedFormat = ss.str(); + prefixed_format = ss.str(); } va_list args; va_start(args, format); va_list args2; va_copy(args2, args); - size_t len = std::vsnprintf(nullptr, 0, prefixedFormat.data(), args2); + size_t len = std::vsnprintf(nullptr, 0, prefixed_format.data(), args2); va_end(args2); std::vector buf(len + 1); - std::vsnprintf(buf.data(), buf.size(), prefixedFormat.data(), args); + std::vsnprintf(buf.data(), buf.size(), prefixed_format.data(), args); va_end(args); std::lock_guard lock(mutex_); diff --git a/src/Logger.hpp b/src/Logger.hpp index ff5c186..f1d3508 100644 --- a/src/Logger.hpp +++ b/src/Logger.hpp @@ -9,7 +9,7 @@ class Logger { public: Logger() : name_("") { } - Logger(const char* name) : name_(name) { } + Logger(const std::string& name) : name_(name) { } void Log(const char* format, ...); protected: virtual void LogImpl(const char* string) = 0; diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index 53d4743..9ff95ab 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -1,21 +1,18 @@ #include "TrackerDevice.hpp" -SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int deviceId, TrackerRole trackerRole_): - serial_(serial), trackerRole(trackerRole_), deviceId_(deviceId) +SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role): + serial_(serial), tracker_role_(tracker_role), device_id_(device_id) { this->last_pose_ = MakeDefaultPose(); - this->isSetup = false; + this->is_setup_ = false; } -std::string SlimeVRDriver::TrackerDevice::GetSerial() -{ +std::string SlimeVRDriver::TrackerDevice::GetSerial() { return this->serial_; } -void SlimeVRDriver::TrackerDevice::Update() -{ - if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) - return; +void SlimeVRDriver::TrackerDevice::Update() { + if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; // Check if this device was asked to be identified auto events = GetDriver()->GetOpenVREvents(); @@ -32,7 +29,7 @@ void SlimeVRDriver::TrackerDevice::Update() // Check if we need to keep vibrating if (this->did_vibrate_) { - this->vibrate_anim_state_ += (GetDriver()->GetLastFrameTime().count()/1000.f); + this->vibrate_anim_state_ += GetDriver()->GetLastFrameTime().count() / 1000.f; if (this->vibrate_anim_state_ > 1.0f) { this->did_vibrate_ = false; this->vibrate_anim_state_ = 0.0f; @@ -40,10 +37,8 @@ void SlimeVRDriver::TrackerDevice::Update() } } -void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) -{ - if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) - return; +void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) { + if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; // Setup pose for this frame auto pose = this->last_pose_; @@ -79,25 +74,23 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) this->last_pose_ = pose; } -void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) -{ +void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) { auto pose = this->last_pose_; - switch (status.status()) - { - case messages::TrackerStatus_Status_OK: - pose.deviceIsConnected = true; - pose.poseIsValid = true; - break; - case messages::TrackerStatus_Status_DISCONNECTED: - pose.deviceIsConnected = false; - pose.poseIsValid = false; - break; - default: - case messages::TrackerStatus_Status_ERROR: - case messages::TrackerStatus_Status_BUSY: - pose.deviceIsConnected = true; - pose.poseIsValid = false; - break; + switch (status.status()) { + case messages::TrackerStatus_Status_OK: + pose.deviceIsConnected = true; + pose.poseIsValid = true; + break; + case messages::TrackerStatus_Status_DISCONNECTED: + pose.deviceIsConnected = false; + pose.poseIsValid = false; + break; + case messages::TrackerStatus_Status_ERROR: + case messages::TrackerStatus_Status_BUSY: + default: + pose.deviceIsConnected = true; + pose.poseIsValid = false; + break; } // TODO: send position/rotation of 0 instead of last pose? @@ -107,18 +100,15 @@ void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status // TODO: update this->last_pose_? } -DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() -{ +DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() { return DeviceType::TRACKER; } -vr::TrackedDeviceIndex_t SlimeVRDriver::TrackerDevice::GetDeviceIndex() -{ +vr::TrackedDeviceIndex_t SlimeVRDriver::TrackerDevice::GetDeviceIndex() { return this->device_index_; } -vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) -{ +vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) { this->device_index_ = unObjectId; GetDriver()->Log("Activating tracker " + this->serial_); @@ -152,43 +142,40 @@ vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_NamedIconPathDeviceAlertLow_String, "{slimevr}/icons/tracker_status_ready_low.b4bfb144.png"); // Automatically select vive tracker roles and set hints for games that need it (Beat Saber avatar mod, for example) - auto roleHint = getViveRoleHint(trackerRole); - if (roleHint != "") - GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, roleHint.c_str()); + auto role_hint = GetViveRoleHint(tracker_role_); + if (role_hint != "") { + GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, role_hint.c_str()); + } - auto role = getViveRole(trackerRole); - if (role != "") + auto role = GetViveRole(tracker_role_); + if (role != "") { vr::VRSettings()->SetString(vr::k_pch_Trackers_Section, ("/devices/slimevr/" + this->serial_).c_str(), role.c_str()); + } return vr::EVRInitError::VRInitError_None; } -void SlimeVRDriver::TrackerDevice::Deactivate() -{ +void SlimeVRDriver::TrackerDevice::Deactivate() { this->device_index_ = vr::k_unTrackedDeviceIndexInvalid; } -void SlimeVRDriver::TrackerDevice::EnterStandby() -{ +void SlimeVRDriver::TrackerDevice::EnterStandby() { } -void* SlimeVRDriver::TrackerDevice::GetComponent(const char* pchComponentNameAndVersion) -{ +void* SlimeVRDriver::TrackerDevice::GetComponent(const char* pchComponentNameAndVersion) { return nullptr; } -void SlimeVRDriver::TrackerDevice::DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) -{ - if (unResponseBufferSize >= 1) +void SlimeVRDriver::TrackerDevice::DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) { + if (unResponseBufferSize >= 1) { pchResponseBuffer[0] = 0; + } } -vr::DriverPose_t SlimeVRDriver::TrackerDevice::GetPose() -{ +vr::DriverPose_t SlimeVRDriver::TrackerDevice::GetPose() { return last_pose_; } -int SlimeVRDriver::TrackerDevice::getDeviceId() -{ - return deviceId_; +int SlimeVRDriver::TrackerDevice::GetDeviceId() { + return device_id_; } diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp index 932627e..d3d474e 100644 --- a/src/TrackerDevice.hpp +++ b/src/TrackerDevice.hpp @@ -17,8 +17,7 @@ namespace SlimeVRDriver { class TrackerDevice : public IVRDevice { public: - - TrackerDevice(std::string serial, int deviceId, TrackerRole trackerRole); + TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role); ~TrackerDevice() = default; // Inherited via IVRDevice @@ -26,26 +25,25 @@ namespace SlimeVRDriver { virtual void Update() override; virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override; virtual DeviceType GetDeviceType() override; + virtual int GetDeviceId() override; + virtual void PositionMessage(messages::Position &position) override; + virtual void StatusMessage(messages::TrackerStatus &status) override; + // Inherited via ITrackedDeviceServerDriver virtual vr::EVRInitError Activate(uint32_t unObjectId) override; virtual void Deactivate() override; virtual void EnterStandby() override; virtual void* GetComponent(const char* pchComponentNameAndVersion) override; virtual void DebugRequest(const char* pchRequest, char* pchResponseBuffer, uint32_t unResponseBufferSize) override; virtual vr::DriverPose_t GetPose() override; - virtual int getDeviceId() override; - virtual void PositionMessage(messages::Position &position) override; - virtual void StatusMessage(messages::TrackerStatus &status) override; + private: vr::TrackedDeviceIndex_t device_index_ = vr::k_unTrackedDeviceIndexInvalid; std::string serial_; - bool isSetup; + bool is_setup_; - char buffer[1024]; - uint32_t dwWritten; - uint32_t dwRead; - int deviceId_; - TrackerRole trackerRole; + int device_id_; + TrackerRole tracker_role_; vr::DriverPose_t last_pose_ = IVRDevice::MakeDefaultPose(); @@ -53,9 +51,7 @@ namespace SlimeVRDriver { float vibrate_anim_state_ = 0.f; vr::VRInputComponentHandle_t haptic_component_ = 0; - vr::VRInputComponentHandle_t system_click_component_ = 0; vr::VRInputComponentHandle_t system_touch_component_ = 0; - }; }; \ No newline at end of file diff --git a/src/TrackerRole.cpp b/src/TrackerRole.cpp index 2cb4b4a..865a5e6 100644 --- a/src/TrackerRole.cpp +++ b/src/TrackerRole.cpp @@ -22,9 +22,8 @@ */ #include "TrackerRole.hpp" - -std::string getViveRoleHint(TrackerRole role) { - switch(role) { +std::string GetViveRoleHint(TrackerRole role) { + switch (role) { case LEFT_CONTROLLER: case RIGHT_CONTROLLER: case GENERIC_CONTROLLER: @@ -55,13 +54,12 @@ std::string getViveRoleHint(TrackerRole role) { return "vive_tracker_camera"; case KEYBOARD: return "vive_tracker_keyboard"; - } return ""; } -std::string getViveRole(TrackerRole role) { - switch(role) { +std::string GetViveRole(TrackerRole role) { + switch (role) { case LEFT_CONTROLLER: case RIGHT_CONTROLLER: case GENERIC_CONTROLLER: @@ -92,13 +90,12 @@ std::string getViveRole(TrackerRole role) { return "TrackerRole_Camera"; case KEYBOARD: return "TrackerRole_Keyboard"; - } return ""; } -DeviceType getDeviceType(TrackerRole role) { - switch(role) { +DeviceType GetDeviceType(TrackerRole role) { + switch (role) { case LEFT_CONTROLLER: case RIGHT_CONTROLLER: case GENERIC_CONTROLLER: diff --git a/src/TrackerRole.hpp b/src/TrackerRole.hpp index c0854cf..d5f9e18 100644 --- a/src/TrackerRole.hpp +++ b/src/TrackerRole.hpp @@ -50,8 +50,8 @@ enum TrackerRole { GENERIC_CONTROLLER = 21, }; -std::string getViveRoleHint(TrackerRole role); +std::string GetViveRoleHint(TrackerRole role); -std::string getViveRole(TrackerRole role); +std::string GetViveRole(TrackerRole role); -DeviceType getDeviceType(TrackerRole role); +DeviceType GetDeviceType(TrackerRole role); diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 9b3f856..4cbc856 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -11,30 +11,30 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont return init_error; } - logger->Log("Activating SlimeVR Driver..."); + logger_->Log("Activating SlimeVR Driver..."); try { auto json = simdjson::padded_string::load(GetVRPathRegistryFilename()); // load VR Path Registry - simdjson::ondemand::document doc = json_parser.iterate(json); + simdjson::ondemand::document doc = json_parser_.iterate(json); auto path = std::string { doc.get_object()["config"].at(0).get_string().value() }; default_chap_path_ = GetDefaultChaperoneFromConfigPath(path); } catch (simdjson::simdjson_error& e) { - logger->Log("Error getting VR Config path, continuing: %s", e.error()); + logger_->Log("Error getting VR Config path, continuing: %s", e.error()); } - logger->Log("SlimeVR Driver Loaded Successfully"); + logger_->Log("SlimeVR Driver Loaded Successfully"); - bridge = std::make_shared( + bridge_ = std::make_shared( std::static_pointer_cast(std::make_shared("Bridge")), std::bind(&SlimeVRDriver::VRDriver::OnBridgeMessage, this, std::placeholders::_1) ); - bridge->start(); + bridge_->Start(); return vr::VRInitError_None; } void SlimeVRDriver::VRDriver::Cleanup() { - bridge->stop(); + bridge_->Stop(); } void SlimeVRDriver::VRDriver::RunFrame() { @@ -43,8 +43,7 @@ void SlimeVRDriver::VRDriver::RunFrame() { // Collect events vr::VREvent_t event; std::vector events; - while (vr::VRServerDriverHost()->PollNextEvent(&event, sizeof(event))) - { + while (vr::VRServerDriverHost()->PollNextEvent(&event, sizeof(event))) { events.push_back(event); } this->openvr_events_ = std::move(events); @@ -57,48 +56,49 @@ void SlimeVRDriver::VRDriver::RunFrame() { // Update devices { std::lock_guard lock(devices_mutex_); - for (auto& device : this->devices_) + for (auto& device : this->devices_) { device->Update(); + } } - if (!bridge->isConnected()) { + if (!bridge_->IsConnected()) { // If bridge not connected, assume we need to resend hmd tracker add message - sentHmdAddMessage = false; + send_hmd_add_message_ = false; return; } messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); - if (!sentHmdAddMessage) { + if (!send_hmd_add_message_) { // Send add message for HMD - messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_added(trackerAdded); - trackerAdded->set_tracker_id(0); - trackerAdded->set_tracker_role(TrackerRole::HMD); - trackerAdded->set_tracker_serial("HMD"); - trackerAdded->set_tracker_name("HMD"); - bridge->sendBridgeMessage(*message); - - messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_status(trackerStatus); - trackerStatus->set_tracker_id(0); - trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - bridge->sendBridgeMessage(*message); - - sentHmdAddMessage = true; - logger->Log("Sent HMD hello message"); + messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_added(tracker_added); + tracker_added->set_tracker_id(0); + tracker_added->set_tracker_role(TrackerRole::HMD); + tracker_added->set_tracker_serial("HMD"); + tracker_added->set_tracker_name("HMD"); + bridge_->SendBridgeMessage(*message); + + messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_status(tracker_status); + tracker_status->set_tracker_id(0); + tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + bridge_->SendBridgeMessage(*message); + + send_hmd_add_message_ = true; + logger_->Log("Sent HMD hello message"); } - vr::PropertyContainerHandle_t hmdPropContainer = + vr::PropertyContainerHandle_t hmd_prop_container = vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd); - uint64_t universe = vr::VRProperties()->GetUint64Property(hmdPropContainer, vr::Prop_CurrentUniverseId_Uint64); - if (!current_universe.has_value() || current_universe.value().first != universe) { - auto res = search_universes(universe); - if (res.has_value()) { - current_universe.emplace(universe, res.value()); + uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64); + if (!current_universe_.has_value() || current_universe_.value().first != universe) { + auto result = SearchUniverses(universe); + if (result.has_value()) { + current_universe_.emplace(universe, result.value()); } else { - logger->Log("Failed to find current universe!"); + logger_->Log("Failed to find current universe!"); } } @@ -108,8 +108,8 @@ void SlimeVRDriver::VRDriver::RunFrame() { vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); - if (current_universe.has_value()) { - auto trans = current_universe.value().second; + if (current_universe_.has_value()) { + auto trans = current_universe_.value().second; pos.v[0] += trans.translation.v[0]; pos.v[1] += trans.translation.v[1]; pos.v[2] += trans.translation.v[2]; @@ -138,19 +138,19 @@ void SlimeVRDriver::VRDriver::RunFrame() { pos.v[2] = pos_z; } - messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_position(hmdPosition); - hmdPosition->set_tracker_id(0); - hmdPosition->set_data_source(messages::Position_DataSource_FULL); - hmdPosition->set_x(pos.v[0]); - hmdPosition->set_y(pos.v[1]); - hmdPosition->set_z(pos.v[2]); - hmdPosition->set_qx((float) q.x); - hmdPosition->set_qy((float) q.y); - hmdPosition->set_qz((float) q.z); - hmdPosition->set_qw((float) q.w); - - bridge->sendBridgeMessage(*message); + messages::Position* hmd_position = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_position(hmd_position); + hmd_position->set_tracker_id(0); + hmd_position->set_data_source(messages::Position_DataSource_FULL); + hmd_position->set_x(pos.v[0]); + hmd_position->set_y(pos.v[1]); + hmd_position->set_z(pos.v[2]); + hmd_position->set_qx((float) q.x); + hmd_position->set_qy((float) q.y); + hmd_position->set_qz((float) q.z); + hmd_position->set_qw((float) q.w); + + bridge_->SendBridgeMessage(*message); } void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& message) { @@ -162,22 +162,22 @@ void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& m std::lock_guard lock(devices_mutex_); if (message.has_tracker_added()) { messages::TrackerAdded ta = message.tracker_added(); - switch(getDeviceType(static_cast(ta.tracker_role()))) { + switch(GetDeviceType(static_cast(ta.tracker_role()))) { case DeviceType::TRACKER: this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); - logger->Log("New tracker device added %s (id %i)", ta.tracker_serial().c_str(), ta.tracker_id()); + logger_->Log("New tracker device added %s (id %i)", ta.tracker_serial().c_str(), ta.tracker_id()); break; } } else if (message.has_position()) { messages::Position pos = message.position(); - auto device = this->devices_by_id.find(pos.tracker_id()); - if (device != this->devices_by_id.end()) { + auto device = this->devices_by_id_.find(pos.tracker_id()); + if (device != this->devices_by_id_.end()) { device->second->PositionMessage(pos); } } else if (message.has_tracker_status()) { messages::TrackerStatus status = message.tracker_status(); - auto device = this->devices_by_id.find(status.tracker_id()); - if (device != this->devices_by_id.end()) { + auto device = this->devices_by_id_.find(status.tracker_id()); + if (device != this->devices_by_id_.end()) { device->second->StatusMessage(status); } } @@ -187,9 +187,11 @@ bool SlimeVRDriver::VRDriver::ShouldBlockStandbyMode() { return false; } -void SlimeVRDriver::VRDriver::EnterStandby() { } +void SlimeVRDriver::VRDriver::EnterStandby() { +} -void SlimeVRDriver::VRDriver::LeaveStandby() { } +void SlimeVRDriver::VRDriver::LeaveStandby() { +} std::vector> SlimeVRDriver::VRDriver::GetDevices() { std::lock_guard lock(devices_mutex_); @@ -226,15 +228,15 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get()); if (result) { this->devices_.push_back(device); - this->devices_by_id[device->getDeviceId()] = device; - this->devices_by_serial[device->GetSerial()] = device; + this->devices_by_id_[device->GetDeviceId()] = device; + this->devices_by_serial_[device->GetSerial()] = device; } else { - std::shared_ptr oldDevice = this->devices_by_serial[device->GetSerial()]; - if (oldDevice->getDeviceId() != device->getDeviceId()) { - this->devices_by_id[device->getDeviceId()] = oldDevice; - logger->Log("Device overridden from id %i to %i for serial %s", oldDevice->getDeviceId(), device->getDeviceId(), device->GetSerial()); + std::shared_ptr oldDevice = this->devices_by_serial_[device->GetSerial()]; + if (oldDevice->GetDeviceId() != device->GetDeviceId()) { + this->devices_by_id_[device->GetDeviceId()] = oldDevice; + logger_->Log("Device overridden from id %i to %i for serial %s", oldDevice->GetDeviceId(), device->GetDeviceId(), device->GetSerial()); } else { - logger->Log("Device readded id %i, serial %s", device->getDeviceId(), device->GetSerial().c_str()); + logger_->Log("Device readded id %i, serial %s", device->GetDeviceId(), device->GetSerial().c_str()); } } return result; @@ -318,18 +320,18 @@ SlimeVRDriver::UniverseTranslation SlimeVRDriver::UniverseTranslation::parse(sim if (iii > 2) { break; // TODO: 4 components in a translation vector? should this be an error? } - res.translation.v[iii] = component.get_double(); + res.translation.v[iii] = static_cast(component.get_double()); iii += 1; } - res.yaw = obj["yaw"].get_double(); + res.yaw = static_cast(obj["yaw"].get_double()); return res; } -std::optional SlimeVRDriver::VRDriver::search_universe(std::string path, uint64_t target) { +std::optional SlimeVRDriver::VRDriver::SearchUniverse(std::string path, uint64_t target) { try { auto json = simdjson::padded_string::load(path); // load VR Path Registry - simdjson::ondemand::document doc = json_parser.iterate(json); + simdjson::ondemand::document doc = json_parser_.iterate(json); for (simdjson::ondemand::object uni: doc["universes"]) { // TODO: universeID comes after the translation, would it be faster to unconditionally parse the translation? @@ -349,33 +351,33 @@ std::optional SlimeVRDriver::VRDriver::searc } } } catch (simdjson::simdjson_error& e) { - logger->Log("Error getting universes from %s: %s", path.c_str(), e.what()); + logger_->Log("Error getting universes from %s: %s", path.c_str(), e.what()); return std::nullopt; } return std::nullopt; } -std::optional SlimeVRDriver::VRDriver::search_universes(uint64_t target) { +std::optional SlimeVRDriver::VRDriver::SearchUniverses(uint64_t target) { auto driver_chap_path = vr::VRProperties()->GetStringProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DriverProvidedChaperonePath_String); if (driver_chap_path != "") { - auto driver_res = search_universe(driver_chap_path, target); + auto driver_res = SearchUniverse(driver_chap_path, target); if (driver_res.has_value()) { return driver_res.value(); } } if (default_chap_path_.has_value()) { - return search_universe(default_chap_path_.value(), target); + return SearchUniverse(default_chap_path_.value(), target); } return std::nullopt; } std::optional SlimeVRDriver::VRDriver::GetCurrentUniverse() { - if (current_universe.has_value()) { - return current_universe.value().second; - } else { - return std::nullopt; + if (current_universe_.has_value()) { + return current_universe_.value().second; } + + return std::nullopt; } \ No newline at end of file diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index c380fb6..dc457c5 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -18,7 +18,6 @@ namespace SlimeVRDriver { class VRDriver : public IVRDriver { public: - // Inherited via IVRDriver virtual std::vector> GetDevices() override; virtual std::vector GetOpenVREvents() override; @@ -26,7 +25,7 @@ namespace SlimeVRDriver { virtual bool AddDevice(std::shared_ptr device) override; virtual SettingsValue GetSettingsValue(std::string key) override; virtual void Log(std::string message) override { - logger->Log("%s", message.c_str()); + logger_->Log("%s", message.c_str()); }; virtual vr::IVRDriverInput* GetInput() override; @@ -47,12 +46,13 @@ namespace SlimeVRDriver { virtual std::optional GetCurrentUniverse() override; private: - std::shared_ptr logger = std::make_shared(); + std::shared_ptr bridge_ = nullptr; + std::shared_ptr logger_ = std::make_shared(); std::mutex devices_mutex_; std::vector> devices_; std::vector openvr_events_; - std::map> devices_by_id; - std::map> devices_by_serial; + std::map> devices_by_id_; + std::map> devices_by_serial_; std::chrono::milliseconds frame_timing_ = std::chrono::milliseconds(16); std::chrono::steady_clock::time_point last_frame_time_ = std::chrono::steady_clock::now(); std::string settings_key_ = "driver_slimevr"; @@ -60,17 +60,14 @@ namespace SlimeVRDriver { vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t &matrix); vr::HmdVector3_t GetPosition(vr::HmdMatrix34_t &matrix); - bool sentHmdAddMessage = false; + bool send_hmd_add_message_ = false; - simdjson::ondemand::parser json_parser; + simdjson::ondemand::parser json_parser_; std::optional default_chap_path_ = std::nullopt; //std::map universes; - std::optional> current_universe = std::nullopt; - - std::optional search_universe(std::string path, uint64_t target); - std::optional search_universes(uint64_t target); - - std::shared_ptr bridge; + std::optional> current_universe_ = std::nullopt; + std::optional SearchUniverse(std::string path, uint64_t target); + std::optional SearchUniverses(uint64_t target); }; }; \ No newline at end of file diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index b8f2fe6..1601442 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -24,49 +24,49 @@ using namespace std::literals::chrono_literals; -void BridgeClient::createConnection() { - logger->Log("connecting"); - resetBuffers(); +void BridgeClient::CreateConnection() { + logger_->Log("connecting"); + ResetBuffers(); /* ipc = false -> pipe will be used for handle passing between processes? no */ - connectionHandle = getLoop()->resource(false); + connection_handle_ = GetLoop()->resource(false); - connectionHandle->on([this](const uvw::ConnectEvent&, uvw::PipeHandle&) { - connectionHandle->read(); - logger->Log("connected"); - connected = true; + connection_handle_->on([this](const uvw::ConnectEvent&, uvw::PipeHandle&) { + connection_handle_->read(); + logger_->Log("connected"); + connected_ = true; }); - connectionHandle->on([this](const uvw::EndEvent&, uvw::PipeHandle&) { - logger->Log("disconnected"); - reconnect(); + connection_handle_->on([this](const uvw::EndEvent&, uvw::PipeHandle&) { + logger_->Log("disconnected"); + Reconnect(); }); - connectionHandle->on([this](const uvw::DataEvent& event, uvw::PipeHandle&) { - onRecv(event); + connection_handle_->on([this](const uvw::DataEvent& event, uvw::PipeHandle&) { + OnRecv(event); }); - connectionHandle->on([this](const uvw::ErrorEvent& event, uvw::PipeHandle&) { - logger->Log("Pipe error: %s", event.what()); - reconnect(); + connection_handle_->on([this](const uvw::ErrorEvent& event, uvw::PipeHandle&) { + logger_->Log("Pipe error: %s", event.what()); + Reconnect(); }); - connectionHandle->connect(path); + connection_handle_->connect(path_); } -void BridgeClient::resetConnection() { - reconnect(); +void BridgeClient::ResetConnection() { + Reconnect(); } -void BridgeClient::reconnect() { - closeConnectionHandles(); - reconnectTimeout = getLoop()->resource(); - reconnectTimeout->start(1000ms, 0ms); - reconnectTimeout->once([this](const uvw::TimerEvent&, uvw::TimerHandle& handle) { - createConnection(); +void BridgeClient::Reconnect() { + CloseConnectionHandles(); + reconnect_timeout_ = GetLoop()->resource(); + reconnect_timeout_->start(1000ms, 0ms); + reconnect_timeout_->once([this](const uvw::TimerEvent&, uvw::TimerHandle& handle) { + CreateConnection(); handle.close(); }); } -void BridgeClient::closeConnectionHandles() { - if (connectionHandle) connectionHandle->close(); - if (reconnectTimeout) reconnectTimeout->close(); - connected = false; +void BridgeClient::CloseConnectionHandles() { + if (connection_handle_) connection_handle_->close(); + if (reconnect_timeout_) reconnect_timeout_->close(); + connected_ = false; } diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp index 020d923..832735f 100644 --- a/src/bridge/BridgeClient.hpp +++ b/src/bridge/BridgeClient.hpp @@ -37,17 +37,17 @@ * from the event loop thread with the message as a parameter. * * @param logger A shared pointer to an Logger object to log messages from the transport. - * @param onMessageReceived A function to be called from event loop thread when a message is received and parsed from the pipe. + * @param on_message_received A function to be called from event loop thread when a message is received and parsed from the pipe. */ class BridgeClient: public BridgeTransport { public: using BridgeTransport::BridgeTransport; private: - void createConnection() override; - void resetConnection() override; - void closeConnectionHandles() override; - void reconnect(); + void CreateConnection() override; + void ResetConnection() override; + void CloseConnectionHandles() override; + void Reconnect(); - std::shared_ptr reconnectTimeout; + std::shared_ptr reconnect_timeout_; }; \ No newline at end of file diff --git a/src/bridge/BridgeTransport.cpp b/src/bridge/BridgeTransport.cpp index a7ec620..9b225fa 100644 --- a/src/bridge/BridgeTransport.cpp +++ b/src/bridge/BridgeTransport.cpp @@ -22,117 +22,117 @@ */ #include "BridgeTransport.hpp" -void BridgeTransport::start() { - thread = std::make_unique(&BridgeTransport::runThread, this); +void BridgeTransport::Start() { + thread_ = std::make_unique(&BridgeTransport::RunThread, this); } -void BridgeTransport::stop() { - if (!thread || !thread->joinable()) return; - stopAsync(); - logger->Log("stopping"); - thread->join(); - thread.reset(); +void BridgeTransport::Stop() { + if (!thread_ || !thread_->joinable()) return; + StopAsync(); + logger_->Log("stopping"); + thread_->join(); + thread_.reset(); } -void BridgeTransport::stopAsync() { - if (!stopSignalHandle || stopSignalHandle->closing()) return; - stopSignalHandle->send(); +void BridgeTransport::StopAsync() { + if (!stop_signal_handle_ || stop_signal_handle_->closing()) return; + stop_signal_handle_->send(); } -void BridgeTransport::runThread() { - logger->Log("thread started"); - loop = uvw::Loop::create(); - stopSignalHandle = getLoop()->resource(); - writeSignalHandle = getLoop()->resource(); - - stopSignalHandle->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { - logger->Log("closing handles"); - closeConnectionHandles(); - writeSignalHandle->close(); - stopSignalHandle->close(); +void BridgeTransport::RunThread() { + logger_->Log("thread started"); + loop_ = uvw::Loop::create(); + stop_signal_handle_ = GetLoop()->resource(); + write_signal_handle_ = GetLoop()->resource(); + + stop_signal_handle_->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + logger_->Log("closing handles"); + CloseConnectionHandles(); + write_signal_handle_->close(); + stop_signal_handle_->close(); }); - writeSignalHandle->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { - sendWrites(); + write_signal_handle_->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + SendWrites(); }); - createConnection(); - getLoop()->run(); - getLoop()->close(); - logger->Log("thread exited"); + CreateConnection(); + GetLoop()->run(); + GetLoop()->close(); + logger_->Log("thread exited"); } -void BridgeTransport::resetBuffers() { - recvBuf.clear(); - sendBuf.clear(); +void BridgeTransport::ResetBuffers() { + recv_buf_.Clear(); + send_buf_.Clear(); } -void BridgeTransport::onRecv(const uvw::DataEvent& event) { - if (!recvBuf.push(event.data.get(), event.length)) { - logger->Log("recvBuf.push %i failed", event.length); - resetConnection(); +void BridgeTransport::OnRecv(const uvw::DataEvent& event) { + if (!recv_buf_.Push(event.data.get(), event.length)) { + logger_->Log("recv_buf_.Push %i failed", event.length); + ResetConnection(); return; } size_t available; - while (available = recvBuf.bytes_available()) { + while (available = recv_buf_.BytesAvailable()) { if (available < 4) return; - char lenBuf[4]; - recvBuf.peek(lenBuf, 4); - uint32_t size = LE32_TO_NATIVE(*reinterpret_cast(lenBuf)); + char len_buf[4]; + recv_buf_.Peek(len_buf, 4); + uint32_t size = LE32_TO_NATIVE(*reinterpret_cast(len_buf)); if (size > VRBRIDGE_MAX_MESSAGE_SIZE) { - logger->Log("message size overflow"); - resetConnection(); + logger_->Log("message size overflow"); + ResetConnection(); return; } - auto unwrappedSize = size - 4; - if (available < unwrappedSize) return; + auto unwrapped_size = size - 4; + if (available < unwrapped_size) return; - auto messageBuf = std::make_unique(size); - if (!recvBuf.skip(4) || !recvBuf.pop(messageBuf.get(), unwrappedSize)) { - logger->Log("recvBuf.pop %i failed", size); - resetConnection(); + auto message_buf = std::make_unique(size); + if (!recv_buf_.Skip(4) || !recv_buf_.Pop(message_buf.get(), unwrapped_size)) { + logger_->Log("recv_buf_.Pop %i failed", size); + ResetConnection(); return; } - messages::ProtobufMessage receivedMessage; - if (receivedMessage.ParseFromArray(messageBuf.get(), unwrappedSize)) { - messageCallback(receivedMessage); + messages::ProtobufMessage message; + if (message.ParseFromArray(message_buf.get(), unwrapped_size)) { + message_callback_(message); } else { - logger->Log("receivedMessage.ParseFromArray failed"); - resetConnection(); + logger_->Log("receivedMessage.ParseFromArray failed"); + ResetConnection(); return; } } } -void BridgeTransport::sendBridgeMessage(const messages::ProtobufMessage& message) { - if (!isConnected()) return; +void BridgeTransport::SendBridgeMessage(const messages::ProtobufMessage& message) { + if (!IsConnected()) return; uint32_t size = static_cast(message.ByteSizeLong()); - uint32_t wrappedSize = size + 4; + uint32_t wrapped_size = size + 4; - auto messageBuf = std::make_unique(wrappedSize); - *reinterpret_cast(messageBuf.get()) = NATIVE_TO_LE32(wrappedSize); - message.SerializeToArray(messageBuf.get() + 4, size); - if (!sendBuf.push(messageBuf.get(), wrappedSize)) { - resetConnection(); + auto message_buf = std::make_unique(wrapped_size); + *reinterpret_cast(message_buf.get()) = NATIVE_TO_LE32(wrapped_size); + message.SerializeToArray(message_buf.get() + 4, size); + if (!send_buf_.Push(message_buf.get(), wrapped_size)) { + ResetConnection(); return; } - writeSignalHandle->send(); + write_signal_handle_->send(); } -void BridgeTransport::sendWrites() { - if (!isConnected()) return; +void BridgeTransport::SendWrites() { + if (!IsConnected()) return; - auto available = sendBuf.bytes_available(); + auto available = send_buf_.BytesAvailable(); if (!available) return; - auto writeBuf = std::make_unique(available); - sendBuf.pop(writeBuf.get(), available); - connectionHandle->write(writeBuf.get(), static_cast(available)); + auto write_buf = std::make_unique(available); + send_buf_.Pop(write_buf.get(), available); + connection_handle_->write(write_buf.get(), static_cast(available)); } \ No newline at end of file diff --git a/src/bridge/BridgeTransport.hpp b/src/bridge/BridgeTransport.hpp index 4b8056b..5144416 100644 --- a/src/bridge/BridgeTransport.hpp +++ b/src/bridge/BridgeTransport.hpp @@ -52,7 +52,7 @@ namespace fs = std::filesystem; #define UNIX_TMP_DIR "/tmp" #define UNIX_SOCKET_NAME "SlimeVRDriver" -static std::string getBridgePath() { +static std::string GetBridgePath() { #ifdef __linux__ if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { const fs::path xdg_runtime = ptr; @@ -77,20 +77,20 @@ static std::string getBridgePath() { * from the event loop thread with the message as a parameter. * * @param logger A shared pointer to an Logger object to log messages from the transport. - * @param onMessageReceived A function to be called from event loop thread when a message is received and parsed from the pipe. + * @param on_message_received A function to be called from event loop thread when a message is received and parsed from the pipe. */ class BridgeTransport { public: - BridgeTransport(std::shared_ptr _logger, std::function onMessageReceived) : - logger(_logger), - messageCallback(onMessageReceived), - path(getBridgePath()), - sendBuf(VRBRIDGE_BUFFERS_SIZE), - recvBuf(VRBRIDGE_BUFFERS_SIZE) + BridgeTransport(std::shared_ptr logger, std::function on_message_received) : + logger_(logger), + message_callback_(on_message_received), + path_(GetBridgePath()), + send_buf_(VRBRIDGE_BUFFERS_SIZE), + recv_buf_(VRBRIDGE_BUFFERS_SIZE) { } ~BridgeTransport() { - stop(); + Stop(); } /** @@ -98,21 +98,21 @@ class BridgeTransport { * * Connects and automatic reconnects with a timeout are implemented internally. */ - void start(); + void Start(); /** * @brief Stops the channel by stopping the libuv event loop and closing the connection handles. * * Blocks until the event loop is stopped and the connection handles are closed. */ - void stop(); + void Stop(); /** * @brief Stops the channel asynchronously by sending a signal to the libuv event loop to stop and returning immediately. * - * The `stop()` function calls this method. + * The `Stop()` function calls this method. */ - void stopAsync(); + void StopAsync(); /** * @brief Sends a message over the channel. @@ -121,41 +121,41 @@ class BridgeTransport { * * @param message The message to send. */ - void sendBridgeMessage(const messages::ProtobufMessage& message); + void SendBridgeMessage(const messages::ProtobufMessage& message); /** * @brief Checks if the channel is connected. * * @return true if the channel is connected, false otherwise. */ - bool isConnected() { - return connected; + bool IsConnected() { + return connected_; }; protected: - virtual void createConnection() = 0; - virtual void resetConnection() = 0; - virtual void closeConnectionHandles() = 0; - void resetBuffers(); - void onRecv(const uvw::DataEvent& event); - auto getLoop() { - return loop; + virtual void CreateConnection() = 0; + virtual void ResetConnection() = 0; + virtual void CloseConnectionHandles() = 0; + void ResetBuffers(); + void OnRecv(const uvw::DataEvent& event); + auto GetLoop() { + return loop_; } - std::shared_ptr connectionHandle = nullptr; - std::shared_ptr logger; - const std::string path; - std::atomic connected = false; + std::shared_ptr logger_; + const std::string path_; + std::atomic connected_ = false; + std::shared_ptr connection_handle_ = nullptr; private: - void runThread(); - void sendWrites(); - - CircularBuffer sendBuf; - CircularBuffer recvBuf; - std::shared_ptr stopSignalHandle = nullptr; - std::shared_ptr writeSignalHandle = nullptr; - std::unique_ptr thread = nullptr; - std::shared_ptr loop = nullptr; - const std::function messageCallback; + void RunThread(); + void SendWrites(); + + CircularBuffer send_buf_; + CircularBuffer recv_buf_; + std::shared_ptr stop_signal_handle_ = nullptr; + std::shared_ptr write_signal_handle_ = nullptr; + std::unique_ptr thread_ = nullptr; + std::shared_ptr loop_ = nullptr; + const std::function message_callback_; }; \ No newline at end of file diff --git a/src/bridge/CircularBuffer.hpp b/src/bridge/CircularBuffer.hpp index b3dcb78..4994887 100644 --- a/src/bridge/CircularBuffer.hpp +++ b/src/bridge/CircularBuffer.hpp @@ -25,19 +25,36 @@ #include #include #include +#include #include +/** + * A fixed-size queue using contiguous memory ONLY for a single producer and a single consumer (SPSC). + * + * @param size Size of the queue in bytes. + */ class CircularBuffer { public: + /** + * Constructs a fixed-size queue using contiguous memory. + * + * @param size Size of the queue in bytes. + */ CircularBuffer(size_t size) : size_(size), - buffer_(std::make_unique(size)), - head_(0), tail_(0), count_(0) + buffer_(std::make_unique(size)) { } ~CircularBuffer() = default; - bool push(const char* data, size_t size) { - if (size > bytes_free()) return false; + /** + * Pushes data into the queue. + * + * @param data A pointer to the data to be pushed. + * @param size Number of bytes to push. + * @return True if the data was successfully pushed, false if the queue is full. + */ + bool Push(const char* data, size_t size) { + if (size > BytesFree()) return false; size_t size1 = std::min(size, size_ - (head_ % size_)); size_t size2 = size - size1; std::memcpy(buffer_.get() + (head_ % size_), data, size1); @@ -47,8 +64,15 @@ class CircularBuffer { return true; } - bool pop(char* data, size_t size) { - if (size > bytes_available()) return false; + /** + * Pops data from the queue. + * + * @param data A pointer to the location where the data should be stored. + * @param size Number of bytes to pop. + * @return True if the data was successfully popped, false if there is not enough data. + */ + bool Pop(char* data, size_t size) { + if (size > BytesAvailable()) return false; size_t size1 = std::min(size, size_ - (tail_ % size_)); size_t size2 = size - size1; std::memcpy(data, buffer_.get() + (tail_ % size_ ), size1); @@ -58,9 +82,16 @@ class CircularBuffer { return true; } - size_t peek(char* data, size_t size) { - if (size > bytes_available()) return false; - size_t available = bytes_available(); + /** + * Copies data from the queue into the given data pointer, without removing it. + * + * @param data A pointer to the location where the data should be copied to. + * @param size Number of bytes to peek. + * @return Number of bytes actually copied, 0 if there is not enough data. + */ + size_t Peek(char* data, size_t size) { + size_t available = BytesAvailable(); + if (size > available) return 0; size_t size1 = std::min(size, size_ - (tail_ % size_)); size_t size2 = std::min(size - size1, available - size1); std::memcpy(data, buffer_.get() + (tail_ % size_), size1); @@ -68,31 +99,50 @@ class CircularBuffer { return size1 + size2; } - bool skip(size_t n) { - if (n > bytes_available()) return false; + /** + * Skips n bytes in the queue. + * + * @param n Number of bytes to skip. + * @return True if n bytes were successfully skipped, false if n is greater than the number of bytes available in the queue. + */ + bool Skip(size_t n) { + if (n > BytesAvailable()) return false; tail_ += n; count_ -= n; return true; } - void clear() { + /** + * Clears the queue. + */ + void Clear() { head_ = 0; tail_ = 0; count_ = 0; } - size_t bytes_available() const { + /** + * Returns the number of bytes available in the queue. + * + * @return Number of bytes available in the queue. + */ + size_t BytesAvailable() const { return count_; } - size_t bytes_free() const { - return size_ - bytes_available(); + /** + * Returns the number of free bytes in the queue. + * + * @return Number of free bytes in the queue. + */ + size_t BytesFree() const { + return size_ - BytesAvailable(); } private: const size_t size_; - std::unique_ptr buffer_; - std::atomic head_; - std::atomic tail_; - std::atomic count_; + std::unique_ptr buffer_ = nullptr; + std::atomic head_ = 0; + std::atomic tail_ = 0; + std::atomic count_ = 0; }; \ No newline at end of file diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp index c0c6b56..de4ce09 100644 --- a/test/BridgeServerMock.cpp +++ b/test/BridgeServerMock.cpp @@ -24,49 +24,49 @@ using namespace std::literals::chrono_literals; -void BridgeServerMock::createConnection() { - logger->Log("listening"); +void BridgeServerMock::CreateConnection() { + logger_->Log("listening"); - serverHandle = getLoop()->resource(false); - serverHandle->once([this](const uvw::ListenEvent &event, uvw::PipeHandle &) { - logger->Log("new client"); - resetBuffers(); + server_handle_ = GetLoop()->resource(false); + server_handle_->once([this](const uvw::ListenEvent &event, uvw::PipeHandle &) { + logger_->Log("new client"); + ResetBuffers(); /* ipc = false -> pipe will be used for handle passing between processes? no */ - connectionHandle = getLoop()->resource(false); + connection_handle_ = GetLoop()->resource(false); - connectionHandle->on([this](const uvw::EndEvent &, uvw::PipeHandle &) { - logger->Log("disconnected"); - stopAsync(); + connection_handle_->on([this](const uvw::EndEvent &, uvw::PipeHandle &) { + logger_->Log("disconnected"); + StopAsync(); }); - connectionHandle->on([this](const uvw::DataEvent &event, uvw::PipeHandle &) { - onRecv(event); + connection_handle_->on([this](const uvw::DataEvent &event, uvw::PipeHandle &) { + OnRecv(event); }); - connectionHandle->on([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { - logger->Log("Pipe error: %s", event.what()); - stopAsync(); + connection_handle_->on([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + logger_->Log("Pipe error: %s", event.what()); + StopAsync(); }); - serverHandle->accept(*connectionHandle); - connectionHandle->read(); - logger->Log("connected"); - connected = true; + server_handle_->accept(*connection_handle_); + connection_handle_->read(); + logger_->Log("connected"); + connected_ = true; }); - serverHandle->once([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { - logger->Log("Bind '%s' error: %s", path, event.what()); - stopAsync(); + server_handle_->once([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + logger_->Log("Bind '%s' error: %s", path_, event.what()); + StopAsync(); }); - serverHandle->bind(path); - serverHandle->listen(); + server_handle_->bind(path_); + server_handle_->listen(); } -void BridgeServerMock::resetConnection() { - closeConnectionHandles(); +void BridgeServerMock::ResetConnection() { + CloseConnectionHandles(); } -void BridgeServerMock::closeConnectionHandles() { - if (serverHandle) serverHandle->close(); - if (connectionHandle) connectionHandle->close(); - connected = false; +void BridgeServerMock::CloseConnectionHandles() { + if (server_handle_) server_handle_->close(); + if (connection_handle_) connection_handle_->close(); + connected_ = false; } diff --git a/test/BridgeServerMock.hpp b/test/BridgeServerMock.hpp index 3154858..1f7cacf 100644 --- a/test/BridgeServerMock.hpp +++ b/test/BridgeServerMock.hpp @@ -32,9 +32,9 @@ class BridgeServerMock: public BridgeTransport { using BridgeTransport::BridgeTransport; private: - void createConnection() override; - void resetConnection() override; - void closeConnectionHandles() override; + void CreateConnection() override; + void ResetConnection() override; + void CloseConnectionHandles() override; - std::shared_ptr serverHandle = nullptr; + std::shared_ptr server_handle_ = nullptr; }; \ No newline at end of file diff --git a/test/TestBridgeClientMock.cpp b/test/TestBridgeClientMock.cpp index 0f64bf9..4b6069a 100644 --- a/test/TestBridgeClientMock.cpp +++ b/test/TestBridgeClientMock.cpp @@ -6,6 +6,14 @@ TEST_CASE("IO with a mock server", "[Bridge]") { using namespace std::chrono; + std::map> serials = { + { 3, { TrackerRole::WAIST, "human://WAIST" } }, + { 4, { TrackerRole::LEFT_FOOT, "human://LEFT_FOOT" } }, + { 5, { TrackerRole::RIGHT_FOOT, "human://RIGHT_FOOT" } }, + { 6, { TrackerRole::LEFT_KNEE, "human://LEFT_KNEE" } }, + { 7, { TrackerRole::RIGHT_KNEE, "human://RIGHT_KNEE" } }, + }; + int positions = 0; int invalidMessages = 0; @@ -21,40 +29,32 @@ TEST_CASE("IO with a mock server", "[Bridge]") { logger, [&](const messages::ProtobufMessage& message) { if (message.has_tracker_added()) { - testLogTrackerAdded(logger, message); + TestLogTrackerAdded(logger, message); } else if (message.has_tracker_status()) { - testLogTrackerStatus(logger, message); + TestLogTrackerStatus(logger, message); } else if (message.has_position()) { messages::Position pos = message.position(); if (!lastLoggedPosition) logger->Log("... tracker positions response"); lastLoggedPosition = true; positions++; - messages::ProtobufMessage* serverMessage = google::protobuf::Arena::CreateMessage(&arena); + messages::ProtobufMessage* server_message = google::protobuf::Arena::CreateMessage(&arena); if (!trackersSent) { - std::map> serials = { - { 3, {TrackerRole::WAIST, "human://WAIST"} }, - { 4, {TrackerRole::LEFT_KNEE, "human://LEFT_KNEE"} }, - { 5, {TrackerRole::RIGHT_KNEE, "human://RIGHT_KNEE"} }, - { 6, {TrackerRole::LEFT_FOOT, "human://LEFT_FOOT"} }, - { 7, {TrackerRole::RIGHT_FOOT, "human://RIGHT_FOOT"} }, - }; - for (int32_t id = 3; id <= 7; id++) { - messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); - serverMessage->set_allocated_tracker_added(trackerAdded); - trackerAdded->set_tracker_id(id); - trackerAdded->set_tracker_role(serials[id].first); - trackerAdded->set_tracker_serial(serials[id].second); - trackerAdded->set_tracker_name(serials[id].second); - serverMock->sendBridgeMessage(*serverMessage); + messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena); + server_message->set_allocated_tracker_added(tracker_added); + tracker_added->set_tracker_id(id); + tracker_added->set_tracker_role(serials[id].first); + tracker_added->set_tracker_serial(serials[id].second); + tracker_added->set_tracker_name(serials[id].second); + serverMock->SendBridgeMessage(*server_message); - messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); - serverMessage->set_allocated_tracker_status(trackerStatus); - trackerStatus->set_tracker_id(id); - trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - serverMock->sendBridgeMessage(*serverMessage); + messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena); + server_message->set_allocated_tracker_status(tracker_status); + tracker_status->set_tracker_id(id); + tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + serverMock->SendBridgeMessage(*server_message); } trackersSent = true; @@ -62,7 +62,7 @@ TEST_CASE("IO with a mock server", "[Bridge]") { for (int32_t id = 3; id <= 7; id++) { messages::Position* trackerPosition = google::protobuf::Arena::CreateMessage(&arena); - serverMessage->set_allocated_position(trackerPosition); + server_message->set_allocated_position(trackerPosition); trackerPosition->set_tracker_id(id); trackerPosition->set_data_source(messages::Position_DataSource_FULL); trackerPosition->set_x(0); @@ -72,7 +72,7 @@ TEST_CASE("IO with a mock server", "[Bridge]") { trackerPosition->set_qy(0); trackerPosition->set_qz(0); trackerPosition->set_qw(0); - serverMock->sendBridgeMessage(*serverMessage); + serverMock->SendBridgeMessage(*server_message); } } else { invalidMessages++; @@ -84,10 +84,10 @@ TEST_CASE("IO with a mock server", "[Bridge]") { } ); - serverMock->start(); + serverMock->Start(); std::this_thread::sleep_for(10ms); - testBridgeClient(); - serverMock->stop(); + TestBridgeClient(); + serverMock->Stop(); if (invalidMessages) FAIL("Invalid messages received"); } \ No newline at end of file diff --git a/test/TestCircularBuffer.cpp b/test/TestCircularBuffer.cpp index 77e4965..66dbe43 100644 --- a/test/TestCircularBuffer.cpp +++ b/test/TestCircularBuffer.cpp @@ -2,33 +2,45 @@ #include "bridge/CircularBuffer.hpp" -TEST_CASE("push/pop", "[CircularBuffer]") { +TEST_CASE("Push/Pop", "[CircularBuffer]") { CircularBuffer buffer(4); char data[4]; - REQUIRE(buffer.push("1234", 4)); // [1234] - REQUIRE(buffer.pop(data, 2)); // [34] + REQUIRE(buffer.Push("1234", 4)); // [1234] + REQUIRE(buffer.BytesAvailable() == 4); + REQUIRE(buffer.Pop(data, 2)); // [34] + REQUIRE(buffer.BytesAvailable() == 2); REQUIRE(std::string(data, 2) == "12"); // test wraparound - REQUIRE(buffer.push("56", 2)); // [3456] - REQUIRE_FALSE(buffer.push("78", 2)); // [3456] buffer full - REQUIRE(buffer.pop(data, 4)); // [] + REQUIRE(buffer.Push("56", 2)); // [3456] + REQUIRE(buffer.BytesAvailable() == 4); + REQUIRE_FALSE(buffer.Push("78", 2)); // [3456] buffer full + REQUIRE(buffer.BytesAvailable() == 4); + REQUIRE(buffer.Pop(data, 4)); // [] + REQUIRE(buffer.BytesAvailable() == 0); REQUIRE(std::string(data, 4) == "3456"); - REQUIRE_FALSE(buffer.pop(data, 4)); // [] buffer empty + REQUIRE_FALSE(buffer.Pop(data, 4)); // [] buffer empty + REQUIRE(buffer.BytesAvailable() == 0); } -TEST_CASE("peek/skip", "[CircularBuffer]") { +TEST_CASE("Peek/Skip", "[CircularBuffer]") { CircularBuffer buffer(4); char data[4]; - REQUIRE_FALSE(buffer.peek(data, 2)); // [] nothing to peek - REQUIRE_FALSE(buffer.skip(2)); // [] nothing to skip + REQUIRE_FALSE(buffer.Peek(data, 2)); // [] nothing to peek + REQUIRE(buffer.BytesAvailable() == 0); + REQUIRE_FALSE(buffer.Skip(2)); // [] nothing to skip + REQUIRE(buffer.BytesAvailable() == 0); - REQUIRE(buffer.push("1234", 4)); // [1234] - REQUIRE(buffer.peek(data, 2) == 2); // [1234] + REQUIRE(buffer.Push("1234", 4)); // [1234] + REQUIRE(buffer.BytesAvailable() == 4); + REQUIRE(buffer.Peek(data, 2) == 2); // [1234] + REQUIRE(buffer.BytesAvailable() == 4); REQUIRE(std::string(data, 2) == "12"); - REQUIRE(buffer.skip(2)); // [34] - REQUIRE(buffer.peek(data, 1) == 1); // [34] + REQUIRE(buffer.Skip(2)); // [34] + REQUIRE(buffer.BytesAvailable() == 2); + REQUIRE(buffer.Peek(data, 1) == 1); // [34] + REQUIRE(buffer.BytesAvailable() == 2); REQUIRE(std::string(data, 1) == "3"); } \ No newline at end of file diff --git a/test/common/TestBridgeClient.cpp b/test/common/TestBridgeClient.cpp index e7e4a7f..4500c9d 100644 --- a/test/common/TestBridgeClient.cpp +++ b/test/common/TestBridgeClient.cpp @@ -1,17 +1,17 @@ #include "TestBridgeClient.hpp" -void testLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message) { +void TestLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message) { if (!message.has_tracker_added()) return; - messages::TrackerAdded ta = message.tracker_added(); + messages::TrackerAdded tracker_added = message.tracker_added(); logger->Log("tracker added id %i name %s role %i serial %s", - ta.tracker_id(), - ta.tracker_name().c_str(), - ta.tracker_role(), - ta.tracker_serial().c_str() + tracker_added.tracker_id(), + tracker_added.tracker_name().c_str(), + tracker_added.tracker_role(), + tracker_added.tracker_serial().c_str() ); } -void testLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message) { +void TestLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message) { if (!message.has_tracker_status()) return; messages::TrackerStatus status = message.tracker_status(); if (status.status() == messages::TrackerStatus_Status_OK) { @@ -25,19 +25,19 @@ void testLogTrackerStatus(std::shared_ptr logger, const messages::Protob } } -void testBridgeClient() { +void TestBridgeClient() { using namespace std::chrono; - std::atomic readyToBench = false; - std::atomic positionRequestedAt = steady_clock::now(); - std::map latencyNanosSum; - std::map latencyNanosCount; + std::atomic ready_to_bench = false; + std::atomic position_requested_at = steady_clock::now(); + std::map latency_nanos_sum; + std::map latency_nanos_count; - int invalidMessages = 0; + int invalid_messages = 0; int trackers = 0; int positions = 0; - bool lastLoggedPosition = false; + bool last_logged_position = false; auto logger = std::static_pointer_cast(std::make_shared("Bridge")); auto bridge = std::make_shared( @@ -45,90 +45,90 @@ void testBridgeClient() { [&](const messages::ProtobufMessage& message) { if (message.has_tracker_added()) { trackers++; - testLogTrackerAdded(logger, message); + TestLogTrackerAdded(logger, message); } else if (message.has_tracker_status()) { - testLogTrackerStatus(logger, message); + TestLogTrackerStatus(logger, message); } else if (message.has_position()) { messages::Position pos = message.position(); - if (!lastLoggedPosition) logger->Log("... tracker positions"); - lastLoggedPosition = true; + if (!last_logged_position) logger->Log("... tracker positions"); + last_logged_position = true; positions++; - if (!readyToBench) return; + if (!ready_to_bench) return; auto id = pos.tracker_id(); - auto dt = duration_cast(steady_clock::now() - positionRequestedAt.load()); - latencyNanosCount[id]++; - latencyNanosSum[id] += dt.count(); + auto dt = duration_cast(steady_clock::now() - position_requested_at.load()); + latency_nanos_count[id]++; + latency_nanos_sum[id] += dt.count(); } else { - invalidMessages++; + invalid_messages++; } if (!message.has_position()) { - lastLoggedPosition = false; + last_logged_position = false; } } ); - bridge->start(); + bridge->Start(); for (int i = 0; i < 20; i++) { - if (bridge->isConnected()) break; + if (bridge->IsConnected()) break; std::this_thread::sleep_for(milliseconds(100)); } - if (!bridge->isConnected()) { + if (!bridge->IsConnected()) { FAIL("Connection attempt timed out"); - bridge->stop(); + bridge->Stop(); return; } google::protobuf::Arena arena; messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); - messages::TrackerAdded* trackerAdded = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_added(trackerAdded); - trackerAdded->set_tracker_id(0); - trackerAdded->set_tracker_role(TrackerRole::HMD); - trackerAdded->set_tracker_serial("HMD"); - trackerAdded->set_tracker_name("HMD"); - bridge->sendBridgeMessage(*message); + messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_added(tracker_added); + tracker_added->set_tracker_id(0); + tracker_added->set_tracker_role(TrackerRole::HMD); + tracker_added->set_tracker_serial("HMD"); + tracker_added->set_tracker_name("HMD"); + bridge->SendBridgeMessage(*message); - messages::TrackerStatus* trackerStatus = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_status(trackerStatus); - trackerStatus->set_tracker_id(0); - trackerStatus->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - bridge->sendBridgeMessage(*message); + messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_tracker_status(tracker_status); + tracker_status->set_tracker_id(0); + tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + bridge->SendBridgeMessage(*message); - readyToBench = true; + ready_to_bench = true; for (int i = 0; i < 50; i++) { - messages::Position* hmdPosition = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_position(hmdPosition); - hmdPosition->set_tracker_id(0); - hmdPosition->set_data_source(messages::Position_DataSource_FULL); - hmdPosition->set_x(0); - hmdPosition->set_y(0); - hmdPosition->set_z(0); - hmdPosition->set_qx(0); - hmdPosition->set_qy(0); - hmdPosition->set_qz(0); - hmdPosition->set_qw(0); - - positionRequestedAt = steady_clock::now(); - bridge->sendBridgeMessage(*message); + messages::Position* hmd_position = google::protobuf::Arena::CreateMessage(&arena); + message->set_allocated_position(hmd_position); + hmd_position->set_tracker_id(0); + hmd_position->set_data_source(messages::Position_DataSource_FULL); + hmd_position->set_x(0); + hmd_position->set_y(0); + hmd_position->set_z(0); + hmd_position->set_qx(0); + hmd_position->set_qy(0); + hmd_position->set_qz(0); + hmd_position->set_qw(0); + + position_requested_at = steady_clock::now(); + bridge->SendBridgeMessage(*message); std::this_thread::sleep_for(milliseconds(10)); } - bridge->stop(); + bridge->Stop(); - for (const auto& [id, sum] : latencyNanosSum) { - auto avgLatencyNanos = static_cast(latencyNanosCount[id] ? sum / latencyNanosCount[id] : -1); - auto avgLatencyMs = duration_cast>(nanoseconds(avgLatencyNanos)); - logger->Log("Avg latency for tracker %i: %.3fms", id, avgLatencyMs.count()); + for (const auto& [id, sum] : latency_nanos_sum) { + auto avg_latency_nanos = static_cast(latency_nanos_count[id] ? sum / latency_nanos_count[id] : -1); + auto avg_latency_ms = duration_cast>(nanoseconds(avg_latency_nanos)); + logger->Log("Avg latency for tracker %i: %.3fms", id, avg_latency_ms.count()); } - if (invalidMessages) FAIL("Invalid messages received"); + if (invalid_messages) FAIL("Invalid messages received"); if (!trackers) FAIL("No trackers received"); if (!positions) FAIL("No tracker positions received"); } diff --git a/test/common/TestBridgeClient.hpp b/test/common/TestBridgeClient.hpp index 89fa716..65a5c28 100644 --- a/test/common/TestBridgeClient.hpp +++ b/test/common/TestBridgeClient.hpp @@ -6,6 +6,6 @@ #include "bridge/BridgeClient.hpp" #include "TrackerRole.hpp" -void testLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message); -void testLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message); -void testBridgeClient(); +void TestLogTrackerAdded(std::shared_ptr logger, const messages::ProtobufMessage& message); +void TestLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message); +void TestBridgeClient(); diff --git a/test/integration/TestBridgeClientReal.cpp b/test/integration/TestBridgeClientReal.cpp index faa1c4b..6149c55 100644 --- a/test/integration/TestBridgeClientReal.cpp +++ b/test/integration/TestBridgeClientReal.cpp @@ -5,5 +5,5 @@ #include "../common/TestBridgeClient.hpp" TEST_CASE("IO with a real server", "[Bridge]") { - testBridgeClient(); + TestBridgeClient(); } \ No newline at end of file From 479c570b35b459f2e1dc519004ad16ecdc16f456 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:57:27 +0300 Subject: [PATCH 06/24] Print connection error only once if not changed --- src/bridge/BridgeClient.cpp | 12 +++++++++--- src/bridge/BridgeClient.hpp | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index 1601442..6e88308 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -25,16 +25,19 @@ using namespace std::literals::chrono_literals; void BridgeClient::CreateConnection() { - logger_->Log("connecting"); ResetBuffers(); + if (!last_error_.has_value()) { + logger_->Log("connecting"); + } + /* ipc = false -> pipe will be used for handle passing between processes? no */ connection_handle_ = GetLoop()->resource(false); - connection_handle_->on([this](const uvw::ConnectEvent&, uvw::PipeHandle&) { connection_handle_->read(); logger_->Log("connected"); connected_ = true; + last_error_ = std::nullopt; }); connection_handle_->on([this](const uvw::EndEvent&, uvw::PipeHandle&) { logger_->Log("disconnected"); @@ -44,7 +47,10 @@ void BridgeClient::CreateConnection() { OnRecv(event); }); connection_handle_->on([this](const uvw::ErrorEvent& event, uvw::PipeHandle&) { - logger_->Log("Pipe error: %s", event.what()); + if (!last_error_.has_value() || last_error_ != event.what()) { + logger_->Log("Pipe error: %s", event.what()); + last_error_ = event.what(); + } Reconnect(); }); diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp index 832735f..f2b3d12 100644 --- a/src/bridge/BridgeClient.hpp +++ b/src/bridge/BridgeClient.hpp @@ -22,6 +22,7 @@ */ #pragma once +#include #include #include @@ -49,5 +50,6 @@ class BridgeClient: public BridgeTransport { void CloseConnectionHandles() override; void Reconnect(); + std::optional last_error_; std::shared_ptr reconnect_timeout_; }; \ No newline at end of file From ab0b1e2b96951e641ed82ae8a8f4127706599a13 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Tue, 28 Mar 2023 05:49:18 +0300 Subject: [PATCH 07/24] Fix indent --- src/DriverFactory.cpp | 26 +++++++++++++------------- src/TrackerDevice.cpp | 4 ++-- src/TrackerDevice.hpp | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/DriverFactory.cpp b/src/DriverFactory.cpp index 3ff0809..771f7c6 100644 --- a/src/DriverFactory.cpp +++ b/src/DriverFactory.cpp @@ -6,22 +6,22 @@ static std::shared_ptr driver; void* HmdDriverFactory(const char* interface_name, int* return_code) { - if (std::strcmp(interface_name, vr::IServerTrackedDeviceProvider_Version) == 0) { - if (!driver) { - // Instantiate concrete impl - driver = std::make_shared(); - } - // We always have at least 1 ref to the shared ptr in "driver" so passing out raw pointer is ok - return driver.get(); - } + if (std::strcmp(interface_name, vr::IServerTrackedDeviceProvider_Version) == 0) { + if (!driver) { + // Instantiate concrete impl + driver = std::make_shared(); + } + // We always have at least 1 ref to the shared ptr in "driver" so passing out raw pointer is ok + return driver.get(); + } - if (return_code) { - *return_code = vr::VRInitError_Init_InterfaceNotFound; - } + if (return_code) { + *return_code = vr::VRInitError_Init_InterfaceNotFound; + } - return nullptr; + return nullptr; } std::shared_ptr SlimeVRDriver::GetDriver() { - return driver; + return driver; } diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index 9ff95ab..91e4d9e 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -123,7 +123,7 @@ vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) { GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ModelNumber_String, "SlimeVR Virtual Tracker"); // Opt out of hand selection - GetDriver()->GetProperties()->SetInt32Property(props, vr::Prop_ControllerRoleHint_Int32, vr::ETrackedControllerRole::TrackedControllerRole_OptOut); + GetDriver()->GetProperties()->SetInt32Property(props, vr::Prop_ControllerRoleHint_Int32, vr::ETrackedControllerRole::TrackedControllerRole_OptOut); vr::VRProperties()->SetInt32Property(props, vr::Prop_DeviceClass_Int32, vr::TrackedDeviceClass_GenericTracker); vr::VRProperties()->SetInt32Property(props, vr::Prop_ControllerHandSelectionPriority_Int32, -1); @@ -144,7 +144,7 @@ vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) { // Automatically select vive tracker roles and set hints for games that need it (Beat Saber avatar mod, for example) auto role_hint = GetViveRoleHint(tracker_role_); if (role_hint != "") { - GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, role_hint.c_str()); + GetDriver()->GetProperties()->SetStringProperty(props, vr::Prop_ControllerType_String, role_hint.c_str()); } auto role = GetViveRole(tracker_role_); diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp index d3d474e..0fe1b5b 100644 --- a/src/TrackerDevice.hpp +++ b/src/TrackerDevice.hpp @@ -42,7 +42,7 @@ namespace SlimeVRDriver { std::string serial_; bool is_setup_; - int device_id_; + int device_id_; TrackerRole tracker_role_; vr::DriverPose_t last_pose_ = IVRDevice::MakeDefaultPose(); From 801e0ffd82ddf3d1a43a3197c96844fc60b2c4b9 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Tue, 28 Mar 2023 05:51:30 +0300 Subject: [PATCH 08/24] Fix the indent fix --- src/DriverFactory.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/DriverFactory.cpp b/src/DriverFactory.cpp index 771f7c6..cbd05cf 100644 --- a/src/DriverFactory.cpp +++ b/src/DriverFactory.cpp @@ -6,22 +6,22 @@ static std::shared_ptr driver; void* HmdDriverFactory(const char* interface_name, int* return_code) { - if (std::strcmp(interface_name, vr::IServerTrackedDeviceProvider_Version) == 0) { - if (!driver) { - // Instantiate concrete impl - driver = std::make_shared(); - } - // We always have at least 1 ref to the shared ptr in "driver" so passing out raw pointer is ok - return driver.get(); - } + if (std::strcmp(interface_name, vr::IServerTrackedDeviceProvider_Version) == 0) { + if (!driver) { + // Instantiate concrete impl + driver = std::make_shared(); + } + // We always have at least 1 ref to the shared ptr in "driver" so passing out raw pointer is ok + return driver.get(); + } - if (return_code) { - *return_code = vr::VRInitError_Init_InterfaceNotFound; - } + if (return_code) { + *return_code = vr::VRInitError_Init_InterfaceNotFound; + } - return nullptr; + return nullptr; } std::shared_ptr SlimeVRDriver::GetDriver() { - return driver; + return driver; } From 9d02b4bfb941461e0f1c6195a1f0777ca0295f1b Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sun, 2 Apr 2023 03:24:25 +0300 Subject: [PATCH 09/24] ci: Add CMakeLists to cache key --- .github/workflows/c-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index b7ecb4c..b5e5e17 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -52,7 +52,7 @@ jobs: # The key is composed in a way that it gets properly invalidated: this must happen whenever vcpkg/submodule Git commit id changes, or the list of packages changes. In this case a cache miss must happen and a new entry with a new key with be pushed to GitHub the cache service. # The key includes: hash of the vcpkg.json file, the hash of the vcpkg Git commit id, and the used vcpkg's triplet. The vcpkg's commit id would suffice, but computing an hash out it does not harm. # Note: given a key, the cache content is immutable. If a cache entry has been created improperly, in order the recreate the right content the key must be changed as well, and it must be brand new (i.e. not existing already). - key: ${{ matrix.triplet }}-${{ hashFiles( '**/vcpkg.json' ) }}-${{ hashFiles( 'submodule_hashes.txt' )}} + key: ${{ matrix.triplet }}-${{ hashFiles( '**/vcpkg.json', '**/CMakeLists.txt' ) }}-${{ hashFiles( 'submodule_hashes.txt' )}} - if: matrix.os == 'windows-latest' name: Set up vcpkg for Windows From 610a9028b4e8f45c3fa6837065f26ec43847c309 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Tue, 28 Mar 2023 23:51:34 +0300 Subject: [PATCH 10/24] Print universe error only once if not changed --- src/VRDriver.cpp | 23 ++++++++++++++++------- src/VRDriver.hpp | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 4cbc856..620541e 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -92,15 +92,24 @@ void SlimeVRDriver::VRDriver::RunFrame() { vr::PropertyContainerHandle_t hmd_prop_container = vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd); - uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64); - if (!current_universe_.has_value() || current_universe_.value().first != universe) { - auto result = SearchUniverses(universe); - if (result.has_value()) { - current_universe_.emplace(universe, result.value()); - } else { - logger_->Log("Failed to find current universe!"); + vr::ETrackedPropertyError universe_error; + uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64, &universe_error); + if (universe_error == vr::ETrackedPropertyError::TrackedProp_Success) { + if (!current_universe_.has_value() || current_universe_.value().first != universe) { + auto result = SearchUniverses(universe); + if (result.has_value()) { + current_universe_.emplace(universe, result.value()); + logger_->Log("Found current universe"); + } else { + logger_->Log("Failed to find current universe!"); + } } + } else if (universe_error != last_universe_error_) { + logger_->Log("Failed to find current universe: Prop_CurrentUniverseId_Uint64 error = %s", + vr::VRPropertiesRaw()->GetPropErrorNameFromEnum(universe_error) + ); } + last_universe_error_ = universe_error; vr::TrackedDevicePose_t hmd_pose[10]; vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index dc457c5..432231d 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -66,6 +66,7 @@ namespace SlimeVRDriver { std::optional default_chap_path_ = std::nullopt; //std::map universes; + vr::ETrackedPropertyError last_universe_error_; std::optional> current_universe_ = std::nullopt; std::optional SearchUniverse(std::string path, uint64_t target); std::optional SearchUniverses(uint64_t target); From 721289046622ee7bed8016d406a009721581fb38 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Tue, 28 Mar 2023 23:55:29 +0300 Subject: [PATCH 11/24] Fix naming and refactor --- test/BridgeServerMock.cpp | 2 +- test/TestBridgeClientMock.cpp | 58 ++++++++++++++++---------------- test/common/TestBridgeClient.cpp | 16 ++++----- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp index de4ce09..940292a 100644 --- a/test/BridgeServerMock.cpp +++ b/test/BridgeServerMock.cpp @@ -53,7 +53,7 @@ void BridgeServerMock::CreateConnection() { connected_ = true; }); server_handle_->once([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { - logger_->Log("Bind '%s' error: %s", path_, event.what()); + logger_->Log("Bind %s error: %s", path_.c_str(), event.what()); StopAsync(); }); diff --git a/test/TestBridgeClientMock.cpp b/test/TestBridgeClientMock.cpp index 4b6069a..b3f1f8e 100644 --- a/test/TestBridgeClientMock.cpp +++ b/test/TestBridgeClientMock.cpp @@ -6,7 +6,7 @@ TEST_CASE("IO with a mock server", "[Bridge]") { using namespace std::chrono; - std::map> serials = { + std::unordered_map> serials = { { 3, { TrackerRole::WAIST, "human://WAIST" } }, { 4, { TrackerRole::LEFT_FOOT, "human://LEFT_FOOT" } }, { 5, { TrackerRole::RIGHT_FOOT, "human://RIGHT_FOOT" } }, @@ -15,17 +15,17 @@ TEST_CASE("IO with a mock server", "[Bridge]") { }; int positions = 0; - int invalidMessages = 0; + int invalid_messages = 0; - bool lastLoggedPosition = false; - bool trackersSent = false; + bool last_logged_position = false; + bool trackers_sent = false; google::protobuf::Arena arena; auto logger = std::static_pointer_cast(std::make_shared("ServerMock")); - std::shared_ptr serverMock; - serverMock = std::make_shared( + std::shared_ptr server_mock; + server_mock = std::make_shared( logger, [&](const messages::ProtobufMessage& message) { if (message.has_tracker_added()) { @@ -34,13 +34,13 @@ TEST_CASE("IO with a mock server", "[Bridge]") { TestLogTrackerStatus(logger, message); } else if (message.has_position()) { messages::Position pos = message.position(); - if (!lastLoggedPosition) logger->Log("... tracker positions response"); - lastLoggedPosition = true; + if (!last_logged_position) logger->Log("... tracker positions response"); + last_logged_position = true; positions++; messages::ProtobufMessage* server_message = google::protobuf::Arena::CreateMessage(&arena); - if (!trackersSent) { + if (!trackers_sent) { for (int32_t id = 3; id <= 7; id++) { messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena); server_message->set_allocated_tracker_added(tracker_added); @@ -48,46 +48,46 @@ TEST_CASE("IO with a mock server", "[Bridge]") { tracker_added->set_tracker_role(serials[id].first); tracker_added->set_tracker_serial(serials[id].second); tracker_added->set_tracker_name(serials[id].second); - serverMock->SendBridgeMessage(*server_message); + server_mock->SendBridgeMessage(*server_message); messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena); server_message->set_allocated_tracker_status(tracker_status); tracker_status->set_tracker_id(id); tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - serverMock->SendBridgeMessage(*server_message); + server_mock->SendBridgeMessage(*server_message); } - trackersSent = true; + trackers_sent = true; } for (int32_t id = 3; id <= 7; id++) { - messages::Position* trackerPosition = google::protobuf::Arena::CreateMessage(&arena); - server_message->set_allocated_position(trackerPosition); - trackerPosition->set_tracker_id(id); - trackerPosition->set_data_source(messages::Position_DataSource_FULL); - trackerPosition->set_x(0); - trackerPosition->set_y(0); - trackerPosition->set_z(0); - trackerPosition->set_qx(0); - trackerPosition->set_qy(0); - trackerPosition->set_qz(0); - trackerPosition->set_qw(0); - serverMock->SendBridgeMessage(*server_message); + messages::Position* tracker_position = google::protobuf::Arena::CreateMessage(&arena); + server_message->set_allocated_position(tracker_position); + tracker_position->set_tracker_id(id); + tracker_position->set_data_source(messages::Position_DataSource_FULL); + tracker_position->set_x(0); + tracker_position->set_y(0); + tracker_position->set_z(0); + tracker_position->set_qx(0); + tracker_position->set_qy(0); + tracker_position->set_qz(0); + tracker_position->set_qw(0); + server_mock->SendBridgeMessage(*server_message); } } else { - invalidMessages++; + invalid_messages++; } if (!message.has_position()) { - lastLoggedPosition = false; + last_logged_position = false; } } ); - serverMock->Start(); + server_mock->Start(); std::this_thread::sleep_for(10ms); TestBridgeClient(); - serverMock->Stop(); + server_mock->Stop(); - if (invalidMessages) FAIL("Invalid messages received"); + if (invalid_messages) FAIL("Invalid messages received"); } \ No newline at end of file diff --git a/test/common/TestBridgeClient.cpp b/test/common/TestBridgeClient.cpp index 4500c9d..d281883 100644 --- a/test/common/TestBridgeClient.cpp +++ b/test/common/TestBridgeClient.cpp @@ -14,14 +14,14 @@ void TestLogTrackerAdded(std::shared_ptr logger, const messages::Protobu void TestLogTrackerStatus(std::shared_ptr logger, const messages::ProtobufMessage& message) { if (!message.has_tracker_status()) return; messages::TrackerStatus status = message.tracker_status(); - if (status.status() == messages::TrackerStatus_Status_OK) { - logger->Log("tracker status id %i status %s", status.tracker_id(), "OK"); - } else if (status.status() == messages::TrackerStatus_Status_DISCONNECTED) { - logger->Log("tracker status id %i status %s", status.tracker_id(), "DISCONNECTED"); - } else if (status.status() == messages::TrackerStatus_Status_ERROR) { - logger->Log("tracker status id %i status %s", status.tracker_id(), "ERROR"); - } else if (status.status() == messages::TrackerStatus_Status_BUSY) { - logger->Log("tracker status id %i status %s", status.tracker_id(), "BUSY"); + static const std::unordered_map status_map = { + { messages::TrackerStatus_Status_OK, "OK" }, + { messages::TrackerStatus_Status_DISCONNECTED, "DISCONNECTED" }, + { messages::TrackerStatus_Status_ERROR, "ERROR" }, + { messages::TrackerStatus_Status_BUSY, "BUSY" }, + }; + if (status_map.count(status.status())) { + logger->Log("tracker status id %i status %s", status.tracker_id(), status_map.at(status.status()).c_str()); } } From 2a54f0d82274978bec7d4e5871216cd354416aaa Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 29 Mar 2023 01:59:17 +0300 Subject: [PATCH 12/24] Log tracker statuses --- src/VRDriver.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 620541e..38cb22a 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -188,6 +188,15 @@ void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& m auto device = this->devices_by_id_.find(status.tracker_id()); if (device != this->devices_by_id_.end()) { device->second->StatusMessage(status); + static const std::unordered_map status_map = { + { messages::TrackerStatus_Status_OK, "OK" }, + { messages::TrackerStatus_Status_DISCONNECTED, "DISCONNECTED" }, + { messages::TrackerStatus_Status_ERROR, "ERROR" }, + { messages::TrackerStatus_Status_BUSY, "BUSY" }, + }; + if (status_map.count(status.status())) { + logger_->Log("Tracker status id %i status %s", status.tracker_id(), status_map.at(status.status()).c_str()); + } } } } From 582e199ef5db8b98c7cb557ac4e7a09673c296fb Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 29 Mar 2023 02:00:24 +0300 Subject: [PATCH 13/24] Poll at 500hz --- src/TrackerDevice.cpp | 13 ++- src/TrackerDevice.hpp | 3 +- src/VRDriver.cpp | 214 ++++++++++++++++++++++-------------------- src/VRDriver.hpp | 8 +- 4 files changed, 126 insertions(+), 112 deletions(-) diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index 91e4d9e..a052562 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -41,7 +41,7 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; // Setup pose for this frame - auto pose = this->last_pose_; + auto pose = MakeDefaultPose(); //send the new position and rotation from the pipe to the tracker object if (position.has_x()) { pose.vecPosition[0] = position.x(); @@ -69,13 +69,13 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) pose.qWorldFromDriverRotation.z = 0; } - // Post pose - GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); + // Notify SteamVR that pose was updated this->last_pose_ = pose; + GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); } void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) { - auto pose = this->last_pose_; + vr::DriverPose_t pose = this->last_pose_; switch (status.status()) { case messages::TrackerStatus_Status_OK: pose.deviceIsConnected = true; @@ -94,10 +94,9 @@ void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status } // TODO: send position/rotation of 0 instead of last pose? - + + this->last_pose_ = pose; GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); - - // TODO: update this->last_pose_? } DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() { diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp index 0fe1b5b..46faeeb 100644 --- a/src/TrackerDevice.hpp +++ b/src/TrackerDevice.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -45,7 +46,7 @@ namespace SlimeVRDriver { int device_id_; TrackerRole tracker_role_; - vr::DriverPose_t last_pose_ = IVRDevice::MakeDefaultPose(); + std::atomic last_pose_ = IVRDevice::MakeDefaultPose(); bool did_vibrate_ = false; float vibrate_anim_state_ = 0.f; diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 38cb22a..c98066c 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -30,6 +30,9 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont ); bridge_->Start(); + pose_request_thread_ = + std::make_unique(&SlimeVRDriver::VRDriver::RunPoseRequestThread, this); + return vr::VRInitError_None; } @@ -37,9 +40,116 @@ void SlimeVRDriver::VRDriver::Cleanup() { bridge_->Stop(); } -void SlimeVRDriver::VRDriver::RunFrame() { - google::protobuf::Arena arena; +void SlimeVRDriver::VRDriver::RunPoseRequestThread() { + logger_->Log("pose request thread started"); + while (!vr::VRServerDriverHost()->IsExiting()) { + if (!bridge_->IsConnected()) { + // If bridge not connected, assume we need to resend hmd tracker add message + send_hmd_add_message_ = false; + continue; + } + + messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena_); + + if (!send_hmd_add_message_) { + // Send add message for HMD + messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena_); + message->set_allocated_tracker_added(tracker_added); + tracker_added->set_tracker_id(0); + tracker_added->set_tracker_role(TrackerRole::HMD); + tracker_added->set_tracker_serial("HMD"); + tracker_added->set_tracker_name("HMD"); + bridge_->SendBridgeMessage(*message); + + messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena_); + message->set_allocated_tracker_status(tracker_status); + tracker_status->set_tracker_id(0); + tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); + bridge_->SendBridgeMessage(*message); + + send_hmd_add_message_ = true; + logger_->Log("Sent HMD hello message"); + } + + vr::PropertyContainerHandle_t hmd_prop_container = + vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd); + + vr::ETrackedPropertyError universe_error; + uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64, &universe_error); + if (universe_error == vr::ETrackedPropertyError::TrackedProp_Success) { + if (!current_universe_.has_value() || current_universe_.value().first != universe) { + auto result = SearchUniverses(universe); + if (result.has_value()) { + current_universe_.emplace(universe, result.value()); + logger_->Log("Found current universe"); + } else { + logger_->Log("Failed to find current universe!"); + } + } + } else if (universe_error != last_universe_error_) { + logger_->Log("Failed to find current universe: Prop_CurrentUniverseId_Uint64 error = %s", + vr::VRPropertiesRaw()->GetPropErrorNameFromEnum(universe_error) + ); + } + last_universe_error_ = universe_error; + + vr::TrackedDevicePose_t hmd_pose[10]; + vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); + + vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); + vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); + + if (current_universe_.has_value()) { + auto trans = current_universe_.value().second; + pos.v[0] += trans.translation.v[0]; + pos.v[1] += trans.translation.v[1]; + pos.v[2] += trans.translation.v[2]; + + // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0 + auto tmp_w = cos(-trans.yaw / 2); + auto tmp_y = sin(-trans.yaw / 2); + auto new_w = tmp_w * q.w - tmp_y * q.y; + auto new_x = tmp_w * q.x + tmp_y * q.z; + auto new_y = tmp_w * q.y + tmp_y * q.w; + auto new_z = tmp_w * q.z - tmp_y * q.x; + + q.w = new_w; + q.x = new_x; + q.y = new_y; + q.z = new_z; + + // rotate point on the xz plane by -trans.yaw radians + // this is equivilant to the quaternion multiplication, after applying the double angle formula. + float tmp_sin = sin(-trans.yaw); + float tmp_cos = cos(-trans.yaw); + auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin; + auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos; + + pos.v[0] = pos_x; + pos.v[2] = pos_z; + } + messages::Position* hmd_position = google::protobuf::Arena::CreateMessage(&arena_); + message->set_allocated_position(hmd_position); + hmd_position->set_tracker_id(0); + hmd_position->set_data_source(messages::Position_DataSource_FULL); + hmd_position->set_x(pos.v[0]); + hmd_position->set_y(pos.v[1]); + hmd_position->set_z(pos.v[2]); + hmd_position->set_qx((float) q.x); + hmd_position->set_qy((float) q.y); + hmd_position->set_qz((float) q.z); + hmd_position->set_qw((float) q.w); + bridge_->SendBridgeMessage(*message); + + arena_.Reset(); + + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + logger_->Log("pose request thread exited"); +} + +void SlimeVRDriver::VRDriver::RunFrame() { // Collect events vr::VREvent_t event; std::vector events; @@ -60,106 +170,6 @@ void SlimeVRDriver::VRDriver::RunFrame() { device->Update(); } } - - if (!bridge_->IsConnected()) { - // If bridge not connected, assume we need to resend hmd tracker add message - send_hmd_add_message_ = false; - return; - } - - messages::ProtobufMessage* message = google::protobuf::Arena::CreateMessage(&arena); - - if (!send_hmd_add_message_) { - // Send add message for HMD - messages::TrackerAdded* tracker_added = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_added(tracker_added); - tracker_added->set_tracker_id(0); - tracker_added->set_tracker_role(TrackerRole::HMD); - tracker_added->set_tracker_serial("HMD"); - tracker_added->set_tracker_name("HMD"); - bridge_->SendBridgeMessage(*message); - - messages::TrackerStatus* tracker_status = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_tracker_status(tracker_status); - tracker_status->set_tracker_id(0); - tracker_status->set_status(messages::TrackerStatus_Status::TrackerStatus_Status_OK); - bridge_->SendBridgeMessage(*message); - - send_hmd_add_message_ = true; - logger_->Log("Sent HMD hello message"); - } - - vr::PropertyContainerHandle_t hmd_prop_container = - vr::VRProperties()->TrackedDeviceToPropertyContainer(vr::k_unTrackedDeviceIndex_Hmd); - - vr::ETrackedPropertyError universe_error; - uint64_t universe = vr::VRProperties()->GetUint64Property(hmd_prop_container, vr::Prop_CurrentUniverseId_Uint64, &universe_error); - if (universe_error == vr::ETrackedPropertyError::TrackedProp_Success) { - if (!current_universe_.has_value() || current_universe_.value().first != universe) { - auto result = SearchUniverses(universe); - if (result.has_value()) { - current_universe_.emplace(universe, result.value()); - logger_->Log("Found current universe"); - } else { - logger_->Log("Failed to find current universe!"); - } - } - } else if (universe_error != last_universe_error_) { - logger_->Log("Failed to find current universe: Prop_CurrentUniverseId_Uint64 error = %s", - vr::VRPropertiesRaw()->GetPropErrorNameFromEnum(universe_error) - ); - } - last_universe_error_ = universe_error; - - vr::TrackedDevicePose_t hmd_pose[10]; - vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); - - vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); - vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); - - if (current_universe_.has_value()) { - auto trans = current_universe_.value().second; - pos.v[0] += trans.translation.v[0]; - pos.v[1] += trans.translation.v[1]; - pos.v[2] += trans.translation.v[2]; - - // rotate by quaternion w = cos(-trans.yaw / 2), x = 0, y = sin(-trans.yaw / 2), z = 0 - auto tmp_w = cos(-trans.yaw / 2); - auto tmp_y = sin(-trans.yaw / 2); - auto new_w = tmp_w * q.w - tmp_y * q.y; - auto new_x = tmp_w * q.x + tmp_y * q.z; - auto new_y = tmp_w * q.y + tmp_y * q.w; - auto new_z = tmp_w * q.z - tmp_y * q.x; - - q.w = new_w; - q.x = new_x; - q.y = new_y; - q.z = new_z; - - // rotate point on the xz plane by -trans.yaw radians - // this is equivilant to the quaternion multiplication, after applying the double angle formula. - float tmp_sin = sin(-trans.yaw); - float tmp_cos = cos(-trans.yaw); - auto pos_x = pos.v[0] * tmp_cos + pos.v[2] * tmp_sin; - auto pos_z = pos.v[0] * -tmp_sin + pos.v[2] * tmp_cos; - - pos.v[0] = pos_x; - pos.v[2] = pos_z; - } - - messages::Position* hmd_position = google::protobuf::Arena::CreateMessage(&arena); - message->set_allocated_position(hmd_position); - hmd_position->set_tracker_id(0); - hmd_position->set_data_source(messages::Position_DataSource_FULL); - hmd_position->set_x(pos.v[0]); - hmd_position->set_y(pos.v[1]); - hmd_position->set_z(pos.v[2]); - hmd_position->set_qx((float) q.x); - hmd_position->set_qy((float) q.y); - hmd_position->set_qz((float) q.z); - hmd_position->set_qw((float) q.w); - - bridge_->SendBridgeMessage(*message); } void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& message) { diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index 432231d..2ad414c 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -41,12 +41,16 @@ namespace SlimeVRDriver { virtual void LeaveStandby() override; virtual ~VRDriver() = default; - void OnBridgeMessage(const messages::ProtobufMessage& message); - virtual std::optional GetCurrentUniverse() override; + void OnBridgeMessage(const messages::ProtobufMessage& message); + void RunPoseRequestThread(); + private: + std::unique_ptr pose_request_thread_ = nullptr; + std::shared_ptr bridge_ = nullptr; + google::protobuf::Arena arena_; std::shared_ptr logger_ = std::make_shared(); std::mutex devices_mutex_; std::vector> devices_; From 73c2f195a38f756f4a922e766dc57d551aa84651 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sun, 2 Apr 2023 03:06:45 +0300 Subject: [PATCH 14/24] Add sleep benchmark --- src/VRDriver.cpp | 8 ++++++++ test/TestSleepTimes.cpp | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/TestSleepTimes.cpp diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index c98066c..45bd140 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -144,6 +144,14 @@ void SlimeVRDriver::VRDriver::RunPoseRequestThread() { arena_.Reset(); + // Windows: + // p1: 2.191 ms 456.413 tps + // avg: 2.492 ms 401.225 tps + // p99: 2.526 ms 395.883 tps + // Linux: + // p1: 2.063 ms 484.731 tps + // avg: 2.079 ms 480.992 tps + // p99: 2.115 ms 472.813 tps std::this_thread::sleep_for(std::chrono::milliseconds(2)); } logger_->Log("pose request thread exited"); diff --git a/test/TestSleepTimes.cpp b/test/TestSleepTimes.cpp new file mode 100644 index 0000000..e6e734e --- /dev/null +++ b/test/TestSleepTimes.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include +#include +#include +#include +#include + +TEST_CASE("Sleep times") { + const int sleep_duration_ms = 2; + const int benchmark_duration_sec = 1; + const int num_iterations = 1000000; + std::vector sleep_times; + sleep_times.reserve(num_iterations); + + printf("Benching std::this_thread::sleep_for(std::chrono::milliseconds(%i));\n", sleep_duration_ms); + auto start_time = std::chrono::high_resolution_clock::now(); + while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() < benchmark_duration_sec) { + auto iteration_start_time = std::chrono::high_resolution_clock::now(); + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_duration_ms)); + auto iteration_end_time = std::chrono::high_resolution_clock::now(); + + sleep_times.push_back(std::chrono::duration_cast(iteration_end_time - iteration_start_time).count()); + } + std::sort(sleep_times.begin(), sleep_times.end()); + + const size_t num_samples = sleep_times.size(); + const size_t p1_index = num_samples * 1 / 100; + const size_t p99_index = num_samples * 99 / 100; + const double avg_time_ms = static_cast(std::accumulate(sleep_times.begin(), sleep_times.end(), 0LL)) / num_samples / 1000; + const double p1_time_ms = static_cast(sleep_times[p1_index]) / 1000; + const double p99_time_ms = static_cast(sleep_times[p99_index]) / 1000; + printf("p1: %.3lf ms %.3lf tps\n", p1_time_ms, 1e3 / p1_time_ms); + printf("avg: %.3lf ms %.3lf tps\n", avg_time_ms, 1e3 / avg_time_ms); + printf("p99: %.3lf ms %.3lf tps\n", p99_time_ms, 1e3 / p99_time_ms); +} \ No newline at end of file From 50e0f95f4c69300bbf71b89c38cb6b36e2c0449d Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sun, 2 Apr 2023 03:08:05 +0300 Subject: [PATCH 15/24] Fix adding trackers --- src/IVRDevice.hpp | 5 +++++ src/TrackerDevice.cpp | 17 ++++++++++++----- src/TrackerDevice.hpp | 3 ++- src/VRDriver.cpp | 29 ++++++++++++++++++++--------- src/VRDriver.hpp | 1 + 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/IVRDevice.hpp b/src/IVRDevice.hpp index 8feb6a9..9f4104e 100644 --- a/src/IVRDevice.hpp +++ b/src/IVRDevice.hpp @@ -59,6 +59,11 @@ namespace SlimeVRDriver { */ virtual int GetDeviceId() = 0; + /** + * Sets the device id. + */ + virtual void SetDeviceId(int device_id) = 0; + /** * Updates device position from a received message. */ diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index a052562..76dc62a 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -1,11 +1,12 @@ #include "TrackerDevice.hpp" SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int device_id, TrackerRole tracker_role): - serial_(serial), tracker_role_(tracker_role), device_id_(device_id) -{ - this->last_pose_ = MakeDefaultPose(); - this->is_setup_ = false; -} + serial_(serial), + tracker_role_(tracker_role), + device_id_(device_id), + last_pose_(MakeDefaultPose()), + is_setup_(false) +{ } std::string SlimeVRDriver::TrackerDevice::GetSerial() { return this->serial_; @@ -75,6 +76,8 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) } void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) { + if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; + vr::DriverPose_t pose = this->last_pose_; switch (status.status()) { case messages::TrackerStatus_Status_OK: @@ -178,3 +181,7 @@ vr::DriverPose_t SlimeVRDriver::TrackerDevice::GetPose() { int SlimeVRDriver::TrackerDevice::GetDeviceId() { return device_id_; } + +void SlimeVRDriver::TrackerDevice::SetDeviceId(int device_id) { + device_id_ = device_id; +} diff --git a/src/TrackerDevice.hpp b/src/TrackerDevice.hpp index 46faeeb..e9789df 100644 --- a/src/TrackerDevice.hpp +++ b/src/TrackerDevice.hpp @@ -27,6 +27,7 @@ namespace SlimeVRDriver { virtual vr::TrackedDeviceIndex_t GetDeviceIndex() override; virtual DeviceType GetDeviceType() override; virtual int GetDeviceId() override; + virtual void SetDeviceId(int device_id) override; virtual void PositionMessage(messages::Position &position) override; virtual void StatusMessage(messages::TrackerStatus &status) override; @@ -39,7 +40,7 @@ namespace SlimeVRDriver { virtual vr::DriverPose_t GetPose() override; private: - vr::TrackedDeviceIndex_t device_index_ = vr::k_unTrackedDeviceIndexInvalid; + std::atomic device_index_ = vr::k_unTrackedDeviceIndexInvalid; std::string serial_; bool is_setup_; diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 45bd140..1024f04 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -30,6 +30,7 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont ); bridge_->Start(); + exiting_pose_request_thread_ = false; pose_request_thread_ = std::make_unique(&SlimeVRDriver::VRDriver::RunPoseRequestThread, this); @@ -37,15 +38,19 @@ vr::EVRInitError SlimeVRDriver::VRDriver::Init(vr::IVRDriverContext* pDriverCont } void SlimeVRDriver::VRDriver::Cleanup() { + exiting_pose_request_thread_ = true; + pose_request_thread_->join(); + pose_request_thread_.reset(); bridge_->Stop(); } void SlimeVRDriver::VRDriver::RunPoseRequestThread() { logger_->Log("pose request thread started"); - while (!vr::VRServerDriverHost()->IsExiting()) { + while (!exiting_pose_request_thread_) { if (!bridge_->IsConnected()) { // If bridge not connected, assume we need to resend hmd tracker add message send_hmd_add_message_ = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } @@ -192,8 +197,7 @@ void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& m switch(GetDeviceType(static_cast(ta.tracker_role()))) { case DeviceType::TRACKER: this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); - logger_->Log("New tracker device added %s (id %i)", ta.tracker_serial().c_str(), ta.tracker_id()); - break; + break; } } else if (message.has_position()) { messages::Position pos = message.position(); @@ -261,21 +265,28 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { default: return false; } - bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get()); - if (result) { - this->devices_.push_back(device); - this->devices_by_id_[device->GetDeviceId()] = device; - this->devices_by_serial_[device->GetSerial()] = device; + if (!devices_by_serial_.count(device->GetSerial())) { + bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get()); + if (result) { + this->devices_.push_back(device); + this->devices_by_id_[device->GetDeviceId()] = device; + this->devices_by_serial_[device->GetSerial()] = device; + logger_->Log("New tracker device added %s (id %i)", device->GetSerial().c_str(), device->GetDeviceId()); + } else { + logger_->Log("Failed to add tracker device %s (id %i)", device->GetSerial().c_str(), device->GetDeviceId()); + return false; + } } else { std::shared_ptr oldDevice = this->devices_by_serial_[device->GetSerial()]; if (oldDevice->GetDeviceId() != device->GetDeviceId()) { this->devices_by_id_[device->GetDeviceId()] = oldDevice; + oldDevice->SetDeviceId(device->GetDeviceId()); logger_->Log("Device overridden from id %i to %i for serial %s", oldDevice->GetDeviceId(), device->GetDeviceId(), device->GetSerial()); } else { logger_->Log("Device readded id %i, serial %s", device->GetDeviceId(), device->GetSerial().c_str()); } } - return result; + return true; } SlimeVRDriver::SettingsValue SlimeVRDriver::VRDriver::GetSettingsValue(std::string key) { diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index 2ad414c..84a9735 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -48,6 +48,7 @@ namespace SlimeVRDriver { private: std::unique_ptr pose_request_thread_ = nullptr; + std::atomic exiting_pose_request_thread_ = false; std::shared_ptr bridge_ = nullptr; google::protobuf::Arena arena_; From c27ee5ba91d1bb27be5f0c40e4d1caaaef32923d Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sun, 2 Apr 2023 03:09:07 +0300 Subject: [PATCH 16/24] Remove redundant thises --- src/TrackerDevice.cpp | 48 +++++++++++++++++++++---------------------- src/VRDriver.cpp | 34 +++++++++++++++--------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/TrackerDevice.cpp b/src/TrackerDevice.cpp index 76dc62a..69c3050 100644 --- a/src/TrackerDevice.cpp +++ b/src/TrackerDevice.cpp @@ -9,37 +9,37 @@ SlimeVRDriver::TrackerDevice::TrackerDevice(std::string serial, int device_id, T { } std::string SlimeVRDriver::TrackerDevice::GetSerial() { - return this->serial_; + return serial_; } void SlimeVRDriver::TrackerDevice::Update() { - if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; + if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; // Check if this device was asked to be identified auto events = GetDriver()->GetOpenVREvents(); for (auto event : events) { - // Note here, event.trackedDeviceIndex does not necessarily equal this->device_index_, not sure why, but the component handle will match so we can just use that instead - //if (event.trackedDeviceIndex == this->device_index_) { + // Note here, event.trackedDeviceIndex does not necessarily equal device_index_, not sure why, but the component handle will match so we can just use that instead + //if (event.trackedDeviceIndex == device_index_) { if (event.eventType == vr::EVREventType::VREvent_Input_HapticVibration) { - if (event.data.hapticVibration.componentHandle == this->haptic_component_) { - this->did_vibrate_ = true; + if (event.data.hapticVibration.componentHandle == haptic_component_) { + did_vibrate_ = true; } } //} } // Check if we need to keep vibrating - if (this->did_vibrate_) { - this->vibrate_anim_state_ += GetDriver()->GetLastFrameTime().count() / 1000.f; - if (this->vibrate_anim_state_ > 1.0f) { - this->did_vibrate_ = false; - this->vibrate_anim_state_ = 0.0f; + if (did_vibrate_) { + vibrate_anim_state_ += GetDriver()->GetLastFrameTime().count() / 1000.f; + if (vibrate_anim_state_ > 1.0f) { + did_vibrate_ = false; + vibrate_anim_state_ = 0.0f; } } } void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) { - if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; + if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; // Setup pose for this frame auto pose = MakeDefaultPose(); @@ -71,14 +71,14 @@ void SlimeVRDriver::TrackerDevice::PositionMessage(messages::Position &position) } // Notify SteamVR that pose was updated - this->last_pose_ = pose; - GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); + last_pose_ = pose; + GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(device_index_, pose, sizeof(vr::DriverPose_t)); } void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status) { - if (this->device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; + if (device_index_ == vr::k_unTrackedDeviceIndexInvalid) return; - vr::DriverPose_t pose = this->last_pose_; + vr::DriverPose_t pose = last_pose_; switch (status.status()) { case messages::TrackerStatus_Status_OK: pose.deviceIsConnected = true; @@ -98,8 +98,8 @@ void SlimeVRDriver::TrackerDevice::StatusMessage(messages::TrackerStatus &status // TODO: send position/rotation of 0 instead of last pose? - this->last_pose_ = pose; - GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(this->device_index_, pose, sizeof(vr::DriverPose_t)); + last_pose_ = pose; + GetDriver()->GetDriverHost()->TrackedDevicePoseUpdated(device_index_, pose, sizeof(vr::DriverPose_t)); } DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() { @@ -107,16 +107,16 @@ DeviceType SlimeVRDriver::TrackerDevice::GetDeviceType() { } vr::TrackedDeviceIndex_t SlimeVRDriver::TrackerDevice::GetDeviceIndex() { - return this->device_index_; + return device_index_; } vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) { - this->device_index_ = unObjectId; + device_index_ = unObjectId; - GetDriver()->Log("Activating tracker " + this->serial_); + GetDriver()->Log("Activating tracker " + serial_); // Get the properties handle - auto props = GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(this->device_index_); + auto props = GetDriver()->GetProperties()->TrackedDeviceToPropertyContainer(device_index_); // Set some universe ID (Must be 2 or higher) GetDriver()->GetProperties()->SetUint64Property(props, vr::Prop_CurrentUniverseId_Uint64, 4); @@ -151,14 +151,14 @@ vr::EVRInitError SlimeVRDriver::TrackerDevice::Activate(uint32_t unObjectId) { auto role = GetViveRole(tracker_role_); if (role != "") { - vr::VRSettings()->SetString(vr::k_pch_Trackers_Section, ("/devices/slimevr/" + this->serial_).c_str(), role.c_str()); + vr::VRSettings()->SetString(vr::k_pch_Trackers_Section, ("/devices/slimevr/" + serial_).c_str(), role.c_str()); } return vr::EVRInitError::VRInitError_None; } void SlimeVRDriver::TrackerDevice::Deactivate() { - this->device_index_ = vr::k_unTrackedDeviceIndexInvalid; + device_index_ = vr::k_unTrackedDeviceIndexInvalid; } void SlimeVRDriver::TrackerDevice::EnterStandby() { diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 1024f04..bcba802 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -169,17 +169,17 @@ void SlimeVRDriver::VRDriver::RunFrame() { while (vr::VRServerDriverHost()->PollNextEvent(&event, sizeof(event))) { events.push_back(event); } - this->openvr_events_ = std::move(events); + openvr_events_ = std::move(events); // Update frame timing std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); - this->frame_timing_ = std::chrono::duration_cast(now - this->last_frame_time_); - this->last_frame_time_ = now; + frame_timing_ = std::chrono::duration_cast(now - last_frame_time_); + last_frame_time_ = now; // Update devices { std::lock_guard lock(devices_mutex_); - for (auto& device : this->devices_) { + for (auto& device : devices_) { device->Update(); } } @@ -196,19 +196,19 @@ void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& m messages::TrackerAdded ta = message.tracker_added(); switch(GetDeviceType(static_cast(ta.tracker_role()))) { case DeviceType::TRACKER: - this->AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); + AddDevice(std::make_shared(ta.tracker_serial(), ta.tracker_id(), static_cast(ta.tracker_role()))); break; } } else if (message.has_position()) { messages::Position pos = message.position(); - auto device = this->devices_by_id_.find(pos.tracker_id()); - if (device != this->devices_by_id_.end()) { + auto device = devices_by_id_.find(pos.tracker_id()); + if (device != devices_by_id_.end()) { device->second->PositionMessage(pos); } } else if (message.has_tracker_status()) { messages::TrackerStatus status = message.tracker_status(); - auto device = this->devices_by_id_.find(status.tracker_id()); - if (device != this->devices_by_id_.end()) { + auto device = devices_by_id_.find(status.tracker_id()); + if (device != devices_by_id_.end()) { device->second->StatusMessage(status); static const std::unordered_map status_map = { { messages::TrackerStatus_Status_OK, "OK" }, @@ -235,15 +235,15 @@ void SlimeVRDriver::VRDriver::LeaveStandby() { std::vector> SlimeVRDriver::VRDriver::GetDevices() { std::lock_guard lock(devices_mutex_); - return this->devices_; + return devices_; } std::vector SlimeVRDriver::VRDriver::GetOpenVREvents() { - return this->openvr_events_; + return openvr_events_; } std::chrono::milliseconds SlimeVRDriver::VRDriver::GetLastFrameTime() { - return this->frame_timing_; + return frame_timing_; } bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { @@ -268,18 +268,18 @@ bool SlimeVRDriver::VRDriver::AddDevice(std::shared_ptr device) { if (!devices_by_serial_.count(device->GetSerial())) { bool result = vr::VRServerDriverHost()->TrackedDeviceAdded(device->GetSerial().c_str(), openvr_device_class, device.get()); if (result) { - this->devices_.push_back(device); - this->devices_by_id_[device->GetDeviceId()] = device; - this->devices_by_serial_[device->GetSerial()] = device; + devices_.push_back(device); + devices_by_id_[device->GetDeviceId()] = device; + devices_by_serial_[device->GetSerial()] = device; logger_->Log("New tracker device added %s (id %i)", device->GetSerial().c_str(), device->GetDeviceId()); } else { logger_->Log("Failed to add tracker device %s (id %i)", device->GetSerial().c_str(), device->GetDeviceId()); return false; } } else { - std::shared_ptr oldDevice = this->devices_by_serial_[device->GetSerial()]; + std::shared_ptr oldDevice = devices_by_serial_[device->GetSerial()]; if (oldDevice->GetDeviceId() != device->GetDeviceId()) { - this->devices_by_id_[device->GetDeviceId()] = oldDevice; + devices_by_id_[device->GetDeviceId()] = oldDevice; oldDevice->SetDeviceId(device->GetDeviceId()); logger_->Log("Device overridden from id %i to %i for serial %s", oldDevice->GetDeviceId(), device->GetDeviceId(), device->GetSerial()); } else { From 93c7f229714601a2c0daef8bb53f19a6f3362bf7 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sun, 2 Apr 2023 07:35:53 +0300 Subject: [PATCH 17/24] Change pose request thread logs --- src/VRDriver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index bcba802..337ef03 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -45,7 +45,7 @@ void SlimeVRDriver::VRDriver::Cleanup() { } void SlimeVRDriver::VRDriver::RunPoseRequestThread() { - logger_->Log("pose request thread started"); + logger_->Log("Pose request thread started"); while (!exiting_pose_request_thread_) { if (!bridge_->IsConnected()) { // If bridge not connected, assume we need to resend hmd tracker add message @@ -159,7 +159,7 @@ void SlimeVRDriver::VRDriver::RunPoseRequestThread() { // p99: 2.115 ms 472.813 tps std::this_thread::sleep_for(std::chrono::milliseconds(2)); } - logger_->Log("pose request thread exited"); + logger_->Log("Pose request thread exited"); } void SlimeVRDriver::VRDriver::RunFrame() { From d3036c3175502816610a1c80c3b366300ac82fab Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Sat, 8 Apr 2023 03:49:09 +0300 Subject: [PATCH 18/24] Fix atomic on linux --- CMakeLists.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ccd2220..3046cb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,11 +56,6 @@ elseif(APPLE) set(PLATFORM_NAME "osx") endif() -if(UNIX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -endif() - find_library(OPENVR_LIB openvr_api HINTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/openvr/lib/${PLATFORM_NAME}${PROCESSOR_ARCH}/" NO_DEFAULT_PATH ) # Protobuf @@ -103,11 +98,15 @@ add_library("${PROJECT_NAME}_static" STATIC ${SOURCES} ${PROTO_HEADER} ${PROTO_S target_link_libraries("${PROJECT_NAME}_static" PUBLIC ${DEPS_LIBS}) set_property(TARGET "${PROJECT_NAME}_static" PROPERTY CXX_STANDARD 17) include_directories("${PROJECT_NAME}_static" PUBLIC ${DEPS_INCLUDES} ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +if(UNIX) + target_compile_options("${PROJECT_NAME}_static" PRIVATE "-fPIC") + target_link_libraries("${PROJECT_NAME}_static" PUBLIC atomic) +endif() # compile driver file(GLOB_RECURSE DRIVER_MAIN "${CMAKE_CURRENT_SOURCE_DIR}/src/DriverFactory.cpp") add_library("${PROJECT_NAME}" SHARED ${DRIVER_MAIN} ${HEADERS} ${PROTO_HEADER}) -target_link_libraries("${PROJECT_NAME}" PRIVATE "${PROJECT_NAME}_static") +target_link_libraries("${PROJECT_NAME}" PUBLIC "${PROJECT_NAME}_static") set_property(TARGET "${PROJECT_NAME}" PROPERTY CXX_STANDARD 17) # compile tests From 572c8e655242191d4442eb93afb6efa7f8e790a2 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 1 Nov 2023 05:48:02 +0300 Subject: [PATCH 19/24] Update dependencies --- CMakeLists.txt | 5 ++--- src/bridge/BridgeClient.cpp | 14 +++++++------- src/bridge/BridgeClient.hpp | 2 +- src/bridge/BridgeTransport.cpp | 12 ++++++------ src/bridge/BridgeTransport.hpp | 16 ++++++++-------- test/BridgeServerMock.cpp | 14 +++++++------- test/BridgeServerMock.hpp | 2 +- vcpkg | 2 +- 8 files changed, 33 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3046cb3..6d724a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,8 +70,7 @@ SET_SOURCE_FILES_PROPERTIES(${PROTO_SRC} ${PROTO_INCL} PROPERTIES GENERATED TRUE find_package(simdjson CONFIG REQUIRED) # libuv -find_package(libuv REQUIRED) -find_package(uvw REQUIRED) +find_package(uvw CONFIG REQUIRED) # Catch2 find_package(Catch2 3 REQUIRED) @@ -88,7 +87,7 @@ set(DEPS_LIBS protobuf::libprotobuf protobuf::libprotobuf-lite simdjson::simdjson - $,uv_a,uv> # libuv + uvw::uvw ) # compile into a static lib diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index 6e88308..246fb00 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -32,21 +32,21 @@ void BridgeClient::CreateConnection() { } /* ipc = false -> pipe will be used for handle passing between processes? no */ - connection_handle_ = GetLoop()->resource(false); - connection_handle_->on([this](const uvw::ConnectEvent&, uvw::PipeHandle&) { + connection_handle_ = GetLoop()->resource(false); + connection_handle_->on([this](const uvw::connect_event&, uvw::pipe_handle&) { connection_handle_->read(); logger_->Log("connected"); connected_ = true; last_error_ = std::nullopt; }); - connection_handle_->on([this](const uvw::EndEvent&, uvw::PipeHandle&) { + connection_handle_->on([this](const uvw::end_event&, uvw::pipe_handle&) { logger_->Log("disconnected"); Reconnect(); }); - connection_handle_->on([this](const uvw::DataEvent& event, uvw::PipeHandle&) { + connection_handle_->on([this](const uvw::data_event& event, uvw::pipe_handle&) { OnRecv(event); }); - connection_handle_->on([this](const uvw::ErrorEvent& event, uvw::PipeHandle&) { + connection_handle_->on([this](const uvw::error_event& event, uvw::pipe_handle&) { if (!last_error_.has_value() || last_error_ != event.what()) { logger_->Log("Pipe error: %s", event.what()); last_error_ = event.what(); @@ -63,9 +63,9 @@ void BridgeClient::ResetConnection() { void BridgeClient::Reconnect() { CloseConnectionHandles(); - reconnect_timeout_ = GetLoop()->resource(); + reconnect_timeout_ = GetLoop()->resource(); reconnect_timeout_->start(1000ms, 0ms); - reconnect_timeout_->once([this](const uvw::TimerEvent&, uvw::TimerHandle& handle) { + reconnect_timeout_->on([this](const uvw::timer_event&, uvw::timer_handle& handle) { CreateConnection(); handle.close(); }); diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp index f2b3d12..1c83a3b 100644 --- a/src/bridge/BridgeClient.hpp +++ b/src/bridge/BridgeClient.hpp @@ -51,5 +51,5 @@ class BridgeClient: public BridgeTransport { void Reconnect(); std::optional last_error_; - std::shared_ptr reconnect_timeout_; + std::shared_ptr reconnect_timeout_; }; \ No newline at end of file diff --git a/src/bridge/BridgeTransport.cpp b/src/bridge/BridgeTransport.cpp index 9b225fa..d88ee18 100644 --- a/src/bridge/BridgeTransport.cpp +++ b/src/bridge/BridgeTransport.cpp @@ -41,18 +41,18 @@ void BridgeTransport::StopAsync() { void BridgeTransport::RunThread() { logger_->Log("thread started"); - loop_ = uvw::Loop::create(); - stop_signal_handle_ = GetLoop()->resource(); - write_signal_handle_ = GetLoop()->resource(); + loop_ = uvw::loop::create(); + stop_signal_handle_ = GetLoop()->resource(); + write_signal_handle_ = GetLoop()->resource(); - stop_signal_handle_->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + stop_signal_handle_->on([this](const uvw::async_event&, uvw::async_handle& handle) { logger_->Log("closing handles"); CloseConnectionHandles(); write_signal_handle_->close(); stop_signal_handle_->close(); }); - write_signal_handle_->on([this](const uvw::AsyncEvent&, uvw::AsyncHandle& handle) { + write_signal_handle_->on([this](const uvw::async_event&, uvw::async_handle& handle) { SendWrites(); }); @@ -67,7 +67,7 @@ void BridgeTransport::ResetBuffers() { send_buf_.Clear(); } -void BridgeTransport::OnRecv(const uvw::DataEvent& event) { +void BridgeTransport::OnRecv(const uvw::data_event& event) { if (!recv_buf_.Push(event.data.get(), event.length)) { logger_->Log("recv_buf_.Push %i failed", event.length); ResetConnection(); diff --git a/src/bridge/BridgeTransport.hpp b/src/bridge/BridgeTransport.hpp index 5144416..5855bd6 100644 --- a/src/bridge/BridgeTransport.hpp +++ b/src/bridge/BridgeTransport.hpp @@ -66,7 +66,7 @@ static std::string GetBridgePath() { } /** - * @brief Abstract implementation for passing messages between SlimeVR Server and SteamVR Driver using pipes. + * @brief Passes messages between SlimeVR Server and SteamVR Driver using pipes. * * Client or Server connection handling is implemented by extending this class. * @@ -74,7 +74,7 @@ static std::string GetBridgePath() { * and is abstracted through `libuv`. * * When a message is received and parsed from the pipe, the messageCallback function passed in the constructor is called - * from the event loop thread with the message as a parameter. + * from the libuv event loop thread with the message as a parameter. * * @param logger A shared pointer to an Logger object to log messages from the transport. * @param on_message_received A function to be called from event loop thread when a message is received and parsed from the pipe. @@ -117,7 +117,7 @@ class BridgeTransport { /** * @brief Sends a message over the channel. * - * This method queues the message in the send buffer to be sent over the pipe. + * Queues the message to the send buffer to be sent over the pipe. * * @param message The message to send. */ @@ -137,7 +137,7 @@ class BridgeTransport { virtual void ResetConnection() = 0; virtual void CloseConnectionHandles() = 0; void ResetBuffers(); - void OnRecv(const uvw::DataEvent& event); + void OnRecv(const uvw::data_event& event); auto GetLoop() { return loop_; } @@ -145,7 +145,7 @@ class BridgeTransport { std::shared_ptr logger_; const std::string path_; std::atomic connected_ = false; - std::shared_ptr connection_handle_ = nullptr; + std::shared_ptr connection_handle_ = nullptr; private: void RunThread(); @@ -153,9 +153,9 @@ class BridgeTransport { CircularBuffer send_buf_; CircularBuffer recv_buf_; - std::shared_ptr stop_signal_handle_ = nullptr; - std::shared_ptr write_signal_handle_ = nullptr; + std::shared_ptr stop_signal_handle_ = nullptr; + std::shared_ptr write_signal_handle_ = nullptr; std::unique_ptr thread_ = nullptr; - std::shared_ptr loop_ = nullptr; + std::shared_ptr loop_ = nullptr; const std::function message_callback_; }; \ No newline at end of file diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp index 940292a..0e89f47 100644 --- a/test/BridgeServerMock.cpp +++ b/test/BridgeServerMock.cpp @@ -27,22 +27,22 @@ using namespace std::literals::chrono_literals; void BridgeServerMock::CreateConnection() { logger_->Log("listening"); - server_handle_ = GetLoop()->resource(false); - server_handle_->once([this](const uvw::ListenEvent &event, uvw::PipeHandle &) { + server_handle_ = GetLoop()->resource(false); + server_handle_->on([this](const uvw::listen_event &event, uvw::pipe_handle &) { logger_->Log("new client"); ResetBuffers(); /* ipc = false -> pipe will be used for handle passing between processes? no */ - connection_handle_ = GetLoop()->resource(false); + connection_handle_ = GetLoop()->resource(false); - connection_handle_->on([this](const uvw::EndEvent &, uvw::PipeHandle &) { + connection_handle_->on([this](const uvw::end_event &, uvw::pipe_handle &) { logger_->Log("disconnected"); StopAsync(); }); - connection_handle_->on([this](const uvw::DataEvent &event, uvw::PipeHandle &) { + connection_handle_->on([this](const uvw::data_event &event, uvw::pipe_handle &) { OnRecv(event); }); - connection_handle_->on([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + connection_handle_->on([this](const uvw::error_event &event, uvw::pipe_handle &) { logger_->Log("Pipe error: %s", event.what()); StopAsync(); }); @@ -52,7 +52,7 @@ void BridgeServerMock::CreateConnection() { logger_->Log("connected"); connected_ = true; }); - server_handle_->once([this](const uvw::ErrorEvent &event, uvw::PipeHandle &) { + server_handle_->on([this](const uvw::error_event &event, uvw::pipe_handle &) { logger_->Log("Bind %s error: %s", path_.c_str(), event.what()); StopAsync(); }); diff --git a/test/BridgeServerMock.hpp b/test/BridgeServerMock.hpp index 1f7cacf..f5f52ba 100644 --- a/test/BridgeServerMock.hpp +++ b/test/BridgeServerMock.hpp @@ -36,5 +36,5 @@ class BridgeServerMock: public BridgeTransport { void ResetConnection() override; void CloseConnectionHandles() override; - std::shared_ptr server_handle_ = nullptr; + std::shared_ptr server_handle_ = nullptr; }; \ No newline at end of file diff --git a/vcpkg b/vcpkg index b81bc3a..0e2a0e0 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit b81bc3a83fdbdffe80325eeabb2ec735a1f3c29d +Subproject commit 0e2a0e0ad1a370f85a637452be52e2ddf2dcac0b From 0b43b194909a6b58215afbcb3fb00b9c5c1f2295 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 1 Nov 2023 06:30:14 +0300 Subject: [PATCH 20/24] Get only the HMD pose --- src/VRDriver.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index fd4e09f..88e6803 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -98,11 +98,12 @@ void SlimeVRDriver::VRDriver::RunPoseRequestThread() { } last_universe_error_ = universe_error; - vr::TrackedDevicePose_t hmd_pose[10]; - vr::VRServerDriverHost()->GetRawTrackedDevicePoses(0, hmd_pose, 10); + vr::TrackedDevicePose_t hmd_pose; + vr::VRServerDriverHost()->GetRawTrackedDevicePoses( + vr::k_unTrackedDeviceIndex_Hmd, &hmd_pose, 1); - vr::HmdQuaternion_t q = GetRotation(hmd_pose[0].mDeviceToAbsoluteTracking); - vr::HmdVector3_t pos = GetPosition(hmd_pose[0].mDeviceToAbsoluteTracking); + vr::HmdQuaternion_t q = GetRotation(hmd_pose.mDeviceToAbsoluteTracking); + vr::HmdVector3_t pos = GetPosition(hmd_pose.mDeviceToAbsoluteTracking); if (current_universe_.has_value()) { auto trans = current_universe_.value().second; From d4349fb936126ae2b47a322e26598c2e0fa4fde5 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 1 Nov 2023 06:34:09 +0300 Subject: [PATCH 21/24] Copy devices properly --- src/VRDriver.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 88e6803..21db360 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -197,11 +197,6 @@ void SlimeVRDriver::VRDriver::RunFrame() { } void SlimeVRDriver::VRDriver::OnBridgeMessage(const messages::ProtobufMessage& message) { - // note: called from bridge thread; - // driver sample says that TrackedDevicePoseUpdated should happen from "some pose tracking thread", - // thus we assume the functions that "notify" (as described in docs) - // like TrackedDevicePoseUpdated and TrackedDeviceAdded are safe to call here - std::lock_guard lock(devices_mutex_); if (message.has_tracker_added()) { messages::TrackerAdded ta = message.tracker_added(); @@ -252,7 +247,9 @@ void SlimeVRDriver::VRDriver::LeaveStandby() { std::vector> SlimeVRDriver::VRDriver::GetDevices() { std::lock_guard lock(devices_mutex_); - return devices_; + std::vector> devices; + devices.assign(devices.begin(), devices.end()); + return devices; } std::vector SlimeVRDriver::VRDriver::GetOpenVREvents() { From fdd6e9a19babb2e26748df11d91171dbcdcb9190 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 1 Nov 2023 06:54:23 +0300 Subject: [PATCH 22/24] Send HMD battery at 10Hz --- src/VRDriver.cpp | 28 ++++++++++++---------------- src/VRDriver.hpp | 1 + src/bridge/BridgeClient.cpp | 2 +- src/bridge/BridgeTransport.cpp | 4 ++-- test/BridgeServerMock.cpp | 4 ++-- test/common/TestBridgeClient.cpp | 4 ++-- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 21db360..6d34bf7 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -148,26 +148,22 @@ void SlimeVRDriver::VRDriver::RunPoseRequestThread() { hmd_position->set_qw((float) q.w); bridge_->SendBridgeMessage(*message); - vr::ETrackedPropertyError err; - if (vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceProvidesBatteryStatus_Bool, &err) == true) { - messages::Battery* hmdBattery = google::protobuf::Arena::CreateMessage(&arena_); - message->set_allocated_battery(hmdBattery); - hmdBattery->set_tracker_id(0); - hmdBattery->set_battery_level(vr::VRProperties()->GetFloatProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceBatteryPercentage_Float, &err) * 100); - hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceIsCharging_Bool, &err)); - bridge_->SendBridgeMessage(*message); + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - battery_sent_at_).count() > 100) { + vr::ETrackedPropertyError err; + if (vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceProvidesBatteryStatus_Bool, &err)) { + messages::Battery* hmdBattery = google::protobuf::Arena::CreateMessage(&arena_); + message->set_allocated_battery(hmdBattery); + hmdBattery->set_tracker_id(0); + hmdBattery->set_battery_level(vr::VRProperties()->GetFloatProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceBatteryPercentage_Float, &err) * 100); + hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceIsCharging_Bool, &err)); + bridge_->SendBridgeMessage(*message); + } + battery_sent_at_ = now; } arena_.Reset(); - // Windows: - // p1: 2.191 ms 456.413 tps - // avg: 2.492 ms 401.225 tps - // p99: 2.526 ms 395.883 tps - // Linux: - // p1: 2.063 ms 484.731 tps - // avg: 2.079 ms 480.992 tps - // p99: 2.115 ms 472.813 tps std::this_thread::sleep_for(std::chrono::milliseconds(2)); } logger_->Log("Pose request thread exited"); diff --git a/src/VRDriver.hpp b/src/VRDriver.hpp index 84a9735..fced819 100644 --- a/src/VRDriver.hpp +++ b/src/VRDriver.hpp @@ -60,6 +60,7 @@ namespace SlimeVRDriver { std::map> devices_by_serial_; std::chrono::milliseconds frame_timing_ = std::chrono::milliseconds(16); std::chrono::steady_clock::time_point last_frame_time_ = std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point battery_sent_at_ = std::chrono::steady_clock::now(); std::string settings_key_ = "driver_slimevr"; vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t &matrix); diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index 246fb00..e0a77cb 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -48,7 +48,7 @@ void BridgeClient::CreateConnection() { }); connection_handle_->on([this](const uvw::error_event& event, uvw::pipe_handle&) { if (!last_error_.has_value() || last_error_ != event.what()) { - logger_->Log("Pipe error: %s", event.what()); + logger_->Log("pipe error: %s", event.what()); last_error_ = event.what(); } Reconnect(); diff --git a/src/bridge/BridgeTransport.cpp b/src/bridge/BridgeTransport.cpp index d88ee18..2f9a94b 100644 --- a/src/bridge/BridgeTransport.cpp +++ b/src/bridge/BridgeTransport.cpp @@ -69,7 +69,7 @@ void BridgeTransport::ResetBuffers() { void BridgeTransport::OnRecv(const uvw::data_event& event) { if (!recv_buf_.Push(event.data.get(), event.length)) { - logger_->Log("recv_buf_.Push %i failed", event.length); + logger_->Log("recv_buf_.Push(%i) failed", event.length); ResetConnection(); return; } @@ -93,7 +93,7 @@ void BridgeTransport::OnRecv(const uvw::data_event& event) { auto message_buf = std::make_unique(size); if (!recv_buf_.Skip(4) || !recv_buf_.Pop(message_buf.get(), unwrapped_size)) { - logger_->Log("recv_buf_.Pop %i failed", size); + logger_->Log("recv_buf_.Pop(%i) failed", size); ResetConnection(); return; } diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp index 0e89f47..dd591e7 100644 --- a/test/BridgeServerMock.cpp +++ b/test/BridgeServerMock.cpp @@ -43,7 +43,7 @@ void BridgeServerMock::CreateConnection() { OnRecv(event); }); connection_handle_->on([this](const uvw::error_event &event, uvw::pipe_handle &) { - logger_->Log("Pipe error: %s", event.what()); + logger_->Log("pipe error: %s", event.what()); StopAsync(); }); @@ -53,7 +53,7 @@ void BridgeServerMock::CreateConnection() { connected_ = true; }); server_handle_->on([this](const uvw::error_event &event, uvw::pipe_handle &) { - logger_->Log("Bind %s error: %s", path_.c_str(), event.what()); + logger_->Log("bind %s error: %s", path_.c_str(), event.what()); StopAsync(); }); diff --git a/test/common/TestBridgeClient.cpp b/test/common/TestBridgeClient.cpp index d281883..fecbc48 100644 --- a/test/common/TestBridgeClient.cpp +++ b/test/common/TestBridgeClient.cpp @@ -39,7 +39,7 @@ void TestBridgeClient() { bool last_logged_position = false; - auto logger = std::static_pointer_cast(std::make_shared("Bridge")); + auto logger = std::static_pointer_cast(std::make_shared("Test")); auto bridge = std::make_shared( logger, [&](const messages::ProtobufMessage& message) { @@ -125,7 +125,7 @@ void TestBridgeClient() { for (const auto& [id, sum] : latency_nanos_sum) { auto avg_latency_nanos = static_cast(latency_nanos_count[id] ? sum / latency_nanos_count[id] : -1); auto avg_latency_ms = duration_cast>(nanoseconds(avg_latency_nanos)); - logger->Log("Avg latency for tracker %i: %.3fms", id, avg_latency_ms.count()); + logger->Log("avg latency for tracker %i: %.3fms", id, avg_latency_ms.count()); } if (invalid_messages) FAIL("Invalid messages received"); From 599abe24e931c6035351fe02aae7a4f575b24a85 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Wed, 1 Nov 2023 07:03:48 +0300 Subject: [PATCH 23/24] Use already defined prop container for battery --- src/VRDriver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VRDriver.cpp b/src/VRDriver.cpp index 6d34bf7..c588744 100644 --- a/src/VRDriver.cpp +++ b/src/VRDriver.cpp @@ -151,12 +151,12 @@ void SlimeVRDriver::VRDriver::RunPoseRequestThread() { auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - battery_sent_at_).count() > 100) { vr::ETrackedPropertyError err; - if (vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceProvidesBatteryStatus_Bool, &err)) { + if (vr::VRProperties()->GetBoolProperty(hmd_prop_container, vr::Prop_DeviceProvidesBatteryStatus_Bool, &err)) { messages::Battery* hmdBattery = google::protobuf::Arena::CreateMessage(&arena_); message->set_allocated_battery(hmdBattery); hmdBattery->set_tracker_id(0); - hmdBattery->set_battery_level(vr::VRProperties()->GetFloatProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceBatteryPercentage_Float, &err) * 100); - hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(vr::VRProperties()->TrackedDeviceToPropertyContainer(0), vr::Prop_DeviceIsCharging_Bool, &err)); + hmdBattery->set_battery_level(vr::VRProperties()->GetFloatProperty(hmd_prop_container, vr::Prop_DeviceBatteryPercentage_Float, &err) * 100); + hmdBattery->set_is_charging(vr::VRProperties()->GetBoolProperty(hmd_prop_container, vr::Prop_DeviceIsCharging_Bool, &err)); bridge_->SendBridgeMessage(*message); } battery_sent_at_ = now; From 996bef32cc5e418c91d61df588013e6adf57e119 Mon Sep 17 00:00:00 2001 From: 0forks <114709761+0forks@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:42:12 +0300 Subject: [PATCH 24/24] Handle $HOME and $XDG_DATA_DIR --- src/bridge/BridgeClient.cpp | 8 +++--- src/bridge/BridgeClient.hpp | 2 +- src/bridge/BridgeTransport.hpp | 49 +++++++++++++++++++++++----------- test/BridgeServerMock.cpp | 8 +++--- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/bridge/BridgeClient.cpp b/src/bridge/BridgeClient.cpp index e0a77cb..097d81d 100644 --- a/src/bridge/BridgeClient.cpp +++ b/src/bridge/BridgeClient.cpp @@ -31,6 +31,8 @@ void BridgeClient::CreateConnection() { logger_->Log("connecting"); } + std::string path = GetBridgePath(); + /* ipc = false -> pipe will be used for handle passing between processes? no */ connection_handle_ = GetLoop()->resource(false); connection_handle_->on([this](const uvw::connect_event&, uvw::pipe_handle&) { @@ -46,15 +48,15 @@ void BridgeClient::CreateConnection() { connection_handle_->on([this](const uvw::data_event& event, uvw::pipe_handle&) { OnRecv(event); }); - connection_handle_->on([this](const uvw::error_event& event, uvw::pipe_handle&) { + connection_handle_->on([this, path](const uvw::error_event& event, uvw::pipe_handle&) { if (!last_error_.has_value() || last_error_ != event.what()) { - logger_->Log("pipe error: %s", event.what()); + logger_->Log("[%s] pipe error: %s", path.c_str(), event.what()); last_error_ = event.what(); } Reconnect(); }); - connection_handle_->connect(path_); + connection_handle_->connect(path); } void BridgeClient::ResetConnection() { diff --git a/src/bridge/BridgeClient.hpp b/src/bridge/BridgeClient.hpp index 1c83a3b..ee75fa1 100644 --- a/src/bridge/BridgeClient.hpp +++ b/src/bridge/BridgeClient.hpp @@ -29,7 +29,7 @@ #include "BridgeTransport.hpp" /** - * @brief Client implementation for communication with SlimeVR Server using pipes. + * @brief Client implementation for communication with SlimeVR Server using pipes or unix sockets. * * This class provides a set of methods to start, stop an IO thread, send messages over a named pipe or unix socket * and is abstracted through `libuv`. diff --git a/src/bridge/BridgeTransport.hpp b/src/bridge/BridgeTransport.hpp index 5855bd6..def23d9 100644 --- a/src/bridge/BridgeTransport.hpp +++ b/src/bridge/BridgeTransport.hpp @@ -49,24 +49,13 @@ namespace fs = std::filesystem; #define WINDOWS_PIPE_NAME "\\\\.\\pipe\\SlimeVRDriver" +#define UNIX_XDG_DATA_DIR_DEFAULT ".local/share/" +#define UNIX_SLIMEVR_DIR "slimevr" #define UNIX_TMP_DIR "/tmp" #define UNIX_SOCKET_NAME "SlimeVRDriver" -static std::string GetBridgePath() { -#ifdef __linux__ - if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { - const fs::path xdg_runtime = ptr; - return (xdg_runtime / UNIX_SOCKET_NAME).string(); - } else { - return (fs::path(UNIX_TMP_DIR) / UNIX_SOCKET_NAME).string(); - } -#else - return WINDOWS_PIPE_NAME; -#endif -} - /** - * @brief Passes messages between SlimeVR Server and SteamVR Driver using pipes. + * @brief Passes messages between SlimeVR Server and SteamVR Driver using pipes or unix sockets. * * Client or Server connection handling is implemented by extending this class. * @@ -84,7 +73,6 @@ class BridgeTransport { BridgeTransport(std::shared_ptr logger, std::function on_message_received) : logger_(logger), message_callback_(on_message_received), - path_(GetBridgePath()), send_buf_(VRBRIDGE_BUFFERS_SIZE), recv_buf_(VRBRIDGE_BUFFERS_SIZE) { } @@ -142,8 +130,37 @@ class BridgeTransport { return loop_; } + static std::string GetBridgePath() { +#ifdef __linux__ + std::vector paths = { }; + if (const char* ptr = std::getenv("XDG_RUNTIME_DIR")) { + const fs::path xdg_runtime = ptr; + paths.push_back((xdg_runtime / UNIX_SOCKET_NAME).string()); + } + + if (const char* ptr = std::getenv("XDG_DATA_DIR")) { + const fs::path xdg_data = ptr; + paths.push_back((xdg_data / UNIX_SLIMEVR_DIR / UNIX_SOCKET_NAME).string()); + } + + if (const char* ptr = std::getenv("HOME")) { + const fs::path home = ptr; + paths.push_back((home / UNIX_XDG_DATA_DIR_DEFAULT / UNIX_SLIMEVR_DIR / UNIX_SOCKET_NAME).string()); + } + + for (auto path : paths) { + if (fs::exists(path)) { + return path; + } + } + + return (fs::path(UNIX_TMP_DIR) / UNIX_SOCKET_NAME).string(); +#else + return WINDOWS_PIPE_NAME; +#endif + } + std::shared_ptr logger_; - const std::string path_; std::atomic connected_ = false; std::shared_ptr connection_handle_ = nullptr; diff --git a/test/BridgeServerMock.cpp b/test/BridgeServerMock.cpp index dd591e7..0caca26 100644 --- a/test/BridgeServerMock.cpp +++ b/test/BridgeServerMock.cpp @@ -27,6 +27,8 @@ using namespace std::literals::chrono_literals; void BridgeServerMock::CreateConnection() { logger_->Log("listening"); + std::string path = GetBridgePath(); + server_handle_ = GetLoop()->resource(false); server_handle_->on([this](const uvw::listen_event &event, uvw::pipe_handle &) { logger_->Log("new client"); @@ -52,12 +54,12 @@ void BridgeServerMock::CreateConnection() { logger_->Log("connected"); connected_ = true; }); - server_handle_->on([this](const uvw::error_event &event, uvw::pipe_handle &) { - logger_->Log("bind %s error: %s", path_.c_str(), event.what()); + server_handle_->on([this, path](const uvw::error_event &event, uvw::pipe_handle &) { + logger_->Log("[%s] bind error: %s", path.c_str(), event.what()); StopAsync(); }); - server_handle_->bind(path_); + server_handle_->bind(path); server_handle_->listen(); }