From bb26ba30b6d8e195a6b61d69b60d7543b32859ff Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Sun, 17 Oct 2021 22:12:33 +0200
Subject: [PATCH] Add progress reporter type

To log/report progress of long duration operations using given time period.
---
 apps/openmw_test_suite/CMakeLists.txt         |  1 +
 .../misc/progressreporter.cpp                 | 43 ++++++++++++++++
 components/misc/progressreporter.hpp          | 50 +++++++++++++++++++
 3 files changed, 94 insertions(+)
 create mode 100644 apps/openmw_test_suite/misc/progressreporter.cpp
 create mode 100644 components/misc/progressreporter.hpp

diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt
index 67001a7885..e8a296b563 100644
--- a/apps/openmw_test_suite/CMakeLists.txt
+++ b/apps/openmw_test_suite/CMakeLists.txt
@@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
 
         misc/test_stringops.cpp
         misc/test_endianness.cpp
+        misc/progressreporter.cpp
 
         nifloader/testbulletnifloader.cpp
 
diff --git a/apps/openmw_test_suite/misc/progressreporter.cpp b/apps/openmw_test_suite/misc/progressreporter.cpp
new file mode 100644
index 0000000000..cd5449acf7
--- /dev/null
+++ b/apps/openmw_test_suite/misc/progressreporter.cpp
@@ -0,0 +1,43 @@
+#include <components/misc/progressreporter.hpp>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <chrono>
+
+namespace
+{
+    using namespace testing;
+    using namespace Misc;
+
+    struct ReportMock
+    {
+        MOCK_METHOD(void, call, (std::size_t, std::size_t), ());
+    };
+
+    struct Report
+    {
+        StrictMock<ReportMock>* mImpl;
+
+        void operator()(std::size_t provided, std::size_t expected)
+        {
+            mImpl->call(provided, expected);
+        }
+    };
+
+    TEST(MiscProgressReporterTest, shouldCallReportWhenPassedInterval)
+    {
+        StrictMock<ReportMock> report;
+        EXPECT_CALL(report, call(13, 42)).WillOnce(Return());
+        ProgressReporter reporter(std::chrono::steady_clock::duration(0), Report {&report});
+        reporter(13, 42);
+    }
+
+    TEST(MiscProgressReporterTest, shouldNotCallReportWhenIntervalIsNotPassed)
+    {
+        StrictMock<ReportMock> report;
+        EXPECT_CALL(report, call(13, 42)).Times(0);
+        ProgressReporter reporter(std::chrono::seconds(1000), Report {&report});
+        reporter(13, 42);
+    }
+}
diff --git a/components/misc/progressreporter.hpp b/components/misc/progressreporter.hpp
new file mode 100644
index 0000000000..733e36191e
--- /dev/null
+++ b/components/misc/progressreporter.hpp
@@ -0,0 +1,50 @@
+#ifndef OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H
+#define OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H
+
+#include <algorithm>
+#include <chrono>
+#include <mutex>
+#include <type_traits>
+#include <utility>
+
+namespace Misc
+{
+    template <class Report>
+    class ProgressReporter
+    {
+    public:
+        explicit ProgressReporter(Report&& report = Report {})
+            : mReport(std::forward<Report>(report))
+        {}
+
+        explicit ProgressReporter(std::chrono::steady_clock::duration interval, Report&& report = Report {})
+            : mInterval(interval)
+            , mReport(std::forward<Report>(report))
+        {}
+
+        void operator()(std::size_t provided, std::size_t expected)
+        {
+            expected = std::max(expected, provided);
+            const bool shouldReport = [&]
+            {
+                const std::lock_guard lock(mMutex);
+                const auto now = std::chrono::steady_clock::now();
+                const auto left = mNextReport - now;
+                if (left.count() > 0 || provided == expected)
+                    return false;
+                mNextReport += mInterval + left;
+                return true;
+            } ();
+            if (shouldReport)
+                mReport(provided, expected);
+        }
+
+    private:
+        const std::chrono::steady_clock::duration mInterval = std::chrono::seconds(1);
+        Report mReport;
+        std::mutex mMutex;
+        std::chrono::steady_clock::time_point mNextReport {std::chrono::steady_clock::now() + mInterval};
+    };
+}
+
+#endif