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