// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include #include #include namespace Utils { /*! \brief A wrapper that provides thread-safe access to the wrapped type using a read/write mutex. Examples: \code void writeAndGet() { SynchronizedValue synchronizedString; // To update the value of the synchronized object, you can use the write function. synchronizedString.write([](QString &str) { str = "Hello World"; }); // If you just need a value from the synchronized object, you can use the get function qDebug() << "New value is:" << synchronizedString.get([](const QString &str) { return str; }); } void read() { SynchronizedValue> synchronized; QString both; // If you want to access multiple members of the synchronized object, you can use the read function synchronized.read([&both](const QPair &pair) { qDebug() << "First value is:" << pair.first(); qDebug() << "Second value is:" << pair.second(); both = pair.first() + pair.second(); // ... }); } // You can use the SynchronizedValue::update() to return whether the value was changed: void setString(const QString &newString) { const bool wasChanged = m_synchronizedString.update([&newString](QString &str) { if (newString == str) return false; str = newString; return true; })); if (wasChanged) emit stringChanged(newString); } // You can also use a lock type to get access void withLocks() { SynchronizedValue synchronizedData; *synchronizedData.writeLocked() = "Hello World"; qDebug() << *synchronizedData.readLocked() << "== Hello World"; auto lk = synchronizedData.writeLocked(); assert(lk.ownsLock()); *lk = "I am locked"; } \endcode */ template class SynchronizedValue { template friend std::tuple synchronize(SV &...sv); public: SynchronizedValue() = default; SynchronizedValue(const SynchronizedValue &other) { std::shared_lock lk(other.mutex); value = other.value; } SynchronizedValue(const T &other) : value(other) {} template typename LockType> class Lock { public: Lock(U &value_, std::shared_mutex &mutex) : m_lock(mutex) , m_value(value_) {} Lock(U &value_, std::shared_mutex &mutex, std::try_to_lock_t) : m_lock(mutex, std::try_to_lock) , m_value(value_) {} Lock(U &value_, std::shared_mutex &mutex, std::defer_lock_t) : m_lock(mutex, std::defer_lock) , m_value(value_) {} Lock(U &value_, std::shared_mutex &mutex, std::adopt_lock_t) : m_lock(mutex, std::adopt_lock) , m_value(value_) {} bool ownsLock() const { return m_lock.owns_lock(); } void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } U *operator->() const { Q_ASSERT(ownsLock()); return &m_value; } U &operator*() const { Q_ASSERT(ownsLock()); return m_value; } private: LockType m_lock; U &m_value; }; using shared_lock = Lock; using unique_lock = Lock; [[nodiscard]] shared_lock readLocked() const { return shared_lock(value, mutex); } [[nodiscard]] shared_lock readLocked(std::try_to_lock_t) const { return shared_lock(value, mutex, std::try_to_lock); } [[nodiscard]] unique_lock writeLocked() { return unique_lock(value, mutex); } [[nodiscard]] unique_lock writeLocked(std::try_to_lock_t) { return unique_lock(value, mutex, std::try_to_lock); } //! Call func with a const reference to the wrapped object void read(const std::function &func) const { std::shared_lock lk(mutex); func(value); } //! Call func with a const reference to the wrapped object and returns the result of func template [[nodiscard]] R get(const std::function &func) const { std::shared_lock lk(mutex); return func(value); } [[nodiscard]] T get() const { std::shared_lock lk(mutex); return value; } //! Call func with a mutable reference to the wrapped object void write(const std::function &func) { std::unique_lock lk(mutex); func(value); } //! Call func with a mutable reference to the wrapped object and returns the result of func template [[nodiscard]] R update(const std::function &func) { std::unique_lock lk(mutex); return func(value); } SynchronizedValue &operator=(const SynchronizedValue &other) { std::unique_lock lk(mutex, std::defer_lock); std::shared_lock lkOther(other.mutex, std::defer_lock); std::lock(lk, lkOther); value = other.value; return *this; } SynchronizedValue &operator=(const T &other) { std::unique_lock lk(mutex); value = other; return *this; } bool operator!=(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value != rhs.value; } bool operator==(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value == rhs.value; } bool operator<(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value < rhs.value; } bool operator<=(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value <= rhs.value; } bool operator>(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value > rhs.value; } bool operator>=(const SynchronizedValue &rhs) const { std::shared_lock lk(mutex, std ::defer_lock); std::shared_lock lkOther(rhs.mutex, std ::defer_lock); std::lock(lk, lkOther); return value >= rhs.value; } bool operator>(const T &rhs) const { std::shared_lock lk(mutex); return value > rhs; } bool operator>=(const T &rhs) const { std::shared_lock lk(mutex); return value >= rhs; } bool operator!=(const T &rhs) const { std::shared_lock lk(mutex); return value != rhs; } bool operator==(const T &rhs) const { std::shared_lock lk(mutex); return value == rhs; } bool operator<(const T &rhs) const { std::shared_lock lk(mutex); return value < rhs; } bool operator<=(const T &rhs) const { std::shared_lock lk(mutex); return value <= rhs; } private: template friend bool operator!=(const L &lhs, const SynchronizedValue &rhs) { return rhs != lhs; } template friend bool operator==(const L &lhs, const SynchronizedValue &rhs) { return rhs == lhs; } template friend bool operator<(const L &lhs, const SynchronizedValue &rhs) { return rhs > lhs; } template friend bool operator<=(const L &lhs, const SynchronizedValue &rhs) { return rhs >= lhs; } template friend bool operator>(const L &lhs, const SynchronizedValue &rhs) { return rhs < lhs; } template friend bool operator>=(const L &lhs, const SynchronizedValue &rhs) { return rhs <= lhs; } private: mutable std::shared_mutex mutex; T value; }; //! Lock a number of SynchronizedValue's using a dead-lock free algorithm. ( see std::lock() ) template std::tuple synchronize(SV &...sv) { std::lock(sv.mutex...); typedef std::tuple t_type; return t_type(typename SV::unique_lock(sv.value, sv.mutex, std::adopt_lock)...); } } // namespace Utils