diff --git a/UnitTests/UnitTests.vcxproj b/UnitTests/UnitTests.vcxproj
index 95931c154..33eac273d 100644
--- a/UnitTests/UnitTests.vcxproj
+++ b/UnitTests/UnitTests.vcxproj
@@ -128,6 +128,7 @@
+
diff --git a/UnitTests/UnitTests.vcxproj.filters b/UnitTests/UnitTests.vcxproj.filters
index d5a92f7e8..69db57137 100644
--- a/UnitTests/UnitTests.vcxproj.filters
+++ b/UnitTests/UnitTests.vcxproj.filters
@@ -39,6 +39,9 @@
Source Files
+
+ Source Files
+
diff --git a/UnitTests/main.cpp b/UnitTests/main.cpp
index 8ee1385dd..8267cf4b4 100644
--- a/UnitTests/main.cpp
+++ b/UnitTests/main.cpp
@@ -11,6 +11,7 @@
#include "sgml.cpp"
#include "stream.cpp"
#include "unicode.cpp"
+#include "watchdog.cpp"
#include
int main(int argc, const char * argv[])
@@ -34,6 +35,7 @@ int main(int argc, const char * argv[])
UnitTests::unicode::str2wstr();
UnitTests::unicode::wstr2str();
UnitTests::unicode::charset_encoder();
+ UnitTests::watchdog::test();
std::cout << "PASS\n";
return 0;
}
diff --git a/UnitTests/pch.hpp b/UnitTests/pch.hpp
index b674b4bfd..b812d1642 100644
--- a/UnitTests/pch.hpp
+++ b/UnitTests/pch.hpp
@@ -1,4 +1,4 @@
-/*
+/*
SPDX-License-Identifier: MIT
Copyright © 2023 Amebis
*/
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include "compat.hpp"
diff --git a/UnitTests/watchdog.cpp b/UnitTests/watchdog.cpp
new file mode 100644
index 000000000..ec90f4e10
--- /dev/null
+++ b/UnitTests/watchdog.cpp
@@ -0,0 +1,32 @@
+/*
+ SPDX-License-Identifier: MIT
+ Copyright © 2023 Amebis
+*/
+
+#include "pch.hpp"
+
+using namespace std;
+#ifdef _WIN32
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+#endif
+
+namespace UnitTests
+{
+ TEST_CLASS(watchdog)
+ {
+ public:
+ TEST_METHOD(test)
+ {
+ volatile bool wd_called = false;
+ stdex::watchdog wd(
+ std::chrono::milliseconds(100), [&] { wd_called = true; });
+ for (int i = 0; i < 100; ++i) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ Assert::IsFalse(wd_called);
+ wd.reset();
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(300));
+ Assert::IsTrue(wd_called);
+ }
+ };
+}
\ No newline at end of file
diff --git a/include/stdex/watchdog.hpp b/include/stdex/watchdog.hpp
new file mode 100644
index 000000000..e899f281a
--- /dev/null
+++ b/include/stdex/watchdog.hpp
@@ -0,0 +1,92 @@
+/*
+ SPDX-License-Identifier: MIT
+ Copyright © 2023 Amebis
+*/
+
+#pragma once
+
+#include "compat.hpp"
+#include
+#include
+#include
+#include
+#include
+
+namespace stdex
+{
+ ///
+ /// Triggers callback if not reset frequently enough
+ ///
+ template
+ class watchdog
+ {
+ public:
+ ///
+ /// Starts the watchdog
+ ///
+ /// \param[in] timeout How long the watchdog is waiting for a reset
+ /// \param[in] callback The function watchdog calls on timeout
+ ///
+ watchdog(_In_ _Duration timeout, _In_ std::function callback) :
+ m_phase(0),
+ m_quit(false),
+ m_timeout(timeout),
+ m_callback(callback),
+ m_thread(run, std::ref(*this))
+ {}
+
+ ///
+ /// Stops the watchdog
+ ///
+ ~watchdog()
+ {
+ {
+ const std::lock_guard lk(m_mutex);
+ m_quit = true;
+ }
+ m_cv.notify_one();
+ if (m_thread.joinable())
+ m_thread.join();
+ }
+
+ ///
+ /// Resets the watchdog
+ ///
+ /// Must be called frequently enough not to timeout the watchdog
+ ///
+ void reset()
+ {
+ {
+ const std::lock_guard lk(m_mutex);
+ m_phase++;
+ }
+ m_cv.notify_one();
+ }
+
+ protected:
+ static void run(_Inout_ watchdog& wd)
+ {
+ for (;;) {
+ std::unique_lock lk(wd.m_mutex);
+ auto phase = wd.m_phase;
+ if (wd.m_cv.wait_for(lk, wd.m_timeout, [&] {return wd.m_quit || phase != wd.m_phase; })) {
+ if (wd.m_quit)
+ break;
+ }
+ else {
+ wd.m_callback();
+ break;
+ }
+ }
+ }
+
+ protected:
+ size_t m_phase; ///< A counter we are incrementing to keep the watchdog happy
+ bool m_quit; ///< Quit the watchdog
+ _Duration m_timeout; ///< How long the watchdog is waiting for a reset
+ std::function m_callback; ///< The function watchdog calls on timeout
+ std::mutex m_mutex;
+ std::condition_variable m_cv;
+ std::thread m_thread;
+ };
+}