From 27958415c572575ff11f844a74f4f3f2253502ee Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 8 Jun 2023 23:07:52 -0500 Subject: [PATCH] Synchronize radar sweep updates to timeline manager --- scwx-qt/source/scwx/qt/main/main_window.cpp | 18 ++ .../scwx/qt/manager/timeline_manager.cpp | 165 ++++++++++++++++-- .../scwx/qt/manager/timeline_manager.hpp | 7 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 3 + scwx-qt/source/scwx/qt/map/map_widget.hpp | 1 + 5 files changed, 179 insertions(+), 15 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 3e126487..07945d18 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -560,6 +560,7 @@ void MainWindowImpl::ConfigureMapLayout() vs->setHandleWidth(1); maps_.resize(mapCount); + timelineManager_->SetMapCount(mapCount); auto MoveSplitter = [=, this](int /*pos*/, int /*index*/) { @@ -720,6 +721,23 @@ void MainWindowImpl::ConnectAnimationSignals() map->SetAutoUpdate(isLive); } }); + + for (std::size_t i = 0; i < maps_.size(); i++) + { + connect(maps_[i], + &map::MapWidget::RadarSweepUpdated, + timelineManager_.get(), + [=, this]() { timelineManager_->ReceiveRadarSweepUpdated(i); }); + connect(maps_[i], + &map::MapWidget::RadarSweepNotUpdated, + timelineManager_.get(), + [=, this](types::NoUpdateReason reason) + { timelineManager_->ReceiveRadarSweepNotUpdated(i, reason); }); + connect(maps_[i], + &map::MapWidget::WidgetPainted, + timelineManager_.get(), + [=, this]() { timelineManager_->ReceiveMapWidgetPainted(i); }); + } } void MainWindowImpl::ConnectOtherSignals() diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 77225c6f..606a58ca 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -26,6 +27,9 @@ enum class Direction Next }; +// Wait up to 5 seconds for radar sweeps to update +static constexpr std::chrono::seconds kRadarSweepMonitorTimeout_ {5}; + class TimelineManager::Impl { public: @@ -49,11 +53,16 @@ public: std::shared_ptr radarProductManager, const std::set& volumeTimes); + void RadarSweepMonitorDisable(); + void RadarSweepMonitorReset(); + void RadarSweepMonitorWait(std::unique_lock& lock); + void Pause(); void Play(); void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); void Step(Direction direction); + std::size_t mapCount_ {0}; std::string radarSite_ {"?"}; std::string previousRadarSite_ {"?"}; std::chrono::system_clock::time_point pinnedTime_ {}; @@ -63,6 +72,12 @@ public: std::chrono::minutes loopTime_ {30}; double loopSpeed_ {5.0}; + bool radarSweepMonitorActive_ {false}; + std::mutex radarSweepMonitorMutex_ {}; + std::condition_variable radarSweepMonitorCondition_ {}; + std::set radarSweepsUpdated_ {}; + std::set radarSweepsComplete_ {}; + types::AnimationState animationState_ {types::AnimationState::Pause}; boost::asio::steady_timer animationTimer_ {scwx::util::io_context()}; std::mutex animationTimerMutex_ {}; @@ -73,6 +88,11 @@ public: TimelineManager::TimelineManager() : p(std::make_unique(this)) {} TimelineManager::~TimelineManager() = default; +void TimelineManager::SetMapCount(std::size_t mapCount) +{ + p->mapCount_ = mapCount; +} + void TimelineManager::SetRadarSite(const std::string& radarSite) { if (p->radarSite_ == radarSite) @@ -217,6 +237,87 @@ void TimelineManager::AnimationStepEnd() } } +void TimelineManager::Impl::RadarSweepMonitorDisable() +{ + radarSweepMonitorActive_ = false; +} + +void TimelineManager::Impl::RadarSweepMonitorReset() +{ + radarSweepsUpdated_.clear(); + radarSweepsComplete_.clear(); + + radarSweepMonitorActive_ = true; +} + +void TimelineManager::Impl::RadarSweepMonitorWait( + std::unique_lock& lock) +{ + radarSweepMonitorCondition_.wait_for(lock, kRadarSweepMonitorTimeout_); + radarSweepMonitorActive_ = false; +} + +void TimelineManager::ReceiveRadarSweepUpdated(std::size_t mapIndex) +{ + if (!p->radarSweepMonitorActive_) + { + return; + } + + std::unique_lock lock {p->radarSweepMonitorMutex_}; + + // Radar sweep is updated, but still needs painted + p->radarSweepsUpdated_.insert(mapIndex); +} + +void TimelineManager::ReceiveRadarSweepNotUpdated( + std::size_t mapIndex, types::NoUpdateReason /* reason */) +{ + if (!p->radarSweepMonitorActive_) + { + return; + } + + std::unique_lock lock {p->radarSweepMonitorMutex_}; + + // Radar sweep is complete, no painting will occur + p->radarSweepsComplete_.insert(mapIndex); + + // If all sweeps have completed rendering + if (p->radarSweepsComplete_.size() == p->mapCount_) + { + // Notify monitors + p->radarSweepMonitorActive_ = false; + p->radarSweepMonitorCondition_.notify_all(); + } +} + +void TimelineManager::ReceiveMapWidgetPainted(std::size_t mapIndex) +{ + if (!p->radarSweepMonitorActive_) + { + return; + } + + std::unique_lock lock {p->radarSweepMonitorMutex_}; + + // If the radar sweep has been updated + if (p->radarSweepsUpdated_.contains(mapIndex)) + { + // Mark the radar sweep complete + p->radarSweepsUpdated_.erase(mapIndex); + p->radarSweepsComplete_.insert(mapIndex); + + // If all sweeps have completed rendering + if (p->radarSweepsComplete_.size() == p->mapCount_) + { + // Notify monitors + p->radarSweepMonitorActive_ = false; + p->radarSweepMonitorCondition_.notify_all(); + } + } +} + void TimelineManager::Impl::Pause() { // Cancel animation @@ -306,12 +407,7 @@ void TimelineManager::Impl::Play() newTime = currentTime + 1min; } - // Unlock prior to selecting time - lock.unlock(); - - // Select the time - SelectTime(newTime); - + // Calculate the interval until the next update, prior to selecting std::chrono::milliseconds interval; if (newTime != endTime) { @@ -325,9 +421,16 @@ void TimelineManager::Impl::Play() interval = std::chrono::milliseconds(2500); } + animationTimer_.expires_after(interval); + + // Unlock prior to selecting time + lock.unlock(); + + // Select the time + SelectTime(newTime); + std::unique_lock animationTimerLock {animationTimerMutex_}; - animationTimer_.expires_after(interval); animationTimer_.async_wait( [this](const boost::system::error_code& e) { @@ -360,15 +463,30 @@ void TimelineManager::Impl::SelectTime( } else if (selectedTime == std::chrono::system_clock::time_point {}) { - // If a default time point is given, reset to a live view - selectedTime_ = selectedTime; - adjustedTime_ = selectedTime; + scwx::util::async( + [=, this]() + { + // Take a lock for time selection + std::unique_lock lock {selectTimeMutex_}; - logger_->debug("Time updated: Live"); + // If a default time point is given, reset to a live view + selectedTime_ = selectedTime; + adjustedTime_ = selectedTime; - Q_EMIT self_->LiveStateUpdated(true); - Q_EMIT self_->VolumeTimeUpdated(selectedTime); - Q_EMIT self_->SelectedTimeUpdated(selectedTime); + logger_->debug("Time updated: Live"); + + std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; + + // Reset radar sweep monitor in preparation for update + RadarSweepMonitorReset(); + + Q_EMIT self_->LiveStateUpdated(true); + Q_EMIT self_->VolumeTimeUpdated(selectedTime); + Q_EMIT self_->SelectedTimeUpdated(selectedTime); + + // Wait for radar sweeps to update + RadarSweepMonitorWait(radarSweepMonitorLock); + }); return; } @@ -395,9 +513,15 @@ void TimelineManager::Impl::SelectTime( // The timeline is no longer live Q_EMIT self_->LiveStateUpdated(false); + bool volumeTimeUpdated = false; + + std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; + + // Reset radar sweep monitor in preparation for update + RadarSweepMonitorReset(); + if (elementPtr != nullptr) { - // If the adjusted time changed, or if a new radar site has been // selected if (adjustedTime_ != *elementPtr || @@ -409,6 +533,7 @@ void TimelineManager::Impl::SelectTime( logger_->debug("Volume time updated: {}", scwx::util::TimeString(adjustedTime_)); + volumeTimeUpdated = true; Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); } } @@ -426,6 +551,16 @@ void TimelineManager::Impl::SelectTime( Q_EMIT self_->SelectedTimeUpdated(selectedTime); previousRadarSite_ = radarSite_; + + if (volumeTimeUpdated) + { + // Wait for radar sweeps to update + RadarSweepMonitorWait(radarSweepMonitorLock); + } + else + { + RadarSweepMonitorDisable(); + } }); } diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index a29278b9..0751fed1 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -24,6 +24,8 @@ public: static std::shared_ptr Instance(); + void SetMapCount(std::size_t mapCount); + public slots: void SetRadarSite(const std::string& radarSite); @@ -39,6 +41,11 @@ public slots: void AnimationStepNext(); void AnimationStepEnd(); + void ReceiveRadarSweepUpdated(std::size_t mapIndex); + void ReceiveRadarSweepNotUpdated(std::size_t mapIndex, + types::NoUpdateReason reason); + void ReceiveMapWidgetPainted(std::size_t mapIndex); + signals: void SelectedTimeUpdated(std::chrono::system_clock::time_point dateTime); void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index a5283c7e..6d0146c0 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -859,6 +859,9 @@ void MapWidget::paintGL() // Render ImGui Frame ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Paint complete + Q_EMIT WidgetPainted(); } void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 7126f0de..eb28dfc6 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -145,6 +145,7 @@ signals: void RadarSiteUpdated(std::shared_ptr radarSite); void RadarSweepUpdated(); void RadarSweepNotUpdated(types::NoUpdateReason reason); + void WidgetPainted(); }; } // namespace map