From ad0f32d240c9f7ad24d75eebd1d40f9cbd153d60 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:22:48 -0400 Subject: [PATCH 1/2] refactor(sonar): fix sonar warnings --- src/network/host_discovery.cpp | 21 +++++++++---- src/streaming/ffmpeg_stream_backend.cpp | 6 ++-- src/streaming/ffmpeg_stream_backend.h | 15 +++++++++- src/streaming/session.cpp | 40 +++++++++++++++++++------ src/ui/host_probe_result_queue.cpp | 12 ++++---- src/ui/host_probe_result_queue.h | 13 +++++++- 6 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/network/host_discovery.cpp b/src/network/host_discovery.cpp index b75c263..1044b34 100644 --- a/src/network/host_discovery.cpp +++ b/src/network/host_discovery.cpp @@ -182,9 +182,20 @@ namespace { * @brief Thread-safe collector updated by the lwIP mDNS search callback. */ struct DiscoveryCollector { - mutable std::mutex mutex; ///< Guards the partial discovery state shared with the callback. std::unordered_map servicesByInstance; ///< Partial records keyed by instance domain. std::unordered_map ipv4ByDomain; ///< Resolved IPv4 addresses keyed by target host domain. + + /** + * @brief Return the mutex guarding the partial discovery state. + * + * @return Mutex used to synchronize callback writes with result assembly. + */ + [[nodiscard]] std::mutex &mutex() const { + return mutex_; + } + + private: + mutable std::mutex mutex_; ///< Guards the partial discovery state shared with the callback. }; std::string encoded_domain_to_string(const char *encodedDomain, std::size_t encodedLength) { @@ -235,7 +246,7 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex); + const std::scoped_lock lock(collector->mutex()); PendingDiscoveredService &service = collector->servicesByInstance[instanceDomain]; service.instanceDomain = std::move(instanceDomain); if (service.displayName.empty()) { @@ -248,7 +259,7 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex); + const std::scoped_lock lock(collector->mutex()); PendingDiscoveredService &service = collector->servicesByInstance[instanceDomain]; service.instanceDomain = std::move(instanceDomain); if (service.displayName.empty()) { @@ -263,7 +274,7 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex); + const std::scoped_lock lock(collector->mutex()); collector->ipv4ByDomain[std::move(domain)] = std::move(ipv4Address); } @@ -315,7 +326,7 @@ namespace { network::DiscoverHostsResult build_discover_hosts_result(const DiscoveryCollector &collector) { network::DiscoverHostsResult result {}; - const std::scoped_lock lock(collector.mutex); + const std::scoped_lock lock(collector.mutex()); for (const auto &[instanceDomain, service] : collector.servicesByInstance) { (void) instanceDomain; if (service.targetDomain.empty() || service.port == 0) { diff --git a/src/streaming/ffmpeg_stream_backend.cpp b/src/streaming/ffmpeg_stream_backend.cpp index e6087e7..7ebf4cc 100644 --- a/src/streaming/ffmpeg_stream_backend.cpp +++ b/src/streaming/ffmpeg_stream_backend.cpp @@ -457,7 +457,7 @@ namespace streaming { bool textureNeedsUpload = false; if (const std::uint64_t publishedFrameVersion = video_.publishedFrameVersion.load(); publishedFrameVersion != video_.renderedFrameVersion) { - std::scoped_lock lock(video_.frameMutex); + std::scoped_lock lock(video_.frame_mutex()); if (video_.latestFrameVersion != video_.renderedFrameVersion && video_.latestFrame.width > 0 && video_.latestFrame.height > 0) { std::swap(video_.renderFrame, video_.latestFrame); video_.renderedFrameVersion = video_.latestFrameVersion; @@ -611,7 +611,7 @@ namespace streaming { video_.renderFrame = LatestVideoFrame {}; video_.decodeFrame = LatestVideoFrame {}; { - std::scoped_lock lock(video_.frameMutex); + std::scoped_lock lock(video_.frame_mutex()); video_.latestFrame = LatestVideoFrame {}; video_.latestFrameVersion = 0; } @@ -742,7 +742,7 @@ namespace streaming { std::memcpy(nextFrame.vPlane.data(), frameToPresent->data[2], nextFrame.vPlane.size()); { - std::scoped_lock lock(video_.frameMutex); + std::scoped_lock lock(video_.frame_mutex()); std::swap(video_.latestFrame, video_.decodeFrame); ++video_.latestFrameVersion; video_.publishedFrameVersion.store(video_.latestFrameVersion); diff --git a/src/streaming/ffmpeg_stream_backend.h b/src/streaming/ffmpeg_stream_backend.h index 07a8fe9..554f3c2 100644 --- a/src/streaming/ffmpeg_stream_backend.h +++ b/src/streaming/ffmpeg_stream_backend.h @@ -277,7 +277,20 @@ namespace streaming { std::uint64_t renderedFrameVersion = 0; std::vector convertedBuffer; std::vector packetBuffer; - mutable std::mutex frameMutex; + + /** + * @brief Return the mutex guarding decoded frame publication. + * + * @return Mutex used to exchange frames between decode and render threads. + */ + [[nodiscard]] std::mutex &frame_mutex() const { + return frameMutex_; + } + + private: + mutable std::mutex frameMutex_; ///< Guards latest decoded frame publication between worker and render threads. + + public: SDL_Rect directFramebufferDestination {0, 0, 0, 0}; LatestVideoFrame latestFrame; LatestVideoFrame decodeFrame; diff --git a/src/streaming/session.cpp b/src/streaming/session.cpp index 11fce20..a4ade73 100644 --- a/src/streaming/session.cpp +++ b/src/streaming/session.cpp @@ -81,8 +81,19 @@ namespace { TTF_Font *bodyFont = nullptr; SDL_GameController *controller = nullptr; streaming::FfmpegStreamBackend mediaBackend {}; - mutable std::mutex controllerMutex; bool ttfInitialized = false; + + /** + * @brief Return the mutex guarding controller lifetime and input reads. + * + * @return Mutex used to synchronize controller open, close, and snapshot access. + */ + [[nodiscard]] std::mutex &controller_mutex() const { + return controllerMutex_; + } + + private: + mutable std::mutex controllerMutex_; ///< Guards controller lifetime and input reads across the stream loop. }; struct ControllerSnapshot { @@ -106,8 +117,19 @@ namespace { std::atomic connectionTerminated = false; std::atomic poorConnection = false; std::atomic stopRequested = false; - mutable std::mutex protocolLogMutex; std::deque recentProtocolMessages; + + /** + * @brief Return the mutex guarding retained protocol log messages. + * + * @return Mutex used to synchronize protocol message appends, reads, and resets. + */ + [[nodiscard]] std::mutex &protocol_log_mutex() const { + return protocolLogMutex_; + } + + private: + mutable std::mutex protocolLogMutex_; ///< Guards the retained protocol log buffer. }; struct StreamInputThreadState { @@ -488,7 +510,7 @@ namespace { return; } - std::scoped_lock lock(connectionState->protocolLogMutex); + std::scoped_lock lock(connectionState->protocol_log_mutex()); if (connectionState->recentProtocolMessages.size() >= MAX_CONNECTION_PROTOCOL_MESSAGES) { connectionState->recentProtocolMessages.pop_front(); } @@ -502,7 +524,7 @@ namespace { * @return Latest protocol message, or an empty string when none was recorded. */ std::string latest_connection_protocol_message(const StreamConnectionState &connectionState) { - std::scoped_lock lock(connectionState.protocolLogMutex); + std::scoped_lock lock(connectionState.protocol_log_mutex()); return connectionState.recentProtocolMessages.empty() ? std::string {} : connectionState.recentProtocolMessages.back(); } @@ -526,7 +548,7 @@ namespace { connectionState->connectionTerminated.store(false); connectionState->poorConnection.store(false); connectionState->stopRequested.store(false); - std::scoped_lock lock(connectionState->protocolLogMutex); + std::scoped_lock lock(connectionState->protocol_log_mutex()); connectionState->recentProtocolMessages.clear(); } @@ -547,7 +569,7 @@ namespace { resources->mediaBackend.shutdown(); { - std::scoped_lock lock(resources->controllerMutex); + std::scoped_lock lock(resources->controller_mutex()); close_controller(resources->controller); resources->controller = nullptr; } @@ -933,7 +955,7 @@ namespace { return; } - std::scoped_lock lock(resources->controllerMutex); + std::scoped_lock lock(resources->controller_mutex()); if (resources->controller == nullptr) { resources->controller = SDL_GameControllerOpen(deviceIndex); } @@ -944,7 +966,7 @@ namespace { return; } - std::scoped_lock lock(resources->controllerMutex); + std::scoped_lock lock(resources->controller_mutex()); if (resources->controller == nullptr) { return; } @@ -1100,7 +1122,7 @@ namespace { return {}; } - std::scoped_lock lock(resources->controllerMutex); + std::scoped_lock lock(resources->controller_mutex()); if (resources->controller == nullptr) { return {}; } diff --git a/src/ui/host_probe_result_queue.cpp b/src/ui/host_probe_result_queue.cpp index 9cee8db..c1cc9bb 100644 --- a/src/ui/host_probe_result_queue.cpp +++ b/src/ui/host_probe_result_queue.cpp @@ -15,7 +15,7 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex); + const std::scoped_lock lock(queue->mutex()); queue->targetCount = 0U; queue->publishedCount = 0U; queue->pendingResults.clear(); @@ -26,7 +26,7 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex); + const std::scoped_lock lock(queue->mutex()); queue->targetCount = targetCount; queue->publishedCount = 0U; queue->pendingResults.clear(); @@ -37,7 +37,7 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex); + const std::scoped_lock lock(queue->mutex()); queue->pendingResults.push_back(std::move(result)); ++queue->publishedCount; } @@ -47,7 +47,7 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex); + const std::scoped_lock lock(queue->mutex()); if (queue->targetCount > 0U) { --queue->targetCount; } @@ -58,14 +58,14 @@ namespace ui { return {}; } - const std::scoped_lock lock(queue->mutex); + const std::scoped_lock lock(queue->mutex()); std::vector results; results.swap(queue->pendingResults); return results; } bool host_probe_result_round_complete(const HostProbeResultQueue &queue) { - const std::scoped_lock lock(queue.mutex); + const std::scoped_lock lock(queue.mutex()); return queue.targetCount != 0U && queue.publishedCount >= queue.targetCount; } diff --git a/src/ui/host_probe_result_queue.h b/src/ui/host_probe_result_queue.h index be344b7..14a22cc 100644 --- a/src/ui/host_probe_result_queue.h +++ b/src/ui/host_probe_result_queue.h @@ -30,10 +30,21 @@ namespace ui { * @brief Thread-safe queue used to publish per-host probe results back to the shell loop. */ struct HostProbeResultQueue { - mutable std::mutex mutex; ///< Guards the current round counters and pending result queue. std::size_t targetCount = 0U; ///< Number of results expected for the active probe round. std::size_t publishedCount = 0U; ///< Number of results published so far for the active round. std::vector pendingResults; ///< Probe results waiting to be drained by the main thread. + + /** + * @brief Return the mutex guarding the probe round counters and result queue. + * + * @return Mutex used to synchronize worker publications with shell-loop drains. + */ + [[nodiscard]] std::mutex &mutex() const { + return mutex_; + } + + private: + mutable std::mutex mutex_; ///< Guards the current round counters and pending result queue. }; /** From aa58d39489eea483b5a915cc44c468fd5725b671 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:38:07 -0400 Subject: [PATCH 2/2] Additional sonar fixes --- src/network/host_discovery.cpp | 38 ++--- src/streaming/session.cpp | 223 ++++++++++++++++++++--------- src/ui/host_probe_result_queue.cpp | 36 ++--- src/ui/host_probe_result_queue.h | 25 ++-- 4 files changed, 206 insertions(+), 116 deletions(-) diff --git a/src/network/host_discovery.cpp b/src/network/host_discovery.cpp index 1044b34..ca45d14 100644 --- a/src/network/host_discovery.cpp +++ b/src/network/host_discovery.cpp @@ -182,20 +182,20 @@ namespace { * @brief Thread-safe collector updated by the lwIP mDNS search callback. */ struct DiscoveryCollector { - std::unordered_map servicesByInstance; ///< Partial records keyed by instance domain. - std::unordered_map ipv4ByDomain; ///< Resolved IPv4 addresses keyed by target host domain. - /** - * @brief Return the mutex guarding the partial discovery state. - * - * @return Mutex used to synchronize callback writes with result assembly. + * @brief Construct an empty discovery collector. */ - [[nodiscard]] std::mutex &mutex() const { - return mutex_; - } + DiscoveryCollector() = default; private: + friend void remember_ptr_service_instance(DiscoveryCollector *collector, std::string instanceDomain); + friend void remember_srv_service_target(DiscoveryCollector *collector, std::string instanceDomain, std::string targetDomain, uint16_t port); + friend void remember_a_record(DiscoveryCollector *collector, std::string domain, std::string ipv4Address); + friend network::DiscoverHostsResult build_discover_hosts_result(const DiscoveryCollector &collector); + mutable std::mutex mutex_; ///< Guards the partial discovery state shared with the callback. + std::unordered_map servicesByInstance_; ///< Partial records keyed by instance domain. + std::unordered_map ipv4ByDomain_; ///< Resolved IPv4 addresses keyed by target host domain. }; std::string encoded_domain_to_string(const char *encodedDomain, std::size_t encodedLength) { @@ -246,8 +246,8 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex()); - PendingDiscoveredService &service = collector->servicesByInstance[instanceDomain]; + const std::scoped_lock lock(collector->mutex_); + PendingDiscoveredService &service = collector->servicesByInstance_[instanceDomain]; service.instanceDomain = std::move(instanceDomain); if (service.displayName.empty()) { service.displayName = first_dns_label(service.instanceDomain); @@ -259,8 +259,8 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex()); - PendingDiscoveredService &service = collector->servicesByInstance[instanceDomain]; + const std::scoped_lock lock(collector->mutex_); + PendingDiscoveredService &service = collector->servicesByInstance_[instanceDomain]; service.instanceDomain = std::move(instanceDomain); if (service.displayName.empty()) { service.displayName = first_dns_label(service.instanceDomain); @@ -274,8 +274,8 @@ namespace { return; } - const std::scoped_lock lock(collector->mutex()); - collector->ipv4ByDomain[std::move(domain)] = std::move(ipv4Address); + const std::scoped_lock lock(collector->mutex_); + collector->ipv4ByDomain_[std::move(domain)] = std::move(ipv4Address); } /** @@ -326,14 +326,14 @@ namespace { network::DiscoverHostsResult build_discover_hosts_result(const DiscoveryCollector &collector) { network::DiscoverHostsResult result {}; - const std::scoped_lock lock(collector.mutex()); - for (const auto &[instanceDomain, service] : collector.servicesByInstance) { + const std::scoped_lock lock(collector.mutex_); + for (const auto &[instanceDomain, service] : collector.servicesByInstance_) { (void) instanceDomain; if (service.targetDomain.empty() || service.port == 0) { continue; } - const auto addressIterator = collector.ipv4ByDomain.find(service.targetDomain); - if (addressIterator == collector.ipv4ByDomain.end()) { + const auto addressIterator = collector.ipv4ByDomain_.find(service.targetDomain); + if (addressIterator == collector.ipv4ByDomain_.end()) { continue; } diff --git a/src/streaming/session.cpp b/src/streaming/session.cpp index a4ade73..b03ce03 100644 --- a/src/streaming/session.cpp +++ b/src/streaming/session.cpp @@ -75,35 +75,115 @@ namespace { static_cast(A_FLAG | B_FLAG | X_FLAG | Y_FLAG | UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG | LB_FLAG | RB_FLAG | PLAY_FLAG | BACK_FLAG | LS_CLK_FLAG | RS_CLK_FLAG | SPECIAL_FLAG); constexpr uint16_t CONTROLLER_CAPABILITIES = LI_CCAP_ANALOG_TRIGGERS | LI_CCAP_RUMBLE; + struct ControllerSnapshot { + int buttonFlags = 0; + unsigned char leftTrigger = 0; + unsigned char rightTrigger = 0; + short leftStickX = 0; + short leftStickY = 0; + short rightStickX = 0; + short rightStickY = 0; + }; + + /** + * @brief Thread-safe owner for the active stream controller handle. + */ + class StreamControllerHandle { + public: + /** + * @brief Replace the current controller handle without closing the old one. + * + * @param controller New controller handle to store. + */ + void reset(SDL_GameController *controller); + + /** + * @brief Close and clear the current controller handle. + */ + void close(); + + /** + * @brief Open a controller when no controller is currently active. + * + * @param deviceIndex SDL joystick device index reported by the add event. + */ + void open_if_needed(int deviceIndex); + + /** + * @brief Close the controller when SDL reports that its joystick was removed. + * + * @param joystickInstanceId SDL joystick instance identifier from the remove event. + */ + void close_if_removed(int joystickInstanceId); + + /** + * @brief Return the raw handle for same-thread startup polling. + * + * @return Current controller handle, or nullptr when no controller is open. + */ + [[nodiscard]] SDL_GameController *startup_poll_handle() const; + + /** + * @brief Read a synchronized controller input snapshot. + * + * @param controllerPresent Optional output set to true when a controller was read. + * @return Current controller input state, or a zeroed snapshot when unavailable. + */ + [[nodiscard]] ControllerSnapshot read_snapshot(bool *controllerPresent) const; + + private: + mutable std::mutex mutex_; ///< Guards controller lifetime and input reads across the stream loop. + SDL_GameController *controller_ = nullptr; ///< Currently opened SDL controller handle. + }; + struct StreamUiResources { SDL_Renderer *renderer = nullptr; TTF_Font *titleFont = nullptr; TTF_Font *bodyFont = nullptr; - SDL_GameController *controller = nullptr; + StreamControllerHandle controller; streaming::FfmpegStreamBackend mediaBackend {}; bool ttfInitialized = false; + }; + /** + * @brief Retained connection-protocol messages protected by their own lock. + */ + class StreamProtocolLogBuffer { + public: /** - * @brief Return the mutex guarding controller lifetime and input reads. + * @brief Append one trimmed protocol message to the rolling buffer. * - * @return Mutex used to synchronize controller open, close, and snapshot access. + * @param message Protocol log message to retain. */ - [[nodiscard]] std::mutex &controller_mutex() const { - return controllerMutex_; + void append(std::string message) { + std::scoped_lock lock(mutex_); + if (messages_.size() >= MAX_CONNECTION_PROTOCOL_MESSAGES) { + messages_.pop_front(); + } + messages_.push_back(std::move(message)); } - private: - mutable std::mutex controllerMutex_; ///< Guards controller lifetime and input reads across the stream loop. - }; + /** + * @brief Return the most recent retained protocol message. + * + * @return Latest message, or an empty string when the buffer is empty. + */ + [[nodiscard]] std::string latest() const { + std::scoped_lock lock(mutex_); + return messages_.empty() ? std::string {} : messages_.back(); + } - struct ControllerSnapshot { - int buttonFlags = 0; - unsigned char leftTrigger = 0; - unsigned char rightTrigger = 0; - short leftStickX = 0; - short leftStickY = 0; - short rightStickX = 0; - short rightStickY = 0; + /** + * @brief Remove every retained protocol message. + */ + void clear() { + std::scoped_lock lock(mutex_); + messages_.clear(); + } + + private: + mutable std::mutex mutex_; ///< Guards retained protocol messages. + std::deque messages_; ///< Rolling protocol message buffer. }; struct StreamConnectionState { @@ -117,19 +197,7 @@ namespace { std::atomic connectionTerminated = false; std::atomic poorConnection = false; std::atomic stopRequested = false; - std::deque recentProtocolMessages; - - /** - * @brief Return the mutex guarding retained protocol log messages. - * - * @return Mutex used to synchronize protocol message appends, reads, and resets. - */ - [[nodiscard]] std::mutex &protocol_log_mutex() const { - return protocolLogMutex_; - } - - private: - mutable std::mutex protocolLogMutex_; ///< Guards the retained protocol log buffer. + StreamProtocolLogBuffer protocolLog; ///< Recent protocol messages reported by Moonlight callbacks. }; struct StreamInputThreadState { @@ -510,11 +578,7 @@ namespace { return; } - std::scoped_lock lock(connectionState->protocol_log_mutex()); - if (connectionState->recentProtocolMessages.size() >= MAX_CONNECTION_PROTOCOL_MESSAGES) { - connectionState->recentProtocolMessages.pop_front(); - } - connectionState->recentProtocolMessages.push_back(std::move(message)); + connectionState->protocolLog.append(std::move(message)); } /** @@ -524,8 +588,7 @@ namespace { * @return Latest protocol message, or an empty string when none was recorded. */ std::string latest_connection_protocol_message(const StreamConnectionState &connectionState) { - std::scoped_lock lock(connectionState.protocol_log_mutex()); - return connectionState.recentProtocolMessages.empty() ? std::string {} : connectionState.recentProtocolMessages.back(); + return connectionState.protocolLog.latest(); } /** @@ -548,8 +611,7 @@ namespace { connectionState->connectionTerminated.store(false); connectionState->poorConnection.store(false); connectionState->stopRequested.store(false); - std::scoped_lock lock(connectionState->protocol_log_mutex()); - connectionState->recentProtocolMessages.clear(); + connectionState->protocolLog.clear(); } std::string build_font_path() { @@ -569,9 +631,7 @@ namespace { resources->mediaBackend.shutdown(); { - std::scoped_lock lock(resources->controller_mutex()); - close_controller(resources->controller); - resources->controller = nullptr; + resources->controller.close(); } if (resources->bodyFont != nullptr) { TTF_CloseFont(resources->bodyFont); @@ -642,7 +702,7 @@ namespace { return false; } - resources->controller = open_primary_controller(); + resources->controller.reset(open_primary_controller()); return true; } @@ -955,10 +1015,7 @@ namespace { return; } - std::scoped_lock lock(resources->controller_mutex()); - if (resources->controller == nullptr) { - resources->controller = SDL_GameControllerOpen(deviceIndex); - } + resources->controller.open_if_needed(deviceIndex); } void close_controller_if_removed(StreamUiResources *resources, int joystickInstanceId) { @@ -966,16 +1023,7 @@ namespace { return; } - std::scoped_lock lock(resources->controller_mutex()); - if (resources->controller == nullptr) { - return; - } - - SDL_Joystick *joystick = SDL_GameControllerGetJoystick(resources->controller); - if (joystick != nullptr && SDL_JoystickInstanceID(joystick) == joystickInstanceId) { - close_controller(resources->controller); - resources->controller = nullptr; - } + resources->controller.close_if_removed(joystickInstanceId); } SDL_ControllerDeviceEvent copy_controller_device_event(const SDL_Event &event) { @@ -1064,6 +1112,53 @@ namespace { return snapshot; } + void StreamControllerHandle::reset(SDL_GameController *controller) { + std::scoped_lock lock(mutex_); + controller_ = controller; + } + + void StreamControllerHandle::close() { + std::scoped_lock lock(mutex_); + close_controller(controller_); + controller_ = nullptr; + } + + void StreamControllerHandle::open_if_needed(int deviceIndex) { + std::scoped_lock lock(mutex_); + if (controller_ == nullptr) { + controller_ = SDL_GameControllerOpen(deviceIndex); + } + } + + void StreamControllerHandle::close_if_removed(int joystickInstanceId) { + std::scoped_lock lock(mutex_); + if (controller_ == nullptr) { + return; + } + + SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller_); + if (joystick != nullptr && SDL_JoystickInstanceID(joystick) == joystickInstanceId) { + close_controller(controller_); + controller_ = nullptr; + } + } + + SDL_GameController *StreamControllerHandle::startup_poll_handle() const { + return controller_; + } + + ControllerSnapshot StreamControllerHandle::read_snapshot(bool *controllerPresent) const { + std::scoped_lock lock(mutex_); + if (controller_ == nullptr) { + return {}; + } + + if (controllerPresent != nullptr) { + *controllerPresent = true; + } + return read_controller_snapshot(controller_); + } + bool controller_snapshots_match(const ControllerSnapshot &left, const ControllerSnapshot &right) { return left.buttonFlags == right.buttonFlags && left.leftTrigger == right.leftTrigger && left.rightTrigger == right.rightTrigger && left.leftStickX == right.leftStickX && left.leftStickY == right.leftStickY && left.rightStickX == right.rightStickX && left.rightStickY == right.rightStickY; @@ -1114,7 +1209,7 @@ namespace { } } - ControllerSnapshot read_controller_snapshot(StreamUiResources *resources, bool *controllerPresent) { + ControllerSnapshot read_controller_snapshot(const StreamUiResources *resources, bool *controllerPresent) { if (controllerPresent != nullptr) { *controllerPresent = false; } @@ -1122,15 +1217,7 @@ namespace { return {}; } - std::scoped_lock lock(resources->controller_mutex()); - if (resources->controller == nullptr) { - return {}; - } - - if (controllerPresent != nullptr) { - *controllerPresent = true; - } - return read_controller_snapshot(resources->controller); + return resources->controller.read_snapshot(controllerPresent); } void send_controller_snapshot_if_needed(const ControllerSnapshot &snapshot, bool controllerPresent, bool *arrivalSent, ControllerSnapshot *lastSnapshot) { @@ -1381,7 +1468,7 @@ namespace { Uint32 exitComboActivatedTick = 0U; while (!attempt.connectionState.startCompleted.load() && !attempt.connectionState.stopRequested.load()) { pump_stream_events(&attempt.resources); - update_stream_exit_combo(attempt.resources.controller, &exitComboActivatedTick, &attempt.connectionState); + update_stream_exit_combo(attempt.resources.controller.startup_poll_handle(), &exitComboActivatedTick, &attempt.connectionState); render_stream_frame(attempt.host, attempt.app, attempt.startContext, attempt.connectionState, &attempt.resources.mediaBackend, &attempt.resources); if (attempt.connectionState.stopRequested.load()) { LiInterruptConnection(); @@ -1430,7 +1517,7 @@ namespace { void poll_fallback_controller_if_needed( const SDL_Thread *inputThread, - StreamUiResources *resources, + const StreamUiResources *resources, StreamConnectionState *connectionState, Uint32 *exitComboActivatedTick, bool *controllerArrivalSent, diff --git a/src/ui/host_probe_result_queue.cpp b/src/ui/host_probe_result_queue.cpp index c1cc9bb..65e9371 100644 --- a/src/ui/host_probe_result_queue.cpp +++ b/src/ui/host_probe_result_queue.cpp @@ -15,10 +15,10 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex()); - queue->targetCount = 0U; - queue->publishedCount = 0U; - queue->pendingResults.clear(); + const std::scoped_lock lock(queue->mutex_); + queue->targetCount_ = 0U; + queue->publishedCount_ = 0U; + queue->pendingResults_.clear(); } void begin_host_probe_result_round(HostProbeResultQueue *queue, std::size_t targetCount) { @@ -26,10 +26,10 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex()); - queue->targetCount = targetCount; - queue->publishedCount = 0U; - queue->pendingResults.clear(); + const std::scoped_lock lock(queue->mutex_); + queue->targetCount_ = targetCount; + queue->publishedCount_ = 0U; + queue->pendingResults_.clear(); } void publish_host_probe_result(HostProbeResultQueue *queue, HostProbeResult result) { @@ -37,9 +37,9 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex()); - queue->pendingResults.push_back(std::move(result)); - ++queue->publishedCount; + const std::scoped_lock lock(queue->mutex_); + queue->pendingResults_.push_back(std::move(result)); + ++queue->publishedCount_; } void skip_host_probe_result_target(HostProbeResultQueue *queue) { @@ -47,9 +47,9 @@ namespace ui { return; } - const std::scoped_lock lock(queue->mutex()); - if (queue->targetCount > 0U) { - --queue->targetCount; + const std::scoped_lock lock(queue->mutex_); + if (queue->targetCount_ > 0U) { + --queue->targetCount_; } } @@ -58,15 +58,15 @@ namespace ui { return {}; } - const std::scoped_lock lock(queue->mutex()); + const std::scoped_lock lock(queue->mutex_); std::vector results; - results.swap(queue->pendingResults); + results.swap(queue->pendingResults_); return results; } bool host_probe_result_round_complete(const HostProbeResultQueue &queue) { - const std::scoped_lock lock(queue.mutex()); - return queue.targetCount != 0U && queue.publishedCount >= queue.targetCount; + const std::scoped_lock lock(queue.mutex_); + return queue.targetCount_ != 0U && queue.publishedCount_ >= queue.targetCount_; } } // namespace ui diff --git a/src/ui/host_probe_result_queue.h b/src/ui/host_probe_result_queue.h index 14a22cc..65a2385 100644 --- a/src/ui/host_probe_result_queue.h +++ b/src/ui/host_probe_result_queue.h @@ -29,22 +29,25 @@ namespace ui { /** * @brief Thread-safe queue used to publish per-host probe results back to the shell loop. */ - struct HostProbeResultQueue { - std::size_t targetCount = 0U; ///< Number of results expected for the active probe round. - std::size_t publishedCount = 0U; ///< Number of results published so far for the active round. - std::vector pendingResults; ///< Probe results waiting to be drained by the main thread. - + class HostProbeResultQueue { + public: /** - * @brief Return the mutex guarding the probe round counters and result queue. - * - * @return Mutex used to synchronize worker publications with shell-loop drains. + * @brief Construct an empty probe result queue. */ - [[nodiscard]] std::mutex &mutex() const { - return mutex_; - } + HostProbeResultQueue() = default; private: + friend void reset_host_probe_result_queue(HostProbeResultQueue *queue); + friend void begin_host_probe_result_round(HostProbeResultQueue *queue, std::size_t targetCount); + friend void publish_host_probe_result(HostProbeResultQueue *queue, HostProbeResult result); + friend void skip_host_probe_result_target(HostProbeResultQueue *queue); + friend std::vector drain_host_probe_results(HostProbeResultQueue *queue); + friend bool host_probe_result_round_complete(const HostProbeResultQueue &queue); + mutable std::mutex mutex_; ///< Guards the current round counters and pending result queue. + std::size_t targetCount_ = 0U; ///< Number of results expected for the active probe round. + std::size_t publishedCount_ = 0U; ///< Number of results published so far for the active round. + std::vector pendingResults_; ///< Probe results waiting to be drained by the main thread. }; /**