From e29da008bb6d1d04bbf499b82d2b73dc63acbac4 Mon Sep 17 00:00:00 2001
From: DonLakeFlyer <don@thegagnes.com>
Date: Sat, 2 Jan 2021 11:03:08 -0800
Subject: [PATCH] Fix/Rework signal compress code to fix signal loss

---
 src/QGCApplication.cc |  96 +++++++++++++++++++++++++++++++++++++++++
 src/QGCApplication.h  | 115 +++++++++++---------------------------------------
 2 files changed, 120 insertions(+), 91 deletions(-)

diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc
index c495e3a..09ea42e 100644
--- a/src/QGCApplication.cc
+++ b/src/QGCApplication.cc
@@ -969,3 +969,99 @@ QString QGCApplication::cachedAirframeMetaDataFile(void)
     QDir airframeDir = QFileInfo(settings.fileName()).dir();
     return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml"));
 }
+
+/// Returns a signal index that is can be compared to QMetaCallEvent.signalId
+int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod & method)
+{
+    if (method.methodType() != QMetaMethod::Signal) {
+        qWarning() << "Internal error: QGCApplication::CompressedSignalList::_signalIndex not a signal" << method.methodType();
+        return -1;
+    }
+
+    int index = -1;
+    const QMetaObject* metaObject = method.enclosingMetaObject();
+    for (int i=0; i<=method.methodIndex(); i++) {
+        if (metaObject->method(i).methodType() != QMetaMethod::Signal) {
+            continue;
+        }
+        index++;
+    }
+    return index;
+}
+
+void QGCApplication::CompressedSignalList::add(const QMetaMethod & method)
+{
+    const QMetaObject*  metaObject  = method.enclosingMetaObject();
+    int                 signalIndex = _signalIndex(method);
+
+    if (signalIndex != -1 && !contains(metaObject, signalIndex)) {
+        _signalMap[method.enclosingMetaObject()].insert(signalIndex);
+    }
+}
+
+void QGCApplication::CompressedSignalList::remove(const QMetaMethod & method)
+{
+    int                 signalIndex = _signalIndex(method);
+    const QMetaObject*  metaObject  = method.enclosingMetaObject();
+
+    if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) {
+        _signalMap[metaObject].remove(signalIndex);
+        if (_signalMap[metaObject].count() == 0) {
+            _signalMap.remove(metaObject);
+        }
+    }
+}
+
+bool QGCApplication::CompressedSignalList::contains(const QMetaObject* metaObject, int signalIndex)
+{
+    return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex);
+}
+
+void QGCApplication::addCompressedSignal(const QMetaMethod & method)
+{
+    _compressedSignals.add(method);
+}
+
+void QGCApplication::removeCompressedSignal(const QMetaMethod & method)
+{
+    _compressedSignals.remove(method);
+}
+
+bool QGCApplication::compressEvent(QEvent*event, QObject* receiver, QPostEventList* postedEvents)
+{
+    if (event->type() != QEvent::MetaCall) {
+        return QApplication::compressEvent(event, receiver, postedEvents);
+    }
+
+    QMetaCallEvent* mce = static_cast<QMetaCallEvent*>(event);
+    if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
+        return QApplication::compressEvent(event, receiver, postedEvents);
+    }
+
+    for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
+        QPostEvent &cur = *it;
+        if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type()) {
+            continue;
+        }
+        QMetaCallEvent *cur_mce = static_cast<QMetaCallEvent*>(cur.event);
+        if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() || cur_mce->id() != mce->id()) {
+            continue;
+        }
+        /* Keep The Newest Call */
+        // We can't merely qSwap the existing posted event with the new one, since QEvent
+        // keeps track of whether it has been posted. Deletion of a formerly posted event
+        // takes the posted event list mutex and does a useless search of the posted event
+        // list upon deletion. We thus clear the QEvent::posted flag before deletion.
+        struct EventHelper : private QEvent {
+            static void clearPostedFlag(QEvent * ev) {
+                (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
+            }
+        };
+        EventHelper::clearPostedFlag(cur.event);
+        delete cur.event;
+        cur.event = event;
+        return true;
+    }
+
+    return false;
+}
diff --git a/src/QGCApplication.h b/src/QGCApplication.h
index 97b03a3..3d5b12b 100644
--- a/src/QGCApplication.h
+++ b/src/QGCApplication.h
@@ -98,14 +98,18 @@ public:
 
     QTranslator& qgcJSONTranslator(void) { return _qgcTranslatorJSON; }
 
-    static QString cachedParameterMetaDataFile(void);
-    static QString cachedAirframeMetaDataFile(void);
-
     void            setLanguage();
     QQuickItem*     mainRootWindow();
-
     uint64_t        msecsSinceBoot(void) { return _msecsElapsedTime.elapsed(); }
 
+    /// Registers the signal such that only the last duplicate signal added is left in the queue.
+    void addCompressedSignal(const QMetaMethod & method);
+
+    void removeCompressedSignal(const QMetaMethod & method);
+
+    static QString cachedParameterMetaDataFile(void);
+    static QString cachedAirframeMetaDataFile(void);
+
 public slots:
     /// You can connect to this slot to show an information message box from a different thread.
     void informationMessageBoxOnMainThread(const QString& title, const QString& msg);
@@ -178,6 +182,8 @@ private:
     void        _checkForNewVersion     ();
     void        _exitWithError          (QString errorMessage);
 
+    // Overrides from QApplication
+    bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) override;
 
     bool                        _runningUnitTests;                                  ///< true: running unit tests, false: normal app
     static const int            _missingParamsDelayedDisplayTimerTimeout = 1000;    ///< Timeout to wait for next missing fact to come in before display
@@ -204,102 +210,29 @@ private:
 
     QList<QPair<QString /* title */, QString /* message */>> _delayedAppMessages;
 
-    static const char* _settingsVersionKey;             ///< Settings key which hold settings version
-    static const char* _deleteAllSettingsKey;           ///< If this settings key is set on boot, all settings will be deleted
+    class CompressedSignalList {
+        Q_DISABLE_COPY(CompressedSignalList)
 
-    /// Unit Test have access to creating and destroying singletons
-    friend class UnitTest;
-
-private:
-    /*! Keeps a list of singal indices for one or more meatobject classes.
-     * The indices are signal indices as given by QMetaCallEvent.signalId.
-     * On Qt 5, those do *not* match QMetaObject::methodIndex since they
-     * exclude non-signal methods. */
-    class SignalList {
-        Q_DISABLE_COPY(SignalList)
-        typedef QMap<const QMetaObject *, QSet<int> > T;
-        T m_data;
-        /*! Returns a signal index that is can be compared to QMetaCallEvent.signalId. */
-        static int signalIndex(const QMetaMethod & method) {
-            Q_ASSERT(method.methodType() == QMetaMethod::Signal);
-
-            int index = -1;
-            const QMetaObject * mobj = method.enclosingMetaObject();
-            for (int i = 0; i <= method.methodIndex(); ++i) {
-                if (mobj->method(i).methodType() != QMetaMethod::Signal) continue;
-                ++ index;
-            }
-            return index;
-        }
     public:
-        SignalList() {}
-        void add(const QMetaMethod & method) {
-            m_data[method.enclosingMetaObject()].insert(signalIndex(method));
-        }
-        void remove(const QMetaMethod & method) {
-            T::iterator it = m_data.find(method.enclosingMetaObject());
-            if (it != m_data.end()) {
-                it->remove(signalIndex(method));
-                if (it->empty()) m_data.erase(it);
-            }
-        }
-        bool contains(const QMetaObject * metaObject, int signalId) {
-            T::const_iterator it = m_data.find(metaObject);
-            return it != m_data.end() && it.value().contains(signalId);
-        }
-    };
+        CompressedSignalList() {}
 
-    SignalList m_compressedSignals;
+        void add        (const QMetaMethod & method);
+        void remove     (const QMetaMethod & method);
+        bool contains   (const QMetaObject * metaObject, int signalIndex);
 
-public:
-    void addCompressedSignal(const QMetaMethod & method) { m_compressedSignals.add(method); }
-    void removeCompressedSignal(const QMetaMethod & method) { m_compressedSignals.remove(method); }
+    private:
+        static int _signalIndex(const QMetaMethod & method);
 
-private:
-    struct EventHelper : private QEvent {
-        static void clearPostedFlag(QEvent * ev) {
-            (&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
-        }
+        QMap<const QMetaObject*, QSet<int> > _signalMap;
     };
 
-    bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) {
-        if (event->type() != QEvent::MetaCall)
-            return QApplication::compressEvent(event, receiver, postedEvents);
-
-        QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(event);
-
-        if (mce->sender() && !m_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) {
-            return false;
-        }
-
-        for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
-            QPostEvent &cur = *it;
-            if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type())
-                continue;
-            QMetaCallEvent *cur_mce = static_cast<QMetaCallEvent*>(cur.event);
-            if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
-                    cur_mce->id() != mce->id())
-                continue;
-            if (true) {
-              /* Keep The Newest Call */
-              // We can't merely qSwap the existing posted event with the new one, since QEvent
-              // keeps track of whether it has been posted. Deletion of a formerly posted event
-              // takes the posted event list mutex and does a useless search of the posted event
-              // list upon deletion. We thus clear the QEvent::posted flag before deletion.
-              EventHelper::clearPostedFlag(cur.event);
-              delete cur.event;
-              cur.event = event;
-            } else {
-              /* Keep the Oldest Call */
-              delete event;
-            }
-            return true;
-        }
-        return false;
-    }
-
+    CompressedSignalList _compressedSignals;
 
+    static const char* _settingsVersionKey;             ///< Settings key which hold settings version
+    static const char* _deleteAllSettingsKey;           ///< If this settings key is set on boot, all settings will be deleted
 
+    /// Unit Test have access to creating and destroying singletons
+    friend class UnitTest;
 };
 
 /// @brief Returns the QGCApplication object singleton.