diff options
| author | Carl Hetherington <cth@carlh.net> | 2025-10-26 22:28:10 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2025-10-27 00:48:27 +0100 |
| commit | f1dfe9f2666c171ccaf5fc06c4e2395dda807e89 (patch) | |
| tree | 21e12fe61162b5e3fcddd14641f4738d4e676144 /src/lib | |
| parent | 01df40b552da6c380a7d788cd1f001d6ff083780 (diff) | |
Add new signal handling.
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/signal.cc | 108 | ||||
| -rw-r--r-- | src/lib/signal.h | 237 | ||||
| -rw-r--r-- | src/lib/wscript | 1 |
3 files changed, 346 insertions, 0 deletions
diff --git a/src/lib/signal.cc b/src/lib/signal.cc new file mode 100644 index 000000000..2ebae8a00 --- /dev/null +++ b/src/lib/signal.cc @@ -0,0 +1,108 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include "signal.h" + +SignalManager2* dcpomatic::signal::manager = nullptr; + + +Trackable::~Trackable() +{ + dcpomatic::signal::manager->unregister_trackable(this); +} + + +SignalManager2::SignalManager2() + : _ui_thread(boost::this_thread::get_id()) +{ + +} + + +void +SignalManager2::add_pending(Trackable* trackable, std::function<void ()> pending) +{ + boost::mutex::scoped_lock lm(_pending_mutex); + _pending.push_back(make_pair(trackable, pending)); + wake(); +} + + +void +SignalManager2::process_pending() +{ + boost::mutex::scoped_lock lm(_pending_mutex); + boost::mutex::scoped_lock lm2(_trackables_mutex); + for (auto const& pending: _pending) { + if (!pending.first || _trackables.find(pending.first) != _trackables.end()) { + pending.second(); + } + } + _pending.clear(); +} + + +void +SignalManager2::register_signal(SignalBase* signal) +{ + boost::mutex::scoped_lock lm(_signals_mutex); + _signals.insert(signal); +} + + +void +SignalManager2::unregister_signal(SignalBase* signal) +{ + boost::mutex::scoped_lock lm(_signals_mutex); + _signals.erase(signal); +} + + +void +SignalManager2::register_trackable(Trackable* trackable) +{ + boost::mutex::scoped_lock lm(_trackables_mutex); + _trackables.insert(trackable); +} + + +void SignalManager2::unregister_trackable(Trackable* trackable) +{ + boost::mutex::scoped_lock lm(_trackables_mutex); + _trackables.erase(trackable); +} + + +void +SignalManager2::disconnect(SignalBase* signal, int id) +{ + boost::mutex::scoped_lock lm(_signals_mutex); + if (_signals.find(signal) != _signals.end()) { + signal->disconnect(id); + } +} + + +void +SignalManager2::wake() +{ + process_pending(); +} diff --git a/src/lib/signal.h b/src/lib/signal.h new file mode 100644 index 000000000..98094c82a --- /dev/null +++ b/src/lib/signal.h @@ -0,0 +1,237 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SIGNAL_H +#define DCPOMATIC_SIGNAL_H + + +#include "dcpomatic_assert.h" +#include <boost/optional.hpp> +#include <boost/thread.hpp> +#include <set> + + +class SignalBase; + + +class Trackable +{ +public: + ~Trackable(); +}; + + +class SignalManager2 +{ +public: + /** Must be called from the UI thread, as the current thread ID will be + * taken as the UI one. + */ + SignalManager2(); + + virtual ~SignalManager2() {} + + boost::thread::id ui_thread() const { + return _ui_thread; + } + + void add_pending(Trackable* trackable, std::function<void ()> pending); + void process_pending(); + + void register_signal(SignalBase* signal); + void unregister_signal(SignalBase* signal); + + void register_trackable(Trackable* trackable); + void unregister_trackable(Trackable* trackable); + + void disconnect(SignalBase* signal, int id); + + virtual void wake(); + +private: + boost::thread::id _ui_thread; + boost::mutex _pending_mutex; + std::list<std::pair<Trackable*, std::function<void ()>>> _pending; + boost::mutex _signals_mutex; + std::set<SignalBase*> _signals; + boost::mutex _trackables_mutex; + std::set<Trackable*> _trackables; +}; + + +namespace dcpomatic { +namespace signal { + extern SignalManager2* manager; +} +} + + +class SignalBase +{ +public: + SignalBase() + { + DCPOMATIC_ASSERT(dcpomatic::signal::manager); + dcpomatic::signal::manager->register_signal(this); + } + + virtual ~SignalBase() + { + dcpomatic::signal::manager->unregister_signal(this); + } + + virtual void disconnect(int id) = 0; +}; + + +class Connection +{ +public: + Connection(SignalBase* signal, int id) + : _signal(signal) + , _id(id) + {} + + Connection& operator=(Connection const& other) + { + _signal = other._signal; + _id = other._id; + return *this; + } + + void disconnect() + { + DCPOMATIC_ASSERT(dcpomatic::signal::manager); + dcpomatic::signal::manager->disconnect(_signal, _id); + } + +private: + SignalBase* _signal; + int _id; +}; + + +/** @class ScopedConnection + * @brief Connection that disconnects itself on destruction. + */ +class ScopedConnection +{ +public: + ScopedConnection() {} + + ScopedConnection(Connection connection) + : _connection(std::move(connection)) + {} + + ScopedConnection(ScopedConnection const& other) = delete; + + ScopedConnection(ScopedConnection&& other) + : _connection(std::move(other._connection)) + {} + + ScopedConnection& operator=(ScopedConnection const& other) = delete; + + ScopedConnection& operator=(ScopedConnection&& other) + { + if (this != &other) { + _connection = other._connection; + other._connection.reset(); + } + return *this; + } + + ~ScopedConnection() + { + if (_connection) { + _connection->disconnect(); + } + } + +private: + boost::optional<Connection> _connection; +}; + + +template <class Signature> +class Signal : public SignalBase +{ +private: + struct Callback + { + int id; + bool same_thread; + Trackable* trackable = nullptr; + std::function<Signature> function; + }; + +public: + Connection connect_same_thread(std::function<Signature> function) + { + boost::mutex::scoped_lock lm(_mutex); + auto const id = _id++; + _callbacks.push_back({id, true, nullptr, function}); + return Connection(this, id); + } + + Connection connect_ui_thread(Trackable* trackable, std::function<Signature> function) + { + dcpomatic::signal::manager->register_trackable(trackable); + boost::mutex::scoped_lock lm(_mutex); + auto const id = _id++; + _callbacks.push_back({id, false, trackable, function}); + return Connection(this, id); + } + + template <typename... Args> + void operator()(Args... args) + { + boost::mutex::scoped_lock lm(_mutex); + DCPOMATIC_ASSERT(dcpomatic::signal::manager); + bool in_ui_thread = boost::this_thread::get_id() == dcpomatic::signal::manager->ui_thread(); + for (auto const& callback: _callbacks) { + if (callback.same_thread || in_ui_thread) { + callback.function(args...); + } else { + dcpomatic::signal::manager->add_pending(callback.trackable, boost::bind(callback.function, args...)); + } + } + } + +private: + void disconnect(int id) override + { + boost::mutex::scoped_lock lm(_mutex); + auto iter = std::find_if(_callbacks.begin(), _callbacks.end(), [id](Callback const& callback) { return callback.id == id; }); + if (iter != _callbacks.end()) { + _callbacks.erase(iter); + } + } + + friend class Connection; + + boost::mutex _mutex; + std::list<Callback> _callbacks; + int _id = 0; +}; + + +#endif + diff --git a/src/lib/wscript b/src/lib/wscript index dc18f713a..b5e9a1f8b 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -193,6 +193,7 @@ sources = """ send_problem_report_job.cc server.cc shuffler.cc + signal.cc spl.cc spl_entry.cc sqlite_database.cc |
