From 97693fdace73711b11b889dca25ab42acd4085cd Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Tue, 15 Apr 2025 13:12:49 -0400 Subject: [PATCH 1/4] Add a maximum number of forward/backward time steps that can be queued --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/manager/timeline_manager.cpp | 22 +++++-- scwx-qt/source/scwx/qt/util/queue_counter.cpp | 49 ++++++++++++++ scwx-qt/source/scwx/qt/util/queue_counter.hpp | 64 +++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/util/queue_counter.cpp create mode 100644 scwx-qt/source/scwx/qt/util/queue_counter.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 4c0ea7cd..30e8adb9 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -372,6 +372,7 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/q_color_modulate.hpp source/scwx/qt/util/q_file_buffer.hpp source/scwx/qt/util/q_file_input_stream.hpp + source/scwx/qt/util/queue_counter.hpp source/scwx/qt/util/time.hpp source/scwx/qt/util/tooltip.hpp) set(SRC_UTIL source/scwx/qt/util/color.cpp @@ -385,6 +386,7 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/q_color_modulate.cpp source/scwx/qt/util/q_file_buffer.cpp source/scwx/qt/util/q_file_input_stream.cpp + source/scwx/qt/util/queue_counter.cpp source/scwx/qt/util/time.cpp source/scwx/qt/util/tooltip.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index f0870f93..f0c95e53 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ enum class Direction // Wait up to 5 seconds for radar sweeps to update static constexpr std::chrono::seconds kRadarSweepMonitorTimeout_ {5}; +// Only allow for 3 steps to be queued at any time +static constexpr size_t kMaxQueuedSteps_ {3}; class TimelineManager::Impl { @@ -80,6 +83,8 @@ public: boost::asio::thread_pool playThreadPool_ {1}; boost::asio::thread_pool selectThreadPool_ {1}; + util::QueueCounter stepCounter_ {kMaxQueuedSteps_}; + std::size_t mapCount_ {0}; std::string radarSite_ {"?"}; std::string previousRadarSite_ {"?"}; @@ -256,7 +261,7 @@ void TimelineManager::AnimationStepEnd() if (p->viewType_ == types::MapTime::Live) { // If the selected view type is live, select the current products - p->SelectTime(); + p->SelectTimeAsync(); } else { @@ -395,8 +400,9 @@ void TimelineManager::Impl::UpdateCacheLimit( { // Calculate the number of volume scans in the loop auto [startTime, endTime] = GetLoopStartAndEndTimes(); - auto startIter = util::GetBoundedElementIterator(volumeTimes, startTime); - auto endIter = util::GetBoundedElementIterator(volumeTimes, endTime); + auto startIter = + scwx::util::GetBoundedElementIterator(volumeTimes, startTime); + auto endIter = scwx::util::GetBoundedElementIterator(volumeTimes, endTime); std::size_t numVolumeScans = std::distance(startIter, endIter) + 1; // Dynamically update maximum cached volume scans to the lesser of @@ -571,7 +577,8 @@ std::pair TimelineManager::Impl::SelectTime( UpdateCacheLimit(radarProductManager, volumeTimes); // Find the best match bounded time - auto elementPtr = util::GetBoundedElementPointer(volumeTimes, selectedTime); + auto elementPtr = + scwx::util::GetBoundedElementPointer(volumeTimes, selectedTime); // The timeline is no longer live Q_EMIT self_->LiveStateUpdated(false); @@ -612,6 +619,12 @@ std::pair TimelineManager::Impl::SelectTime( void TimelineManager::Impl::StepAsync(Direction direction) { + // Prevent too many steps from being added to the queue + if (!stepCounter_.add()) + { + return; + } + boost::asio::post(selectThreadPool_, [=, this]() { @@ -623,6 +636,7 @@ void TimelineManager::Impl::StepAsync(Direction direction) { logger_->error(ex.what()); } + stepCounter_.remove(); }); } diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.cpp b/scwx-qt/source/scwx/qt/util/queue_counter.cpp new file mode 100644 index 00000000..929229e2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/queue_counter.cpp @@ -0,0 +1,49 @@ +#include + +#include + +namespace scwx::qt::util +{ + +class QueueCounter::Impl +{ +public: + explicit Impl(size_t maxCount) : maxCount_ {maxCount} {} + + const size_t maxCount_; + std::atomic count_ {0}; +}; + +QueueCounter::QueueCounter(size_t maxCount) : + p {std::make_unique(maxCount)} +{ +} + +QueueCounter::~QueueCounter() = default; + +bool QueueCounter::add() +{ + const size_t count = p->count_.fetch_add(1); + // Must be >= (not ==) to avoid race conditions + if (count >= p->maxCount_) + { + p->count_.fetch_sub(1); + return false; + } + else + { + return true; + } +} + +void QueueCounter::remove() +{ + p->count_.fetch_sub(1); +} + +bool QueueCounter::is_lock_free() +{ + return p->count_.is_lock_free(); +} + +} // namespace scwx::qt::util diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.hpp b/scwx-qt/source/scwx/qt/util/queue_counter.hpp new file mode 100644 index 00000000..c540ec63 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/queue_counter.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace scwx::qt::util +{ + +class QueueCounter +{ +public: + /** + * Counts the number of items in a queue, and prevents it from exceeding a + * count in a thread safe manor. This is lock free, assuming + * std::atomic supports lock free fetch_add and fetch_sub. + */ + + /** + * Construct a QueueCounter with a given maximum count + * + * @param maxCount The maximum number of items in the queue + */ + explicit QueueCounter(size_t maxCount); + + ~QueueCounter(); + QueueCounter(const QueueCounter&) = delete; + QueueCounter(QueueCounter&&) = delete; + QueueCounter& operator=(const QueueCounter&) = delete; + QueueCounter& operator=(QueueCounter&&) = delete; + + /** + * Called before adding an item. If it returns true, it is ok to add. If it + * returns false, it should not be added + * + * @return true if it is ok to add, false if the queue is full + */ + bool add(); + + /** + * Called when item is removed from the queue. Should only be called after a + * corresponding and successful call to add. + */ + void remove(); + + /** + * Tells if this instance is lock free + * + * @return true if it is lock free, false otherwise + */ + bool is_lock_free(); + + /** + * Tells if this class is always lock free. True if it is lock free, false + * otherwise + */ + static constexpr bool is_always_lock_free = + std::atomic::is_always_lock_free; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace scwx::qt::util From 3f9f5fcb90dadc339fb10b27b32b8016512c94c7 Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Thu, 17 Apr 2025 09:22:11 -0400 Subject: [PATCH 2/4] Explicitly link atomic for max_time_step_queue_size --- scwx-qt/scwx-qt.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 30e8adb9..86606d9e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -692,6 +692,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Positioning Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Svg + atomic Boost::json Boost::timer QMapLibre::Core From b4694d637bb37158e601b99a9d0d815654e32f1f Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Thu, 17 Apr 2025 10:51:27 -0400 Subject: [PATCH 3/4] Ensure atomic is only linked for non-windows OS's --- scwx-qt/scwx-qt.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 86606d9e..674bc661 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -686,13 +686,17 @@ else() target_compile_options(supercell-wx PRIVATE "$<$:-g>") endif() +# link atomic only for Linux +if (!MSVC) + target_link_libraries(scwx-qt PUBLIC atomic) +endif() + target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Qt${QT_VERSION_MAJOR}::Multimedia Qt${QT_VERSION_MAJOR}::Positioning Qt${QT_VERSION_MAJOR}::SerialPort Qt${QT_VERSION_MAJOR}::Svg - atomic Boost::json Boost::timer QMapLibre::Core From 9f5c126b7fc1856de67b594ae2229ba3f80f98c2 Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Thu, 17 Apr 2025 11:28:29 -0400 Subject: [PATCH 4/4] Use boost::atomic for max_time_step_queue_size, for easier linking --- scwx-qt/scwx-qt.cmake | 6 +----- scwx-qt/source/scwx/qt/util/queue_counter.cpp | 6 +++--- scwx-qt/source/scwx/qt/util/queue_counter.hpp | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 674bc661..89b31011 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -686,11 +686,6 @@ else() target_compile_options(supercell-wx PRIVATE "$<$:-g>") endif() -# link atomic only for Linux -if (!MSVC) - target_link_libraries(scwx-qt PUBLIC atomic) -endif() - target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Qt${QT_VERSION_MAJOR}::Multimedia @@ -699,6 +694,7 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Svg Boost::json Boost::timer + Boost::atomic QMapLibre::Core $<$:opengl32> $<$:SetupAPI> diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.cpp b/scwx-qt/source/scwx/qt/util/queue_counter.cpp index 929229e2..39b9fb9d 100644 --- a/scwx-qt/source/scwx/qt/util/queue_counter.cpp +++ b/scwx-qt/source/scwx/qt/util/queue_counter.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace scwx::qt::util { @@ -10,8 +10,8 @@ class QueueCounter::Impl public: explicit Impl(size_t maxCount) : maxCount_ {maxCount} {} - const size_t maxCount_; - std::atomic count_ {0}; + const size_t maxCount_; + boost::atomic count_ {0}; }; QueueCounter::QueueCounter(size_t maxCount) : diff --git a/scwx-qt/source/scwx/qt/util/queue_counter.hpp b/scwx-qt/source/scwx/qt/util/queue_counter.hpp index c540ec63..471bd645 100644 --- a/scwx-qt/source/scwx/qt/util/queue_counter.hpp +++ b/scwx-qt/source/scwx/qt/util/queue_counter.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace scwx::qt::util { @@ -54,7 +54,7 @@ public: * otherwise */ static constexpr bool is_always_lock_free = - std::atomic::is_always_lock_free; + boost::atomic::is_always_lock_free; private: class Impl;