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);
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()

View file

@ -5,6 +5,7 @@
#include <scwx/util/threads.hpp>
#include <scwx/util/time.hpp>
#include <condition_variable>
#include <mutex>
#include <boost/asio/steady_timer.hpp>
@ -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<manager::RadarProductManager> radarProductManager,
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 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<std::size_t> radarSweepsUpdated_ {};
std::set<std::size_t> 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<Impl>(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<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()
{
// 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,16 +463,31 @@ void TimelineManager::Impl::SelectTime(
}
else if (selectedTime == std::chrono::system_clock::time_point {})
{
scwx::util::async(
[=, this]()
{
// Take a lock for time selection
std::unique_lock lock {selectTimeMutex_};
// If a default time point is given, reset to a live view
selectedTime_ = selectedTime;
adjustedTime_ = 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();
}
});
}

View file

@ -24,6 +24,8 @@ public:
static std::shared_ptr<TimelineManager> 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);

View file

@ -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)
@ -1010,6 +1013,10 @@ void MapWidgetImpl::RadarProductViewConnect()
Q_EMIT widget_->RadarSweepUpdated();
},
Qt::QueuedConnection);
connect(radarProductView.get(),
&view::RadarProductView::SweepNotComputed,
widget_,
&MapWidget::RadarSweepNotUpdated);
}
}
@ -1027,6 +1034,10 @@ void MapWidgetImpl::RadarProductViewDisconnect()
&view::RadarProductView::SweepComputed,
this,
nullptr);
disconnect(radarProductView.get(),
&view::RadarProductView::SweepNotComputed,
widget_,
nullptr);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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