From 62cf867383f5b6747f938dfecceaccf017ad25c6 Mon Sep 17 00:00:00 2001
From: Nicolas Pope <nwpope@utu.fi>
Date: Sat, 13 Jun 2020 19:27:04 +0300
Subject: [PATCH] Switch timer to use handler class

---
 CMakeLists.txt                                |  2 +-
 applications/vision/src/main.cpp              |  2 +-
 components/audio/include/ftl/audio/source.hpp |  4 +-
 components/common/cpp/include/ftl/handle.hpp  | 62 ++++++++++++++++++-
 components/common/cpp/include/ftl/timer.hpp   | 46 +++-----------
 components/common/cpp/src/configuration.cpp   |  2 +-
 components/common/cpp/src/timer.cpp           | 54 +++++++---------
 components/common/cpp/test/timer_unit.cpp     | 25 ++------
 .../net/cpp/include/ftl/net/universe.hpp      | 13 ++--
 components/net/cpp/src/universe.cpp           |  2 +-
 .../include/ftl/rgbd/frameset.hpp             |  2 +-
 .../rgbd-sources/include/ftl/rgbd/group.hpp   |  6 +-
 .../streams/include/ftl/streams/builder.hpp   |  2 +-
 .../include/ftl/streams/filestream.hpp        |  2 +-
 14 files changed, 112 insertions(+), 112 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4df38ff5c..55bb87265 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -394,7 +394,7 @@ if (WIN32) # TODO(nick) Should do based upon compiler (VS)
 	set(OS_LIBS "")
 else()
 	add_definitions(-DUNIX)
-	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -msse3 -Wall")
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC -msse3 -Wall -Werror=unused-result")
 	set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG -pg")
 	set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -mfpmath=sse")
 	set(OS_LIBS "dl")
diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp
index 2ed9ed444..ceddbaf4c 100644
--- a/applications/vision/src/main.cpp
+++ b/applications/vision/src/main.cpp
@@ -86,7 +86,7 @@ static void run(ftl::Configurable *root) {
 	ftl::ctrl::Master ctrl(root, net);
 
 	// Sync clocks!
-	ftl::timer::add(ftl::timer::kTimerMain, [&time_peer,&sync_counter,net](int64_t ts) {
+	auto timer = ftl::timer::add(ftl::timer::kTimerMain, [&time_peer,&sync_counter,net](int64_t ts) {
 		if (sync_counter-- <= 0 && time_peer != ftl::UUID(0) ) {
 			sync_counter = 20;
 			auto start = std::chrono::high_resolution_clock::now();
diff --git a/components/audio/include/ftl/audio/source.hpp b/components/audio/include/ftl/audio/source.hpp
index ff0663ddc..fa8249d23 100644
--- a/components/audio/include/ftl/audio/source.hpp
+++ b/components/audio/include/ftl/audio/source.hpp
@@ -37,8 +37,8 @@ class Source : public ftl::Configurable, public ftl::audio::Generator {
     private:
     ftl::audio::FrameState state_;
     bool active_;
-    ftl::timer::TimerHandle timer_hp_;
-	ftl::timer::TimerHandle timer_main_;
+    ftl::Handle timer_hp_;
+	ftl::Handle timer_main_;
 	ftl::audio::FrameSet::Callback cb_;
 
 	ftl::audio::Buffer<short> *buffer_;
diff --git a/components/common/cpp/include/ftl/handle.hpp b/components/common/cpp/include/ftl/handle.hpp
index d7e2f8089..92b99fdcc 100644
--- a/components/common/cpp/include/ftl/handle.hpp
+++ b/components/common/cpp/include/ftl/handle.hpp
@@ -2,6 +2,7 @@
 #define _FTL_HANDLE_HPP_
 
 #include <ftl/threads.hpp>
+#include <ftl/exception.hpp>
 #include <functional>
 #include <unordered_map>
 
@@ -22,7 +23,7 @@ struct BaseHandler {
  * A `Handle` is used to manage registered callbacks, allowing them to be
  * removed safely whenever the `Handle` instance is destroyed.
  */
-struct Handle {
+struct [[nodiscard]] Handle {
 	friend struct BaseHandler;
 
 	/**
@@ -72,7 +73,7 @@ struct Handler : BaseHandler {
 	 * Add a new callback function. It returns a `Handle` object that must
 	 * remain in scope, the destructor of the `Handle` will remove the callback.
 	 */
-	[[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) {
+	Handle on(const std::function<bool(ARGS...)> &f) {
 		std::unique_lock<std::mutex> lk(mutex_);
 		int id = id_++;
 		callbacks_[id] = f;
@@ -111,6 +112,63 @@ struct Handler : BaseHandler {
 	std::unordered_map<int, std::function<bool(ARGS...)>> callbacks_;
 };
 
+/**
+ * This class is used to manage callbacks. The template parameters are the
+ * arguments to be passed to the callback when triggered. This class is already
+ * thread-safe. Note that this version only allows a single callback at a time
+ * and throws an exception if multiple are added without resetting.
+ */
+template <typename ...ARGS>
+struct SingletonHandler : BaseHandler {
+	/**
+	 * Add a new callback function. It returns a `Handle` object that must
+	 * remain in scope, the destructor of the `Handle` will remove the callback.
+	 */
+	[[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) {
+		std::unique_lock<std::mutex> lk(mutex_);
+		if (callback_) throw FTL_Error("Callback already bound");
+		callback_ = f;
+		return make_handle(this, id_++);
+	}
+
+	/**
+	 * Safely trigger all callbacks. Note that `Handler` is locked when
+	 * triggering so callbacks cannot make modifications to it or they will
+	 * lock up. To remove a callback, return false from the callback, else
+	 * return true.
+	 */
+	bool trigger(ARGS ...args) {
+		std::unique_lock<std::mutex> lk(mutex_);
+		if (callback_) {
+			bool keep = callback_(std::forward<ARGS>(args)...);
+			if (!keep) callback_ = nullptr;
+			return keep;
+		} else {
+			return false;
+		}
+		//} catch (const std::exception &e) {
+		//	LOG(ERROR) << "Exception in callback: " << e.what();
+		//}
+	}
+
+	/**
+	 * Remove a callback using its `Handle`. This is equivalent to allowing the
+	 * `Handle` to be destroyed or cancelled. If the handle does not match the
+	 * currently bound callback then the callback is not removed.
+	 */
+	void remove(const Handle &h) override {
+		std::unique_lock<std::mutex> lk(mutex_);
+		if (h.id() == id_-1) callback_ = nullptr;
+	}
+
+	void reset() { callback_ = nullptr; }
+
+	operator bool() const { return (bool)callback_; }
+
+	private:
+	std::function<bool(ARGS...)> callback_;
+};
+
 }
 
 ftl::Handle ftl::BaseHandler::make_handle(BaseHandler *h, int id) {
diff --git a/components/common/cpp/include/ftl/timer.hpp b/components/common/cpp/include/ftl/timer.hpp
index bf378425d..9ccb0b31f 100644
--- a/components/common/cpp/include/ftl/timer.hpp
+++ b/components/common/cpp/include/ftl/timer.hpp
@@ -1,6 +1,7 @@
 #ifndef _FTL_COMMON_TIMER_HPP_
 #define _FTL_COMMON_TIMER_HPP_
 
+#include <ftl/handle.hpp>
 #include <functional>
 
 namespace ftl {
@@ -15,6 +16,11 @@ namespace ftl {
  */
 namespace timer {
 
+/**
+ * Timer level determines in what order and when a timer callback is called.
+ * This allows some timers to operate at higher precision / lower latency
+ * than others, as well as having idle callbacks.
+ */
 enum timerlevel_t {
 	kTimerHighPrecision = 0,
 	kTimerSwap,
@@ -24,44 +30,6 @@ enum timerlevel_t {
 	kTimerMAXLEVEL
 };
 
-/**
- * Represents a timer job for control purposes. Use to remove timer jobs in
- * a destructor, for example.
- */
-struct TimerHandle {
-	TimerHandle() : id_(-1) {}
-	explicit TimerHandle(int i) : id_(i) {}
-	TimerHandle(const TimerHandle &t) : id_(t.id()) {}
-
-	/**
-	 * Cancel the timer job. If currently executing it will block and wait for
-	 * the job to complete.
-	 */
-	void cancel() const;
-	void pause() const;
-	void unpause() const;
-
-	/**
-	 * Do the timer job every N frames.
-	 */
-	void setMultiplier(unsigned int) const;
-
-	/**
-	 * Give the timer job a name for logging output.
-	 */
-	void setName(const std::string &) const;
-
-	/**
-	 * Allow copy assignment.
-	 */
-	TimerHandle &operator=(const TimerHandle &h) { id_ = h.id(); return *this; }
-
-	inline int id() const { return id_; }
-
-	private:
-	int id_;
-};
-
 int64_t get_time();
 
 /**
@@ -114,7 +82,7 @@ void setClockSlave(bool);
  * If all high precision callbacks together take more than 1ms to complete, a
  * warning is produced.
  */
-const TimerHandle add(timerlevel_t, const std::function<bool(int64_t ts)> &);
+ftl::Handle add(timerlevel_t, const std::function<bool(int64_t ts)> &);
 
 /**
  * Initiate the timer and optionally block the current process.
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 0d2b79a97..111154022 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -664,7 +664,7 @@ std::vector<nlohmann::json*> ftl::config::_createArray(ftl::Configurable *parent
 		//LOG(WARNING) << "Expected an array for '" << name << "' in " << parent->getID();
 	}
 
-	return std::move(result);
+	return result;
 }
 
 nlohmann::json &ftl::config::_create(ftl::Configurable *parent, const std::string &name) {
diff --git a/components/common/cpp/src/timer.cpp b/components/common/cpp/src/timer.cpp
index bc8f97e2d..54ab2cb64 100644
--- a/components/common/cpp/src/timer.cpp
+++ b/components/common/cpp/src/timer.cpp
@@ -29,9 +29,9 @@ static int last_id = 0;
 static bool clock_slave = true;
 
 struct TimerJob {
-	int id;
-	function<bool(int64_t)> job;
-	volatile bool active;
+	int id=0;
+	ftl::SingletonHandler<int64_t> job;
+	volatile bool active=false;
 	// TODO: (Nick) Implement richer forms of timer
 	//bool paused;
 	//int multiplier;
@@ -76,7 +76,7 @@ static void waitTimePoint() {
 		auto idle_job = jobs[kTimerIdle10].begin();
 		while (idle_job != jobs[kTimerIdle10].end() && msdelay >= 10 && sincelast != mspf) {
 			(*idle_job).active = true;
-			bool doremove = !(*idle_job).job(now);
+			bool doremove = !(*idle_job).job.trigger(now);
 
 			if (doremove) {
 				idle_job = jobs[kTimerIdle10].erase(idle_job);
@@ -132,13 +132,16 @@ bool ftl::timer::isClockSlave() {
 	return clock_slave;
 }
 
-const TimerHandle ftl::timer::add(timerlevel_t l, const std::function<bool(int64_t ts)> &f) {
+ftl::Handle ftl::timer::add(timerlevel_t l, const std::function<bool(int64_t ts)> &f) {
 	if (l < 0 || l >= kTimerMAXLEVEL) return {};
 
 	UNIQUE_LOCK(mtx, lk);
 	int newid = last_id++;
-	jobs[l].push_back({newid, f, false, "NoName"});
-	return TimerHandle(newid);
+	auto &j = jobs[l].emplace_back();
+	j.id = newid;
+	j.name = "NoName";
+	ftl::Handle h = j.job.on(f);
+	return h;
 }
 
 static void removeJob(int id) {
@@ -165,14 +168,14 @@ static void trigger_jobs() {
 	// First do non-blocking high precision callbacks
 	const int64_t before = get_time();
 	for (auto &j : jobs[kTimerHighPrecision]) {
-		j.job(ts);
+		j.job.trigger(ts);
 	}
 	const int64_t after = get_time();
 	if (after - before > 1) LOG(WARNING) << "Precision jobs took too long (" << (after-before) << "ms)";
 
 	// Then do also non-blocking swap callbacks
 	for (auto &j : jobs[kTimerSwap]) {
-		j.job(ts);
+		j.job.trigger(ts);
 	}
 
 	// Now use thread jobs to do more intensive callbacks
@@ -187,12 +190,21 @@ static void trigger_jobs() {
 		auto *pj = &j;
 
 		ftl::pool.push([pj,ts](int id) {
-			bool doremove = !pj->job(ts);
+			bool doremove = !pj->job.trigger(ts);
 			pj->active = false;
 			active_jobs--;
 			if (doremove) removeJob(pj->id);
 		});
 	}
+
+	// Final cleanup of stale jobs
+	for (size_t j=0; j<kTimerMAXLEVEL; ++j) {
+		for (auto i=jobs[j].begin(); i!=jobs[j].end(); i++) {
+			if ((bool)((*i).job) == false) {
+				i = jobs[j].erase(i);
+			}
+		}
+	}
 }
 
 namespace ftl {
@@ -246,25 +258,3 @@ void ftl::timer::reset() {
 		jobs[i].clear();
 	}
 }
-
-// ===== TimerHandle ===========================================================
-
-void ftl::timer::TimerHandle::cancel() const {
-	removeJob(id());
-}
-
-void ftl::timer::TimerHandle::pause() const {
-
-}
-
-void ftl::timer::TimerHandle::unpause() const {
-
-}
-
-void ftl::timer::TimerHandle::setMultiplier(unsigned int N) const {
-
-}
-
-void ftl::timer::TimerHandle::setName(const std::string &name) const {
-
-}
diff --git a/components/common/cpp/test/timer_unit.cpp b/components/common/cpp/test/timer_unit.cpp
index 2fdc70034..2c7602646 100644
--- a/components/common/cpp/test/timer_unit.cpp
+++ b/components/common/cpp/test/timer_unit.cpp
@@ -59,13 +59,13 @@ TEST_CASE( "Timer::add() High Precision Accuracy" ) {
 
 		REQUIRE( (rc.id() >= 0) );
 
-		ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+		auto h = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
 			didrun[1] = true;
 			ftl::timer::stop(false);
 			return true;
 		});
 
-		ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
+		auto h2 = ftl::timer::add(ftl::timer::kTimerHighPrecision, [&didrun](int64_t ts) {
 			didrun[2] = true;
 			ftl::timer::stop(false);
 			return true;
@@ -184,7 +184,7 @@ TEST_CASE( "Timer::add() Main job" ) {
 
 		REQUIRE( (rc.id() >= 0) );
 
-		ftl::timer::add(ftl::timer::kTimerMain, [&job2](int64_t ts) {
+		auto h = ftl::timer::add(ftl::timer::kTimerMain, [&job2](int64_t ts) {
 			job2++;
 			return true;
 		});
@@ -212,24 +212,7 @@ TEST_CASE( "Timer::add() Main job" ) {
 	}
 }
 
-TEST_CASE( "TimerHandle::cancel()" ) {
-	SECTION( "Invalid id" ) {
-		bool didjob = false;
-		ftl::timer::reset();
-
-		ftl::timer::add(ftl::timer::kTimerMain, [&didjob](int64_t ts) {
-			didjob = true;
-			ftl::timer::stop(false);
-			return true;
-		});
-
-		// Fake Handle
-		ftl::timer::TimerHandle h(44);
-		h.cancel();
-		ftl::timer::start(true);
-		REQUIRE( didjob );
-	}
-
+TEST_CASE( "Timer Handle::cancel()" ) {
 	SECTION( "Valid id" ) {
 		bool didjob = false;
 		ftl::timer::reset();
diff --git a/components/net/cpp/include/ftl/net/universe.hpp b/components/net/cpp/include/ftl/net/universe.hpp
index f5af2edef..54748fdc6 100644
--- a/components/net/cpp/include/ftl/net/universe.hpp
+++ b/components/net/cpp/include/ftl/net/universe.hpp
@@ -244,6 +244,7 @@ class Universe : public ftl::Configurable {
 	std::list<ReconnectInfo> reconnects_;
 	size_t phase_;
 	std::list<ftl::net::Peer*> garbage_;
+	ftl::Handle garbage_timer_;
 
 	size_t send_size_;
 	size_t recv_size_;
@@ -342,9 +343,9 @@ std::optional<R> Universe::findOne(const std::string &name, ARGS... args) {
 		// Cancel any further results
 		lk.lock();
 		for (auto p : peers_) {
-			auto m = record.find(p);
-			if (m != record.end()) {
-				p->cancelCall(m->second);
+			auto mm = record.find(p);
+			if (mm != record.end()) {
+				p->cancelCall(mm->second);
 			}
 		}
 	}
@@ -387,9 +388,9 @@ std::vector<R> Universe::findAll(const std::string &name, ARGS... args) {
 		// Cancel any further results
 		lk.lock();
 		for (auto p : peers_) {
-			auto m = record.find(p);
-			if (m != record.end()) {
-				p->cancelCall(m->second);
+			auto mm = record.find(p);
+			if (mm != record.end()) {
+				p->cancelCall(mm->second);
 			}
 		}
 	}
diff --git a/components/net/cpp/src/universe.cpp b/components/net/cpp/src/universe.cpp
index d5aeb743a..783d44707 100644
--- a/components/net/cpp/src/universe.cpp
+++ b/components/net/cpp/src/universe.cpp
@@ -76,7 +76,7 @@ Universe::Universe(nlohmann::json &config) :
 	// Add an idle timer job to garbage collect peer objects
 	// Note: Important to be a timer job to ensure no other timer jobs are
 	// using the object.
-	ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) {
+	garbage_timer_ = ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) {
 		if (garbage_.size() > 0) {
 			UNIQUE_LOCK(net_mutex_,lk);
 			if (ftl::pool.n_idle() == ftl::pool.size()) {
diff --git a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
index 1c4f7da20..30ced7b65 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frameset.hpp
@@ -157,7 +157,7 @@ class Builder : public Generator {
 	int id_;
 	std::atomic<int> jobs_;
 	volatile bool skip_;
-	ftl::timer::TimerHandle main_id_;
+	ftl::Handle main_id_;
 	size_t size_;
 	size_t bufferSize_;
 	std::vector<ftl::rgbd::FrameState*> states_;
diff --git a/components/rgbd-sources/include/ftl/rgbd/group.hpp b/components/rgbd-sources/include/ftl/rgbd/group.hpp
index eed75bbc5..1b227ead0 100644
--- a/components/rgbd-sources/include/ftl/rgbd/group.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/group.hpp
@@ -105,9 +105,9 @@ class Group : public ftl::rgbd::Generator {
 	std::atomic<int> jobs_;
 	std::atomic<int> cjobs_;
 	volatile bool skip_;
-	ftl::timer::TimerHandle cap_id_;
-	ftl::timer::TimerHandle swap_id_;
-	ftl::timer::TimerHandle main_id_;
+	ftl::Handle cap_id_;
+	ftl::Handle swap_id_;
+	ftl::Handle main_id_;
 	std::string name_;
 	MUTEX mutex_;
 
diff --git a/components/streams/include/ftl/streams/builder.hpp b/components/streams/include/ftl/streams/builder.hpp
index 4cd32246a..7bfc42050 100644
--- a/components/streams/include/ftl/streams/builder.hpp
+++ b/components/streams/include/ftl/streams/builder.hpp
@@ -71,7 +71,7 @@ class Builder {
 	int id_;
 	std::atomic<int> jobs_;
 	volatile bool skip_;
-	ftl::timer::TimerHandle main_id_;
+	ftl::Handle main_id_;
 	size_t size_;
 	size_t bufferSize_;
 
diff --git a/components/streams/include/ftl/streams/filestream.hpp b/components/streams/include/ftl/streams/filestream.hpp
index d1fea1d9f..ecbb427bb 100644
--- a/components/streams/include/ftl/streams/filestream.hpp
+++ b/components/streams/include/ftl/streams/filestream.hpp
@@ -71,7 +71,7 @@ class File : public Stream {
 	int64_t first_ts_;
 	bool active_;
 	int version_;
-	ftl::timer::TimerHandle timer_;
+	ftl::Handle timer_;
 	bool is_video_;
 	bool save_data_;
 
-- 
GitLab