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