diff options
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 176 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.h | 5 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher_p.h | 176 | ||||
-rw-r--r-- | src/webchannel/signalhandler_p.h | 2 | ||||
-rw-r--r-- | tests/qml/tst_bench.qml | 35 | ||||
-rw-r--r-- | tests/webchannel/tst_webchannel.cpp | 96 | ||||
-rw-r--r-- | tests/webchannel/tst_webchannel.h | 97 |
7 files changed, 388 insertions, 199 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index fcf809a..375cf1b 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -41,20 +41,14 @@ ****************************************************************************/ #include "qmetaobjectpublisher.h" +#include "qmetaobjectpublisher_p.h" #include "qwebchannel.h" -#include "variantargument_p.h" -#include "signalhandler_p.h" - -#include <QStringList> -#include <QMetaObject> -#include <QJsonObject> -#include <QJsonArray> -#include <QBasicTimer> -#include <QDebug> -#include <QPointer> #include <QEvent> #include <QJsonDocument> +#include <QDebug> +#include <QJsonObject> +#include <QJsonArray> namespace { const QString KEY_SIGNALS = QStringLiteral("signals"); @@ -95,130 +89,15 @@ const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMethod("dest const int PROPERTY_UPDATE_INTERVAL = 50; } -struct QMetaObjectPublisherPrivate +QMetaObjectPublisherPrivate::QMetaObjectPublisherPrivate(QMetaObjectPublisher *q) + : q(q) + , signalHandler(this) + , clientIsIdle(false) + , blockUpdates(false) + , pendingInit(false) + , propertyUpdatesInitialized(false) { - QMetaObjectPublisherPrivate(QMetaObjectPublisher *q) - : q(q) - , signalHandler(this) - , clientIsIdle(false) - , blockUpdates(false) - , pendingInit(false) - , propertyUpdatesInitialized(false) - { - } - - /** - * Set the client to idle or busy, based on the value of @p isIdle. - * - * When the value changed, start/stop the property update timer accordingly. - */ - void setClientIsIdle(bool isIdle); - - /** - * Initialize clients by sending them the class information of the registered objects. - * - * Furthermore, if that was not done already, connect to their property notify signals. - */ - void initializeClients(); - - /** - * Go through all properties of the given object and connect to their notify signal. - * - * When receiving a notify signal, it will store the information in pendingPropertyUpdates which - * gets send via a Qt.propertyUpdate message to the server when the grouping timer timeouts. - */ - void initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo); - - /** - * Send the clients the new property values since the last time this function was invoked. - * - * This is a grouped batch of all properties for which their notify signal was emitted. - * The list of signals as well as the arguments they contained, are also transmitted to - * the remote clients. - * - * @sa timer, initializePropertyUpdates - */ - void sendPendingPropertyUpdates(); - - /** - * Invoke the method of index @p methodIndex on @p object with the arguments @p args. - * - * The return value of the method invocation is then transmitted to the calling client - * via a webchannel response to the message identified by @p id. - */ - bool invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id); - - /** - * Callback of the signalHandler which forwards the signal invocation to the webchannel clients. - */ - void signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments); - - /** - * Callback for registered or wrapped objects which erases all data related to @p object. - * - * @sa signalEmitted - */ - void objectDestroyed(const QObject *object); - - /** - * Given a QVariant containing a QObject*, wrap the object and register for property updates - * return the objects class information. - * - * All other input types are returned as-is. - * - * TODO: support wrapping of initially-registered objects - */ - QVariant wrapResult(const QVariant &result); - - /** - * Invoke delete later on @p object. - */ - void deleteWrappedObject(QObject *object) const; - - QMetaObjectPublisher *q; - QPointer<QWebChannel> webChannel; - SignalHandler<QMetaObjectPublisherPrivate> signalHandler; - - // true when the client is idle, false otherwise - bool clientIsIdle; - - // true when no property updates should be sent, false otherwise - bool blockUpdates; - - // true when at least one client needs to be initialized, - // i.e. when a Qt.init came in which was not handled yet. - bool pendingInit; - - // true when at least one client was initialized and thus - // the property updates have been initialized and the - // object info map set. - bool propertyUpdatesInitialized; - - // Map of registered objects indexed by their id. - QHash<QString, QObject *> registeredObjects; - - // Map the registered objects to their id. - QHash<const QObject *, QString> registeredObjectIds; - - // Map of object names to maps of signal indices to a set of all their properties. - // The last value is a set as a signal can be the notify signal of multiple properties. - typedef QHash<int, QSet<QString> > SignalToPropertyNameMap; - QHash<const QObject *, SignalToPropertyNameMap> signalToPropertyMap; - - // Objects that changed their properties and are waiting for idle client. - // map of object name to map of signal index to arguments - typedef QHash<int, QVariantList> SignalToArgumentsMap; - typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates; - PendingPropertyUpdates pendingPropertyUpdates; - - // Maps wrapped object to class info - QHash<const QObject *, QVariantMap> wrappedObjects; - - // Aggregate property updates since we get multiple Qt.idle message when we have multiple - // clients. They all share the same QWebProcess though so we must take special care to - // prevent message flooding. - QBasicTimer timer; -}; +} void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) { @@ -235,6 +114,10 @@ void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) void QMetaObjectPublisherPrivate::initializeClients() { + if (!webChannel) { + return; + } + QJsonObject objectInfos; { const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd(); @@ -700,33 +583,6 @@ void QMetaObjectPublisher::setBlockUpdates(bool block) emit blockUpdatesChanged(block); } -void QMetaObjectPublisher::bench_ensureUpdatesInitialized() -{ - if (!d->propertyUpdatesInitialized) { - d->initializeClients(); - } -} - -void QMetaObjectPublisher::bench_sendPendingPropertyUpdates() -{ - d->clientIsIdle = true; - d->sendPendingPropertyUpdates(); -} - -void QMetaObjectPublisher::bench_initializeClients() -{ - d->propertyUpdatesInitialized = false; - d->signalToPropertyMap.clear(); - d->signalHandler.clear(); - d->initializeClients(); -} - -void QMetaObjectPublisher::bench_registerObjects(const QVariantMap &objects) -{ - d->propertyUpdatesInitialized = false; - registerObjects(objects); -} - bool QMetaObjectPublisher::test_clientIsIdle() const { return d->clientIsIdle; diff --git a/src/webchannel/qmetaobjectpublisher.h b/src/webchannel/qmetaobjectpublisher.h index 684be1d..f4763a7 100644 --- a/src/webchannel/qmetaobjectpublisher.h +++ b/src/webchannel/qmetaobjectpublisher.h @@ -93,10 +93,6 @@ public: void setBlockUpdates(bool block); /// TODO: cleanup: rewrite tests in C++ and access PIMPL data from there - Q_INVOKABLE void bench_ensureUpdatesInitialized(); - Q_INVOKABLE void bench_sendPendingPropertyUpdates(); - Q_INVOKABLE void bench_registerObjects(const QVariantMap &objects); - Q_INVOKABLE void bench_initializeClients(); Q_INVOKABLE bool test_clientIsIdle() const; signals: @@ -118,6 +114,7 @@ protected: private: QScopedPointer<QMetaObjectPublisherPrivate> d; friend struct QMetaObjectPublisherPrivate; + friend class TestWebChannel; }; #endif // QMETAOBJECTPUBLISHER_H diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h new file mode 100644 index 0000000..e364510 --- /dev/null +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> +** Contact: http://www.qt-project.org/legal +* +** This file is part of the QtWebChannel module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMETAOBJECTPUBLISHER_P_H +#define QMETAOBJECTPUBLISHER_P_H + +#include "variantargument_p.h" +#include "signalhandler_p.h" + +#include <QStringList> +#include <QMetaObject> +#include <QBasicTimer> +#include <QPointer> + +class QMetaObjectPublisher; +class QWebChannel; + +#include "qwebchannelglobal.h" + +struct Q_WEBCHANNEL_EXPORT QMetaObjectPublisherPrivate +{ + QMetaObjectPublisherPrivate(QMetaObjectPublisher *q); + + /** + * Set the client to idle or busy, based on the value of @p isIdle. + * + * When the value changed, start/stop the property update timer accordingly. + */ + void setClientIsIdle(bool isIdle); + + /** + * Initialize clients by sending them the class information of the registered objects. + * + * Furthermore, if that was not done already, connect to their property notify signals. + */ + void initializeClients(); + + /** + * Go through all properties of the given object and connect to their notify signal. + * + * When receiving a notify signal, it will store the information in pendingPropertyUpdates which + * gets send via a Qt.propertyUpdate message to the server when the grouping timer timeouts. + */ + void initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo); + + /** + * Send the clients the new property values since the last time this function was invoked. + * + * This is a grouped batch of all properties for which their notify signal was emitted. + * The list of signals as well as the arguments they contained, are also transmitted to + * the remote clients. + * + * @sa timer, initializePropertyUpdates + */ + void sendPendingPropertyUpdates(); + + /** + * Invoke the method of index @p methodIndex on @p object with the arguments @p args. + * + * The return value of the method invocation is then transmitted to the calling client + * via a webchannel response to the message identified by @p id. + */ + bool invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id); + + /** + * Callback of the signalHandler which forwards the signal invocation to the webchannel clients. + */ + void signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments); + + /** + * Callback for registered or wrapped objects which erases all data related to @p object. + * + * @sa signalEmitted + */ + void objectDestroyed(const QObject *object); + + /** + * Given a QVariant containing a QObject*, wrap the object and register for property updates + * return the objects class information. + * + * All other input types are returned as-is. + * + * TODO: support wrapping of initially-registered objects + */ + QVariant wrapResult(const QVariant &result); + + /** + * Invoke delete later on @p object. + */ + void deleteWrappedObject(QObject *object) const; + + QMetaObjectPublisher *q; + QPointer<QWebChannel> webChannel; + SignalHandler<QMetaObjectPublisherPrivate> signalHandler; + + // true when the client is idle, false otherwise + bool clientIsIdle; + + // true when no property updates should be sent, false otherwise + bool blockUpdates; + + // true when at least one client needs to be initialized, + // i.e. when a Qt.init came in which was not handled yet. + bool pendingInit; + + // true when at least one client was initialized and thus + // the property updates have been initialized and the + // object info map set. + bool propertyUpdatesInitialized; + + // Map of registered objects indexed by their id. + QHash<QString, QObject *> registeredObjects; + + // Map the registered objects to their id. + QHash<const QObject *, QString> registeredObjectIds; + + // Map of object names to maps of signal indices to a set of all their properties. + // The last value is a set as a signal can be the notify signal of multiple properties. + typedef QHash<int, QSet<QString> > SignalToPropertyNameMap; + QHash<const QObject *, SignalToPropertyNameMap> signalToPropertyMap; + + // Objects that changed their properties and are waiting for idle client. + // map of object name to map of signal index to arguments + typedef QHash<int, QVariantList> SignalToArgumentsMap; + typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates; + PendingPropertyUpdates pendingPropertyUpdates; + + // Maps wrapped object to class info + QHash<const QObject *, QVariantMap> wrappedObjects; + + // Aggregate property updates since we get multiple Qt.idle message when we have multiple + // clients. They all share the same QWebProcess though so we must take special care to + // prevent message flooding. + QBasicTimer timer; +}; + +#endif // QMETAOBJECTPUBLISHER_P_H diff --git a/src/webchannel/signalhandler_p.h b/src/webchannel/signalhandler_p.h index 2613f92..5bc1f4d 100644 --- a/src/webchannel/signalhandler_p.h +++ b/src/webchannel/signalhandler_p.h @@ -272,7 +272,9 @@ void SignalHandler<Receiver>::clear() } } m_connectionsCounter.clear(); + const SignalArgumentHash keep = m_signalArgumentTypes.take(&QObject::staticMetaObject); m_signalArgumentTypes.clear(); + m_signalArgumentTypes[&QObject::staticMetaObject] = keep; } #endif // SIGNALHANDLER_H diff --git a/tests/qml/tst_bench.qml b/tests/qml/tst_bench.qml index 6893a00..46eb6be 100644 --- a/tests/qml/tst_bench.qml +++ b/tests/qml/tst_bench.qml @@ -100,41 +100,6 @@ WebChannelTest { publisher.registerObjects(objects); } - function benchmark_classInfo() - { - publisher.classInfoForObjects(objects); - } - - function benchmark_initializeClients() - { - publisher.bench_initializeClients(); - } - - function benchmark_propertyUpdates() - { - // required to make the benchmark work standalone - publisher.bench_ensureUpdatesInitialized(); - - for (var o in objects) { - objects[o].p0++; - objects[o].p1++; - objects[o].p2++; - objects[o].p3++; - objects[o].p4++; - objects[o].p5++; - objects[o].p6++; - objects[o].p7++; - objects[o].p8++; - objects[o].p9++; - } - publisher.bench_sendPendingPropertyUpdates(); - } - - function benchmark_registerObjects() - { - publisher.bench_registerObjects(objects); - } - function benchmark_init_baseline() { loadUrl("bench_init.html"); diff --git a/tests/webchannel/tst_webchannel.cpp b/tests/webchannel/tst_webchannel.cpp index c1f23d1..e997bd7 100644 --- a/tests/webchannel/tst_webchannel.cpp +++ b/tests/webchannel/tst_webchannel.cpp @@ -44,6 +44,7 @@ #include <qwebchannel.h> #include <qmetaobjectpublisher.h> +#include <qmetaobjectpublisher_p.h> #include <QtTest> @@ -145,4 +146,99 @@ void TestWebChannel::testInfoForObject() } } +static QVariantMap createObjects(QObject *parent) +{ + const int num = 100; + QVariantMap objects; + for (int i = 0; i < num; ++i) { + objects[QStringLiteral("obj%1").arg(i)] = QVariant::fromValue(new BenchObject(parent)); + } + return objects; +} + +void TestWebChannel::benchClassInfo() +{ + QWebChannel channel; + QSignalSpy initSpy(&channel, SIGNAL(initialized())); + QVERIFY(initSpy.wait()); + + QMetaObjectPublisher publisher; + publisher.setWebChannel(&channel); + + QObject parent; + const QVariantMap objects = createObjects(&parent); + + QBENCHMARK { + publisher.classInfoForObjects(objects); + } +} + +void TestWebChannel::benchInitializeClients() +{ + QWebChannel channel; + QSignalSpy initSpy(&channel, SIGNAL(initialized())); + QVERIFY(initSpy.wait()); + + QMetaObjectPublisher publisher; + publisher.setWebChannel(&channel); + + QObject parent; + const QVariantMap objects = createObjects(&parent); + publisher.registerObjects(objects); + + QBENCHMARK { + publisher.d->initializeClients(); + + publisher.d->propertyUpdatesInitialized = false; + publisher.d->signalToPropertyMap.clear(); + publisher.d->signalHandler.clear(); + } +} + +void TestWebChannel::benchPropertyUpdates() +{ + QWebChannel channel; + QSignalSpy initSpy(&channel, SIGNAL(initialized())); + QVERIFY(initSpy.wait()); + + QMetaObjectPublisher publisher; + publisher.setWebChannel(&channel); + + QObject parent; + const QVariantMap objects = createObjects(&parent); + QVector<BenchObject*> objectList; + foreach (const QVariant &var, objects) { + objectList << var.value<BenchObject*>(); + } + + publisher.registerObjects(objects); + publisher.d->initializeClients(); + + QBENCHMARK { + foreach (BenchObject *obj, objectList) { + obj->change(); + } + + publisher.d->clientIsIdle = true; + publisher.d->sendPendingPropertyUpdates(); + } +} + +void TestWebChannel::benchRegisterObjects() +{ + QWebChannel channel; + QSignalSpy initSpy(&channel, SIGNAL(initialized())); + QVERIFY(initSpy.wait()); + + QMetaObjectPublisher publisher; + publisher.setWebChannel(&channel); + + QObject parent; + const QVariantMap objects = createObjects(&parent); + + QBENCHMARK { + publisher.registerObjects(objects); + } +} + QTEST_MAIN(TestWebChannel) diff --git a/tests/webchannel/tst_webchannel.h b/tests/webchannel/tst_webchannel.h index 5ea7a45..173921b 100644 --- a/tests/webchannel/tst_webchannel.h +++ b/tests/webchannel/tst_webchannel.h @@ -92,6 +92,98 @@ private slots: void slot4() {} }; +class BenchObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int p0 MEMBER m_p0 NOTIFY p0Changed) + Q_PROPERTY(int p1 MEMBER m_p1 NOTIFY p1Changed) + Q_PROPERTY(int p2 MEMBER m_p2 NOTIFY p2Changed) + Q_PROPERTY(int p3 MEMBER m_p3 NOTIFY p3Changed) + Q_PROPERTY(int p4 MEMBER m_p4 NOTIFY p4Changed) + Q_PROPERTY(int p5 MEMBER m_p5 NOTIFY p5Changed) + Q_PROPERTY(int p6 MEMBER m_p6 NOTIFY p6Changed) + Q_PROPERTY(int p7 MEMBER m_p7 NOTIFY p7Changed) + Q_PROPERTY(int p8 MEMBER m_p8 NOTIFY p8Changed) + Q_PROPERTY(int p9 MEMBER m_p9 NOTIFY p9Changed) +public: + explicit BenchObject(QObject *parent = 0) + : QObject(parent) + , m_p0(0) + , m_p1(0) + , m_p2(0) + , m_p3(0) + , m_p4(0) + , m_p5(0) + , m_p6(0) + , m_p7(0) + , m_p8(0) + , m_p9(0) + { } + + void change() + { + m_p0++; + m_p1++; + m_p2++; + m_p3++; + m_p4++; + m_p5++; + m_p6++; + m_p7++; + m_p8++; + m_p9++; + emit p0Changed(m_p0); + emit p1Changed(m_p1); + emit p2Changed(m_p2); + emit p3Changed(m_p3); + emit p4Changed(m_p4); + emit p5Changed(m_p5); + emit p6Changed(m_p6); + emit p7Changed(m_p7); + emit p8Changed(m_p8); + emit p9Changed(m_p9); + } + +signals: + void s0(); + void s1(); + void s2(); + void s3(); + void s4(); + void s5(); + void s6(); + void s7(); + void s8(); + void s9(); + + void p0Changed(int); + void p1Changed(int); + void p2Changed(int); + void p3Changed(int); + void p4Changed(int); + void p5Changed(int); + void p6Changed(int); + void p7Changed(int); + void p8Changed(int); + void p9Changed(int); + +public slots: + void m0(){}; + void m1(){}; + void m2(){}; + void m3(){}; + void m4(){}; + void m5(){}; + void m6(){}; + void m7(){}; + void m8(){}; + void m9(){}; + +private: + int m_p0, m_p1, m_p2, m_p3, m_p4, m_p5, m_p6, m_p7, m_p8, m_p9; +}; + class TestWebChannel : public QObject { Q_OBJECT @@ -104,6 +196,11 @@ private slots: void testInitChannel(); void testRegisterObjects(); void testInfoForObject(); + + void benchClassInfo(); + void benchInitializeClients(); + void benchPropertyUpdates(); + void benchRegisterObjects(); }; #endif // TST_WEBCHANNEL_H |