File indexing completed on 2024-05-19 04:25:10
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 /** 0055 * In unittests the connection should work in 'Auto' mode, because most of the 0056 * actions are executed in the GUI thread, and (usually) don't have an event 0057 * loop at all 0058 * 0059 * Defautl value: false 0060 */ 0061 static void setAutoModeForUnittestsEnabled(bool value); 0062 static bool isAutoModeForUnittestsEnabled(); 0063 0064 0065 protected: 0066 bool event(QEvent *event) override; 0067 0068 protected: 0069 virtual void deliverEventToReceiver() = 0; 0070 void postEvent(); 0071 }; 0072 0073 /** 0074 * A "simple" class for ensuring a queued connection is never executed in 0075 * a recursive event processing loop. 0076 * 0077 * In several places in Krita we use queued signals for synchronizing 0078 * image changes to the GUI. In such cases we use Qt::DirectConnection 0079 * to fetch some data from the image, wrap that into the signal 0080 * parameters and post at the events queue as a queued signal. Obviously, 0081 * we expect this queued signal to be executed "after all the currently 0082 * processed GUI actions are finished". But that is not always true in Qt... 0083 * 0084 * In Qt the queued signal will be executed "as soon as execution path 0085 * returns to the event loop". And it can also happen when a nested 0086 * event loop started (by opening a QDialog) or QApplication::processEvent() 0087 * is called. It means that the processing of a queued signal can start 0088 * before the currently running GUI action is finished (because the current 0089 * task has been recursively overridden by KisBusyWaitBroker. 0090 * 0091 * KisSynchronizedConnection is workaround to this problem. Every connection 0092 * made via KisSynchronizedConnection ensures that the target slot 0093 * is executed without any recursion. The class tried to resemble new 0094 * member-function-pointer-based API of QObject::connect. 0095 * 0096 * In case the signal is emitted from the GUI thread, KisSynchronizedConnection 0097 * behaves as Qt::AutoConnection, that is, delivers event right away, skipping 0098 * the event loop. 0099 * 0100 * Under the hood the class uses a custom event (KisSynchronizedConnectionEvent), 0101 * which is recognized by KisApplication and postponed until the recursion state 0102 * is over. 0103 * 0104 * @param Args... the list of arguments that are passed through the signal 0105 * 0106 * Usage: 0107 * 0108 * \code{.cpp} 0109 * 0110 * class KisImage 0111 * { 0112 * // ... 0113 * Q_SIGNALS: 0114 * void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); 0115 * }; 0116 * 0117 * KisSynchronizedConnection<KisNodeSP, KisNodeList> connection; 0118 * 0119 * // if you want connect input and output separately 0120 * connection.connectInputSignal(image, &KisImage::sigRequestNodeReselection); 0121 * connection.connectOutputSlot(nodeManager, &KisNodeManager::slotImageRequestNodeReselection) 0122 * 0123 * // if you want to connect them in one call (in QObject style) 0124 * connection.connectSync(image, &KisImage::sigRequestNodeReselection, 0125 * nodeManager, &KisNodeManager::slotImageRequestNodeReselection); 0126 * 0127 * \endcode 0128 */ 0129 template <typename... Args> 0130 class KisSynchronizedConnection : public KisSynchronizedConnectionBase 0131 { 0132 public: 0133 public: 0134 using ArgsTuple = std::tuple<Args...>; 0135 using CallbackFunction = std::function<void (Args...)>; 0136 0137 public: 0138 KisSynchronizedConnection() = default; 0139 KisSynchronizedConnection(CallbackFunction callback) 0140 : m_callback(callback) 0141 {} 0142 0143 /** 0144 * Triggers the delivery of the signal to the destination slot manually 0145 */ 0146 void start(const Args &...argsTuple) { 0147 { 0148 QMutexLocker l(&m_inputConnectionMutex); 0149 m_queue.emplace(std::make_tuple(argsTuple...)); 0150 } 0151 this->postEvent(); 0152 } 0153 0154 /** 0155 * Sets an arbitrary callback as a destination slot in the connection. 0156 * The callback should have a signature `void (Args...)` 0157 */ 0158 void setCallback(CallbackFunction callback) { 0159 m_callback = callback; 0160 } 0161 0162 /** 0163 * Connect input signal to the connection 0164 * 0165 * This part of the connection is based on Qt-signal mechanism, therefore 0166 * @p object should be convertible into `const QObject*`. 0167 */ 0168 template <typename Dptr, typename C, typename R, typename ...MemFnArgs> 0169 void connectInputSignal(Dptr object, R (C::* memfn)(MemFnArgs...)) { 0170 static_assert (std::is_convertible<Dptr, const C*>::value, "Source object should be convertible into the base of the member pointer"); 0171 static_assert (std::is_convertible<Dptr, const QObject*>::value, "Source object should be convertible into QObject"); 0172 0173 QObject::connect(static_cast<const C*>(object), memfn, 0174 this, &KisSynchronizedConnection::start, Qt::DirectConnection); 0175 } 0176 0177 /** 0178 * Connect output slot to the connection 0179 * 0180 * Since destination slot doesn't use Qt-signal machinery, the destination 0181 * object shouldn't necessarily be a QObject. It should just be a member 0182 * function with a compatible signature. 0183 */ 0184 template <typename Dptr, typename C, typename R, typename ...MemFnArgs> 0185 void connectOutputSlot(Dptr object, R (C::* memfn)(MemFnArgs...)) { 0186 static_assert (std::is_convertible<Dptr, C*>::value, "Destination object should be convertible into the base of the member pointer"); 0187 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_callback); 0188 0189 m_callback = bindToMemberFunction(object, memfn, 0190 kismpl::make_index_sequence_from_1< 0191 std::tuple_size<ArgsTuple>::value>()); 0192 } 0193 0194 /** 0195 * A convenience method for setting up input and output connections at 0196 * the same time 0197 */ 0198 template <typename Dptr1, typename C1, typename R1, typename ...MemFnArgs1, 0199 typename Dptr2, typename C2, typename R2, typename ...MemFnArgs2> 0200 void connectSync(Dptr1 object1, R1 (C1::* memfn1)(MemFnArgs1...), 0201 Dptr2 object2, R2 (C2::* memfn2)(MemFnArgs2...)) { 0202 0203 connectInputSignal(object1, memfn1); 0204 connectOutputSlot(object2, memfn2); 0205 } 0206 0207 bool hasPendingSignals() const { 0208 QMutexLocker l(&m_inputConnectionMutex); 0209 return !m_queue.empty(); 0210 } 0211 0212 private: 0213 0214 template <typename Dptr, typename C, typename R, typename ...MemFnArgs, std::size_t ...Idx> 0215 CallbackFunction bindToMemberFunction(Dptr object, R (C::* memfn)(MemFnArgs...), std::index_sequence<Idx...>) { 0216 0217 /// we cannot use std::bind here, because it doesn't support 0218 /// indexed iteration over the argument placeholders 0219 0220 return boost::bind(memfn, object, boost::arg<Idx>()...); 0221 } 0222 0223 protected: 0224 void deliverEventToReceiver() override { 0225 ArgsTuple args; 0226 0227 { 0228 QMutexLocker l(&m_inputConnectionMutex); 0229 args = m_queue.front(); 0230 m_queue.pop(); 0231 } 0232 0233 std::apply(m_callback, args); 0234 } 0235 0236 private: 0237 CallbackFunction m_callback; 0238 std::queue<ArgsTuple> m_queue; 0239 mutable QMutex m_inputConnectionMutex; 0240 }; 0241 0242 #endif // KISSYNCHRONIZEDCONNECTION_H