Merge pull request #58 from dpaulat/feature/animation-sync

Synchronize Radar Sweep to Timeline Manager
This commit is contained in:
Dan Paulat 2023-06-08 23:19:03 -05:00 committed by GitHub
commit a5df5a237e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 223 additions and 16 deletions

View file

@ -560,6 +560,7 @@ void MainWindowImpl::ConfigureMapLayout()
vs->setHandleWidth(1); vs->setHandleWidth(1);
maps_.resize(mapCount); maps_.resize(mapCount);
timelineManager_->SetMapCount(mapCount);
auto MoveSplitter = [=, this](int /*pos*/, int /*index*/) auto MoveSplitter = [=, this](int /*pos*/, int /*index*/)
{ {
@ -720,6 +721,23 @@ void MainWindowImpl::ConnectAnimationSignals()
map->SetAutoUpdate(isLive); 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() void MainWindowImpl::ConnectOtherSignals()

View file

@ -5,6 +5,7 @@
#include <scwx/util/threads.hpp> #include <scwx/util/threads.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
#include <condition_variable>
#include <mutex> #include <mutex>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
@ -26,6 +27,9 @@ enum class Direction
Next Next
}; };
// Wait up to 5 seconds for radar sweeps to update
static constexpr std::chrono::seconds kRadarSweepMonitorTimeout_ {5};
class TimelineManager::Impl class TimelineManager::Impl
{ {
public: public:
@ -49,11 +53,16 @@ public:
std::shared_ptr<manager::RadarProductManager> radarProductManager, std::shared_ptr<manager::RadarProductManager> radarProductManager,
const std::set<std::chrono::system_clock::time_point>& volumeTimes); const std::set<std::chrono::system_clock::time_point>& volumeTimes);
void RadarSweepMonitorDisable();
void RadarSweepMonitorReset();
void RadarSweepMonitorWait(std::unique_lock<std::mutex>& lock);
void Pause(); void Pause();
void Play(); void Play();
void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); void SelectTime(std::chrono::system_clock::time_point selectedTime = {});
void Step(Direction direction); void Step(Direction direction);
std::size_t mapCount_ {0};
std::string radarSite_ {"?"}; std::string radarSite_ {"?"};
std::string previousRadarSite_ {"?"}; std::string previousRadarSite_ {"?"};
std::chrono::system_clock::time_point pinnedTime_ {}; std::chrono::system_clock::time_point pinnedTime_ {};
@ -63,6 +72,12 @@ public:
std::chrono::minutes loopTime_ {30}; std::chrono::minutes loopTime_ {30};
double loopSpeed_ {5.0}; double loopSpeed_ {5.0};
bool radarSweepMonitorActive_ {false};
std::mutex radarSweepMonitorMutex_ {};
std::condition_variable radarSweepMonitorCondition_ {};
std::set<std::size_t> radarSweepsUpdated_ {};
std::set<std::size_t> radarSweepsComplete_ {};
types::AnimationState animationState_ {types::AnimationState::Pause}; types::AnimationState animationState_ {types::AnimationState::Pause};
boost::asio::steady_timer animationTimer_ {scwx::util::io_context()}; boost::asio::steady_timer animationTimer_ {scwx::util::io_context()};
std::mutex animationTimerMutex_ {}; std::mutex animationTimerMutex_ {};
@ -73,6 +88,11 @@ public:
TimelineManager::TimelineManager() : p(std::make_unique<Impl>(this)) {} TimelineManager::TimelineManager() : p(std::make_unique<Impl>(this)) {}
TimelineManager::~TimelineManager() = default; TimelineManager::~TimelineManager() = default;
void TimelineManager::SetMapCount(std::size_t mapCount)
{
p->mapCount_ = mapCount;
}
void TimelineManager::SetRadarSite(const std::string& radarSite) void TimelineManager::SetRadarSite(const std::string& radarSite)
{ {
if (p->radarSite_ == 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<std::mutex>& 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() void TimelineManager::Impl::Pause()
{ {
// Cancel animation // Cancel animation
@ -306,12 +407,7 @@ void TimelineManager::Impl::Play()
newTime = currentTime + 1min; newTime = currentTime + 1min;
} }
// Unlock prior to selecting time // Calculate the interval until the next update, prior to selecting
lock.unlock();
// Select the time
SelectTime(newTime);
std::chrono::milliseconds interval; std::chrono::milliseconds interval;
if (newTime != endTime) if (newTime != endTime)
{ {
@ -325,9 +421,16 @@ void TimelineManager::Impl::Play()
interval = std::chrono::milliseconds(2500); 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_}; std::unique_lock animationTimerLock {animationTimerMutex_};
animationTimer_.expires_after(interval);
animationTimer_.async_wait( animationTimer_.async_wait(
[this](const boost::system::error_code& e) [this](const boost::system::error_code& e)
{ {
@ -360,15 +463,30 @@ void TimelineManager::Impl::SelectTime(
} }
else if (selectedTime == std::chrono::system_clock::time_point {}) else if (selectedTime == std::chrono::system_clock::time_point {})
{ {
// If a default time point is given, reset to a live view scwx::util::async(
selectedTime_ = selectedTime; [=, this]()
adjustedTime_ = selectedTime; {
// 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); logger_->debug("Time updated: Live");
Q_EMIT self_->VolumeTimeUpdated(selectedTime);
Q_EMIT self_->SelectedTimeUpdated(selectedTime); 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; return;
} }
@ -395,9 +513,15 @@ void TimelineManager::Impl::SelectTime(
// The timeline is no longer live // The timeline is no longer live
Q_EMIT self_->LiveStateUpdated(false); 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 (elementPtr != nullptr)
{ {
// If the adjusted time changed, or if a new radar site has been // If the adjusted time changed, or if a new radar site has been
// selected // selected
if (adjustedTime_ != *elementPtr || if (adjustedTime_ != *elementPtr ||
@ -409,6 +533,7 @@ void TimelineManager::Impl::SelectTime(
logger_->debug("Volume time updated: {}", logger_->debug("Volume time updated: {}",
scwx::util::TimeString(adjustedTime_)); scwx::util::TimeString(adjustedTime_));
volumeTimeUpdated = true;
Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); Q_EMIT self_->VolumeTimeUpdated(adjustedTime_);
} }
} }
@ -426,6 +551,16 @@ void TimelineManager::Impl::SelectTime(
Q_EMIT self_->SelectedTimeUpdated(selectedTime); Q_EMIT self_->SelectedTimeUpdated(selectedTime);
previousRadarSite_ = radarSite_; previousRadarSite_ = radarSite_;
if (volumeTimeUpdated)
{
// Wait for radar sweeps to update
RadarSweepMonitorWait(radarSweepMonitorLock);
}
else
{
RadarSweepMonitorDisable();
}
}); });
} }

View file

@ -24,6 +24,8 @@ public:
static std::shared_ptr<TimelineManager> Instance(); static std::shared_ptr<TimelineManager> Instance();
void SetMapCount(std::size_t mapCount);
public slots: public slots:
void SetRadarSite(const std::string& radarSite); void SetRadarSite(const std::string& radarSite);
@ -39,6 +41,11 @@ public slots:
void AnimationStepNext(); void AnimationStepNext();
void AnimationStepEnd(); void AnimationStepEnd();
void ReceiveRadarSweepUpdated(std::size_t mapIndex);
void ReceiveRadarSweepNotUpdated(std::size_t mapIndex,
types::NoUpdateReason reason);
void ReceiveMapWidgetPainted(std::size_t mapIndex);
signals: signals:
void SelectedTimeUpdated(std::chrono::system_clock::time_point dateTime); void SelectedTimeUpdated(std::chrono::system_clock::time_point dateTime);
void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime); void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime);

View file

@ -859,6 +859,9 @@ void MapWidget::paintGL()
// Render ImGui Frame // Render ImGui Frame
ImGui::Render(); ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Paint complete
Q_EMIT WidgetPainted();
} }
void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange)
@ -1010,6 +1013,10 @@ void MapWidgetImpl::RadarProductViewConnect()
Q_EMIT widget_->RadarSweepUpdated(); Q_EMIT widget_->RadarSweepUpdated();
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
connect(radarProductView.get(),
&view::RadarProductView::SweepNotComputed,
widget_,
&MapWidget::RadarSweepNotUpdated);
} }
} }
@ -1027,6 +1034,10 @@ void MapWidgetImpl::RadarProductViewDisconnect()
&view::RadarProductView::SweepComputed, &view::RadarProductView::SweepComputed,
this, this,
nullptr); nullptr);
disconnect(radarProductView.get(),
&view::RadarProductView::SweepNotComputed,
widget_,
nullptr);
} }
} }

View file

@ -2,6 +2,7 @@
#include <scwx/common/products.hpp> #include <scwx/common/products.hpp>
#include <scwx/qt/config/radar_site.hpp> #include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/types/map_types.hpp>
#include <scwx/qt/types/radar_product_record.hpp> #include <scwx/qt/types/radar_product_record.hpp>
#include <chrono> #include <chrono>
@ -143,6 +144,8 @@ signals:
void MapStyleChanged(const std::string& styleName); void MapStyleChanged(const std::string& styleName);
void RadarSiteUpdated(std::shared_ptr<config::RadarSite> radarSite); void RadarSiteUpdated(std::shared_ptr<config::RadarSite> radarSite);
void RadarSweepUpdated(); void RadarSweepUpdated();
void RadarSweepNotUpdated(types::NoUpdateReason reason);
void WidgetPainted();
}; };
} // namespace map } // namespace map

View file

@ -21,6 +21,14 @@ enum class MapTime
Archive Archive
}; };
enum class NoUpdateReason
{
NoChange,
NotLoaded,
InvalidProduct,
InvalidData
};
std::string GetMapTimeName(MapTime mapTime); std::string GetMapTimeName(MapTime mapTime);
} // namespace types } // namespace types

View file

@ -402,6 +402,7 @@ void Level2ProductView::ComputeSweep()
if (p->dataBlockType_ == wsr88d::rda::DataBlockType::Unknown) if (p->dataBlockType_ == wsr88d::rda::DataBlockType::Unknown)
{ {
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidProduct);
return; return;
} }
@ -423,8 +424,14 @@ void Level2ProductView::ComputeSweep()
SelectTime(foundTime); SelectTime(foundTime);
} }
if (radarData == nullptr || radarData == p->elevationScan_) if (radarData == nullptr)
{ {
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
return;
}
if (radarData == p->elevationScan_)
{
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
return; return;
} }
@ -443,6 +450,7 @@ void Level2ProductView::ComputeSweep()
{ {
logger_->warn("No moment data for {}", logger_->warn("No moment data for {}",
common::GetLevel2Name(p->product_)); common::GetLevel2Name(p->product_));
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }

View file

@ -133,6 +133,7 @@ void Level3RadialView::ComputeSweep()
if (message == nullptr) if (message == nullptr)
{ {
logger_->debug("Level 3 data not found"); logger_->debug("Level 3 data not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
return; return;
} }
@ -142,11 +143,13 @@ void Level3RadialView::ComputeSweep()
if (gpm == nullptr) if (gpm == nullptr)
{ {
logger_->warn("Graphic Product Message not found"); logger_->warn("Graphic Product Message not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
else if (gpm == graphic_product_message()) else if (gpm == graphic_product_message())
{ {
// Skip if this is the message we previously processed // Skip if this is the message we previously processed
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
return; return;
} }
set_graphic_product_message(gpm); set_graphic_product_message(gpm);
@ -160,6 +163,7 @@ void Level3RadialView::ComputeSweep()
if (descriptionBlock == nullptr || symbologyBlock == nullptr) if (descriptionBlock == nullptr || symbologyBlock == nullptr)
{ {
logger_->warn("Missing blocks"); logger_->warn("Missing blocks");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -168,6 +172,7 @@ void Level3RadialView::ComputeSweep()
if (numberOfLayers < 1) if (numberOfLayers < 1)
{ {
logger_->warn("No layers present in symbology block"); logger_->warn("No layers present in symbology block");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -219,6 +224,7 @@ void Level3RadialView::ComputeSweep()
else else
{ {
logger_->debug("No radial data found"); logger_->debug("No radial data found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -227,6 +233,7 @@ void Level3RadialView::ComputeSweep()
if (radials < 1 || radials > 720) if (radials < 1 || radials > 720)
{ {
logger_->warn("Unsupported number of radials: {}", radials); logger_->warn("Unsupported number of radials: {}", radials);
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -254,6 +261,7 @@ void Level3RadialView::ComputeSweep()
if (gates < 1) if (gates < 1)
{ {
logger_->warn("No range bins in radial data"); logger_->warn("No range bins in radial data");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }

View file

@ -116,6 +116,7 @@ void Level3RasterView::ComputeSweep()
if (message == nullptr) if (message == nullptr)
{ {
logger_->debug("Level 3 data not found"); logger_->debug("Level 3 data not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
return; return;
} }
@ -125,11 +126,13 @@ void Level3RasterView::ComputeSweep()
if (gpm == nullptr) if (gpm == nullptr)
{ {
logger_->warn("Graphic Product Message not found"); logger_->warn("Graphic Product Message not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
else if (gpm == graphic_product_message()) else if (gpm == graphic_product_message())
{ {
// Skip if this is the message we previously processed // Skip if this is the message we previously processed
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
return; return;
} }
set_graphic_product_message(gpm); set_graphic_product_message(gpm);
@ -143,6 +146,7 @@ void Level3RasterView::ComputeSweep()
if (descriptionBlock == nullptr || symbologyBlock == nullptr) if (descriptionBlock == nullptr || symbologyBlock == nullptr)
{ {
logger_->warn("Missing blocks"); logger_->warn("Missing blocks");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -151,6 +155,7 @@ void Level3RasterView::ComputeSweep()
if (numberOfLayers < 1) if (numberOfLayers < 1)
{ {
logger_->warn("No layers present in symbology block"); logger_->warn("No layers present in symbology block");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -182,6 +187,7 @@ void Level3RasterView::ComputeSweep()
if (rasterData == nullptr) if (rasterData == nullptr)
{ {
logger_->debug("No raster data found"); logger_->debug("No raster data found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }
@ -196,6 +202,7 @@ void Level3RasterView::ComputeSweep()
if (maxColumns == 0) if (maxColumns == 0)
{ {
logger_->debug("No raster bins found"); logger_->debug("No raster bins found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return; return;
} }

View file

@ -3,6 +3,7 @@
#include <scwx/common/color_table.hpp> #include <scwx/common/color_table.hpp>
#include <scwx/common/products.hpp> #include <scwx/common/products.hpp>
#include <scwx/qt/manager/radar_product_manager.hpp> #include <scwx/qt/manager/radar_product_manager.hpp>
#include <scwx/qt/types/map_types.hpp>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
@ -75,6 +76,7 @@ protected slots:
signals: signals:
void ColorTableUpdated(); void ColorTableUpdated();
void SweepComputed(); void SweepComputed();
void SweepNotComputed(types::NoUpdateReason reason);
private: private:
std::unique_ptr<RadarProductViewImpl> p; std::unique_ptr<RadarProductViewImpl> p;