File indexing completed on 2024-05-12 15:57:03
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef KISSYNCHRONIZEDCONNECTION_H 0008 #define KISSYNCHRONIZEDCONNECTION_H 0009 0010 #include <kritaglobal_export.h> 0011 0012 #include <QObject> 0013 #include <QEvent> 0014 0015 #include <KisMpl.h> 0016 #include <QMutex> 0017 #include <QMutexLocker> 0018 #include <QPointer> 0019 #include <boost/bind/bind.hpp> 0020 #include <functional> 0021 #include <kis_assert.h> 0022 #include <queue> 0023 0024 /** 0025 * @brief Event type used for synchronizing connection in KisSynchronizedConnection 0026 * 0027 * KisApplication will recognize this event type and postpone it until the 0028 * recursion state is over 0029 */ 0030 struct KRITAGLOBAL_EXPORT KisSynchronizedConnectionEvent : public QEvent 0031 { 0032 KisSynchronizedConnectionEvent(QObject *_destination); 0033 KisSynchronizedConnectionEvent(const KisSynchronizedConnectionEvent &rhs); 0034 ~KisSynchronizedConnectionEvent() override; 0035 0036 const QPointer<QObject> destination; 0037 }; 0038 0039 /** 0040 * @brief A base class for KisSynchronizedConnection 0041 * 0042 * This class implements QEvent logic for KisSynchronizedConnection. Since 0043 * KisSynchronizedConnection is templated, it should be implemented fully 0044 * inline, but we don't want to expose our interactions with KisApplication. 0045 * Therefore we implement this logic in a separate non-templated class that 0046 * will be hidden in `kritaglobal`. 0047 */ 0048 class KRITAGLOBAL_EXPORT KisSynchronizedConnectionBase : public QObject 0049 { 0050 public: 0051 static int eventType(); 0052 static void registerSynchronizedEventBarrier(std::function<void()> callback); 0053 0054 protected: 0055 bool event(QEvent *event) override; 0056 0057 protected: 0058 virtual void deliverEventToReceiver() = 0; 0059 void postEvent(); 0060 }; 0061 0062 /** 0063 * A "simple" class for ensuring a queued connection is never executed in 0064 * a recursive event processing loop. 0065 * 0066 * In several places in Krita we use queued signals for synchronizing 0067 * image chages to the GUI. In such cases we use Qt::DirectConnection 0068 * to fetch some data from the image, wrap that into the signal 0069 * parameters and post at the events queue as a queued signal. Obviously, 0070 * we expect this queued signal to be executed "after all the currently 0071 * processed GUI actions are finished". But that is not always true in Qt... 0072 * 0073 * In Qt the queued signal will be executed "as soon as execution path 0074 * returns to the event loop". And it can also happen when a nested 0075 * event loop started (by opening a QDialog) or QApplication::processEvent() 0076 * is called. It means that the processing of a queued signal can start 0077 * before the currently running GUI action is finished (because the current 0078 * task has been recursively overridden by KisBusyWaitBroker. 0079 * 0080 * KisSynchronizedConnection is workaround to this problem. Every connection 0081 * made via KisSynchronizedConnection ensures that the target slot 0082 * is executed without any recursion. The class tried to resemble new 0083 * member-function-pointer-based API of QObject::connect. 0084 * 0085 * In case the signal is emitted from the GUI thread, KisSynchronizedConnection 0086 * behaves as Qt::AutoConnection, that is, delivers event right away, skipping 0087 * the event loop. 0088 * 0089 * Under the hood the class uses a custom event (KisSynchronizedConnectionEvent), 0090 * which is recognized by KisApplication and postponed until the recursion state 0091 * is over. 0092 * 0093 * @param Args... the list of arguments that are passed through the signal 0094 * 0095 * Usage: 0096 * 0097 * \code{.cpp} 0098 * 0099 * class KisImage 0100 * { 0101 * // ... 0102 * Q_SIGNALS: 0103 * void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); 0104 * }; 0105 * 0106 * KisSynchronizedConnection<KisNodeSP, KisNodeList> connection; 0107 * 0108 * // if you want connect input and output separately 0109 * connection.connectInputSignal(image, &KisImage::sigRequestNodeReselection); 0110 * connection.connectOutputSlot(nodeManager, &KisNodeManager::slotImageRequestNodeReselection) 0111 * 0112 * // if you want to connect them in one call (in QObject style) 0113 * connection.connectSync(image, &KisImage::sigRequestNodeReselection, 0114 * nodeManager, &KisNodeManager::slotImageRequestNodeReselection); 0115 * 0116 * \endcode 0117 */ 0118 template <typename... Args> 0119 class KisSynchronizedConnection : public KisSynchronizedConnectionBase 0120 { 0121 public: 0122 public: 0123 using ArgsTuple = std::tuple<Args...>; 0124 using CallbackFunction = std::function<void (Args...)>; 0125 0126 public: 0127 KisSynchronizedConnection() = default; 0128 KisSynchronizedConnection(CallbackFunction callback) 0129 : m_callback(callback) 0130 {} 0131 0132 /** 0133 * Triggers the delivery of the signal to the destination slot manualy 0134 */ 0135 void start(const Args &...argsTuple) { 0136 { 0137 QMutexLocker l(&m_inputConnectionMutex); 0138 m_queue.emplace(std::make_tuple(argsTuple...)); 0139 } 0140 this->postEvent(); 0141 } 0142 0143 /** 0144 * Sets an arbitrary callback as a destination slot in the connection. 0145 * The callback should have a signature `void (Args...)` 0146 */ 0147 void setCallback(CallbackFunction callback) { 0148 m_callback = callback; 0149 } 0150 0151 /** 0152 * Connect input signal to the connection 0153 * 0154 * This part of the connection is based on Qt-signal mechanism, therefore 0155 * @p object should be convertible into `const QObject*`. 0156 */ 0157 template <typename Dptr, typename C, typename R, typename ...MemFnArgs> 0158 void connectInputSignal(Dptr object, R (C::* memfn)(MemFnArgs...)) { 0159 static_assert (std::is_convertible<Dptr, const C*>::value, "Source object should be convertible into the base of the member pointer"); 0160 static_assert (std::is_convertible<Dptr, const QObject*>::value, "Source object should be convertible into QObject"); 0161 0162 QObject::connect(static_cast<const C*>(object), memfn, 0163 this, &KisSynchronizedConnection::start, Qt::DirectConnection); 0164 } 0165 0166 /** 0167 * Connect output slot to the connection 0168 * 0169 * Since destination slot doesn't use Qt-signal machinery, the destination 0170 * object shouldn't necessarily be a QObject. It should just be a member 0171 * function with a compatible signature. 0172 */ 0173 template <typename Dptr, typename C, typename R, typename ...MemFnArgs> 0174 void connectOutputSlot(Dptr object, R (C::* memfn)(MemFnArgs...)) { 0175 static_assert (std::is_convertible<Dptr, C*>::value, "Destination object should be convertible into the base of the member pointer"); 0176 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_callback); 0177 0178 m_callback = bindToMemberFunction(object, memfn, 0179 kismpl::make_index_sequence_from_1< 0180 std::tuple_size<ArgsTuple>::value>()); 0181 } 0182 0183 /** 0184 * A convenience method for seting up input and output connections at 0185 * the same time 0186 */ 0187 template <typename Dptr1, typename C1, typename R1, typename ...MemFnArgs1, 0188 typename Dptr2, typename C2, typename R2, typename ...MemFnArgs2> 0189 void connectSync(Dptr1 object1, R1 (C1::* memfn1)(MemFnArgs1...), 0190 Dptr2 object2, R2 (C2::* memfn2)(MemFnArgs2...)) { 0191 0192 connectInputSignal(object1, memfn1); 0193 connectOutputSlot(object2, memfn2); 0194 } 0195 0196 bool hasPendingSignals() const { 0197 QMutexLocker l(&m_inputConnectionMutex); 0198 return !m_queue.empty(); 0199 } 0200 0201 private: 0202 0203 template <typename Dptr, typename C, typename R, typename ...MemFnArgs, std::size_t ...Idx> 0204 CallbackFunction bindToMemberFunction(Dptr object, R (C::* memfn)(MemFnArgs...), std::index_sequence<Idx...>) { 0205 0206 /// we cannot use std::bind here, because it doesn't support 0207 /// indexed iteration over the argument placeholders 0208 0209 return boost::bind(memfn, object, boost::arg<Idx>()...); 0210 } 0211 0212 protected: 0213 void deliverEventToReceiver() override { 0214 ArgsTuple args; 0215 0216 { 0217 QMutexLocker l(&m_inputConnectionMutex); 0218 args = m_queue.front(); 0219 m_queue.pop(); 0220 } 0221 0222 kismpl::apply(m_callback, args); 0223 } 0224 0225 private: 0226 CallbackFunction m_callback; 0227 std::queue<ArgsTuple> m_queue; 0228 mutable QMutex m_inputConnectionMutex; 0229 }; 0230 0231 #endif // KISSYNCHRONIZEDCONNECTION_H