From 655fb5042f7e6d3b758e7568a7ac91151195cee5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 28 Oct 2022 18:36:18 -0500 Subject: [PATCH] Create a timer to expire alerts and remove them from the map --- scwx-qt/source/scwx/qt/map/alert_layer.cpp | 133 ++++++++++++++++++--- 1 file changed, 118 insertions(+), 15 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index 77563d5f..b3d6f238 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -1,10 +1,13 @@ #include #include #include +#include +#include #include #include +#include #include namespace scwx @@ -52,7 +55,10 @@ class AlertLayerHandler : public QObject { Q_OBJECT public : explicit AlertLayerHandler() : - alertSourceMap_ {}, featureMap_ {} + alertUpdateTimer_ {util::io_context()}, + alertUpdateTimerActive_ {true}, + alertSourceMap_ {}, + featureMap_ {} { for (auto& phenomenon : kAlertPhenomena_) { @@ -66,8 +72,9 @@ class AlertLayerHandler : public QObject connect(&manager::TextEventManager::Instance(), &manager::TextEventManager::AlertUpdated, this, - &AlertLayerHandler::HandleAlert, - Qt::QueuedConnection); + &AlertLayerHandler::HandleAlert); + + ScheduleAlertUpdate(); } ~AlertLayerHandler() = default; @@ -75,18 +82,25 @@ class AlertLayerHandler : public QObject std::list* FeatureList(awips::Phenomenon phenomenon, bool alertActive); + void HandleAlert(const types::TextEventKey& key, size_t messageIndex); + void CancelAlertTimer(); + void ScheduleAlertUpdate(); + void UpdateAlerts(const boost::system::error_code& e = {}); + + boost::asio::steady_timer alertUpdateTimer_; + bool alertUpdateTimerActive_; std::unordered_map, QVariantMap, AlertTypeHash>> alertSourceMap_; - std::unordered_multimap< - types::TextEventKey, - std::tuple::iterator>, - types::TextEventHash> + std::unordered_multimap::iterator, + std::chrono::system_clock::time_point>, + types::TextEventHash> featureMap_; std::shared_mutex alertMutex_; @@ -106,7 +120,13 @@ public: this, &AlertLayerImpl::UpdateSource); } - ~AlertLayerImpl() = default; + ~AlertLayerImpl() + { + // Alert layer should never be destructed until the end of execution, this + // allows the alert timer to be cancelled and application cleanup to + // continue + AlertLayerHandler::Instance().CancelAlertTimer(); + }; void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); @@ -260,7 +280,7 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, auto existingFeatures = featureMap_.equal_range(key); for (auto it = existingFeatures.first; it != existingFeatures.second; ++it) { - auto& [phenomenon, alertActive, featureIt] = it->second; + auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; auto featureList = FeatureList(phenomenon, alertActive); if (featureList != nullptr) { @@ -283,6 +303,7 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, auto& vtec = segment->header_->vtecString_.front(); auto action = vtec.pVtec_.action(); awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); + auto eventEnd = vtec.pVtec_.event_end(); bool alertActive = (action != awips::PVtec::Action::Canceled); auto featureList = FeatureList(phenomenon, alertActive); @@ -294,10 +315,10 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, CreateFeature(segment->codedLocation_.value())); // Store iterator for created feature in feature map - featureMap_.emplace( - std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple(phenomenon, alertActive, featureIt)); + featureMap_.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple( + phenomenon, alertActive, featureIt, eventEnd)); // Mark alert type as updated alertsUpdated.emplace(phenomenon, alertActive); @@ -314,6 +335,88 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, } } +void AlertLayerHandler::UpdateAlerts(const boost::system::error_code& e) +{ + logger_->trace("UpdateAlerts"); + + if (e == boost::asio::error::operation_aborted) + { + logger_->debug("Alert update timer cancelled"); + return; + } + else if (e != boost::system::errc::success) + { + logger_->warn("Alert update timer error: {}", e.message()); + return; + } + + // Take a unique lock before modifying feature lists + std::unique_lock lock(alertMutex_); + + std::unordered_set, + AlertTypeHash>> + alertsUpdated {}; + + // Evaluate each rendered feature for expiration + for (auto it = featureMap_.begin(); it != featureMap_.end();) + { + auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; + + // If the event has ended, remove it from the feature list + if (eventEnd < std::chrono::system_clock::now()) + { + logger_->debug("Alert expired: {}", it->first.ToString()); + + auto featureList = FeatureList(phenomenon, alertActive); + if (featureList != nullptr) + { + // Remove existing feature for key + featureList->erase(featureIt); + + // Mark alert type as updated + alertsUpdated.emplace(phenomenon, alertActive); + } + + // Erase current item and increment iterator + it = featureMap_.erase(it); + } + else + { + // Current item is not expired, continue + ++it; + } + } + + // Release the lock after completing feature list updates + lock.unlock(); + + for (auto& alert : alertsUpdated) + { + // Emit signal for each updated alert type + emit AlertsUpdated(alert.first, alert.second); + } + + if (alertUpdateTimerActive_) + { + ScheduleAlertUpdate(); + } +} + +void AlertLayerHandler::ScheduleAlertUpdate() +{ + using namespace std::chrono; + + alertUpdateTimer_.expires_after(15s); + alertUpdateTimer_.async_wait([=](const boost::system::error_code& e) + { UpdateAlerts(e); }); +} + +void AlertLayerHandler::CancelAlertTimer() +{ + alertUpdateTimerActive_ = false; + alertUpdateTimer_.cancel(); +} + void AlertLayerImpl::UpdateSource(awips::Phenomenon phenomenon, bool alertActive) {