diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2016-06-08 12:36:11 +0200 |
---|---|---|
committer | Paul Lemire <paul.lemire@kdab.com> | 2016-07-05 05:47:48 +0000 |
commit | 8e317698fd58d7703332d5aae36a266d2efc026c (patch) | |
tree | 41c6b4538b77a3d82b5f558c7fd8bc5fe3a84ced | |
parent | 0b85e7ad6a962ad86b5c6a74ec20980e72ef9a57 (diff) |
Add AspectCommandDebugger
Will allow to send command and replies from remote applications to Qt3D.
Also added the class AsynchronousCommandReply which will behave basically
like QNetworkReply for internal commands;
Change-Id: Ia130f96387fb200658eb46a05e581abe9ef78f63
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
-rw-r--r-- | src/core/aspects/aspectcommanddebugger.cpp | 205 | ||||
-rw-r--r-- | src/core/aspects/aspectcommanddebugger_p.h | 107 | ||||
-rw-r--r-- | src/core/aspects/aspects.pri | 6 | ||||
-rw-r--r-- | src/core/aspects/qabstractaspect.cpp | 23 | ||||
-rw-r--r-- | src/core/aspects/qabstractaspect_p.h | 26 | ||||
-rw-r--r-- | src/core/core.pro | 2 |
6 files changed, 366 insertions, 3 deletions
diff --git a/src/core/aspects/aspectcommanddebugger.cpp b/src/core/aspects/aspectcommanddebugger.cpp new file mode 100644 index 000000000..e38ffc844 --- /dev/null +++ b/src/core/aspects/aspectcommanddebugger.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef QT3D_JOBS_RUN_STATS + +#include "aspectcommanddebugger_p.h" +#include <Qt3DCore/qaspectengine.h> +#include <Qt3DCore/private/qabstractaspect_p.h> +#include <QTcpSocket> +#include <QJsonDocument> +#include <QJsonObject> + +QT_BEGIN_NAMESPACE + +namespace Qt3DCore { + +namespace Debug { + +namespace { + +const qint32 MagicNumber = 0x454; + +struct CommandHeader +{ + qint32 magic; + qint32 size; +}; + +} // anonymous + +AspectCommandDebugger::ReadBuffer::ReadBuffer() + : buffer(4096, 0) + , startIdx(0) + , endIdx(0) +{ +} + +AspectCommandDebugger::AspectCommandDebugger(QObject *parent) + : QTcpServer(parent) + , m_aspectEngine(nullptr) +{ +} + +void AspectCommandDebugger::initialize() +{ + QObject::connect(this, &QTcpServer::newConnection, [this] { + QTcpSocket *socket = nextPendingConnection(); + m_connections.push_back(socket); + + QObject::connect(socket, &QTcpSocket::disconnected, [this, socket] { + m_connections.removeOne(socket); + // Destroy object to make sure all QObject connection are removed + socket->deleteLater(); + }); + + QObject::connect(socket, &QTcpSocket::readyRead, [this, socket] { + onCommandReceived(socket); + }); + }); + const bool listening = listen(QHostAddress::Any, 8883); + if (!listening) + qWarning() << Q_FUNC_INFO << "failed to listen on port 8883"; +} + +void AspectCommandDebugger::setAspectEngine(QAspectEngine *engine) +{ + m_aspectEngine = engine; +} + +void AspectCommandDebugger::asynchronousReplyFinished(AsynchronousCommandReply *reply) +{ + Q_ASSERT(reply->isFinished()); + QTcpSocket *socket = m_asyncCommandToSocketEntries.take(reply); + if (m_connections.contains(socket)) + sendReply(socket, reply->data()); + reply->deleteLater(); +} + + +// Expects to receive commands in the form +// CommandHeader { MagicNumber; size } +// JSON { +// command: "commandName" +// data: JSON Obj +// } +void AspectCommandDebugger::onCommandReceived(QTcpSocket *socket) +{ + const QByteArray newData = socket->readAll(); + m_readBuffer.buffer.insert(m_readBuffer.endIdx, newData); + m_readBuffer.endIdx += newData.size(); + + const int commandPacketSize = sizeof(CommandHeader); + while ((m_readBuffer.endIdx - m_readBuffer.startIdx) >= commandPacketSize) { + CommandHeader *header = reinterpret_cast<CommandHeader *>(m_readBuffer.buffer.data() + m_readBuffer.startIdx); + if (header->magic == MagicNumber && + (m_readBuffer.endIdx - (m_readBuffer.startIdx + commandPacketSize)) >= header->size) { + // We have a valid command + // We expect command to be a CommandHeader + some json text + const QJsonDocument doc = QJsonDocument::fromJson( + QByteArray(m_readBuffer.buffer.data() + m_readBuffer.startIdx + commandPacketSize, + header->size)); + + if (!doc.isNull()) { + qDebug() << Q_FUNC_INFO << doc.toJson(); + // Send command to the aspectEngine + QJsonObject commandObj = doc.object(); + const QJsonValue commandNameValue = commandObj.value(QLatin1String("command")); + executeCommand(commandNameValue.toString(), socket); + } + + m_readBuffer.startIdx += commandPacketSize + header->size; + } + } + if (m_readBuffer.startIdx != m_readBuffer.endIdx && m_readBuffer.startIdx != 0) { + // Copy remaining length of buffer at begininning + memcpy(m_readBuffer.buffer.data(), + m_readBuffer.buffer.constData() + m_readBuffer.startIdx, + m_readBuffer.endIdx - m_readBuffer.startIdx); + } + m_readBuffer.endIdx -= m_readBuffer.startIdx; + m_readBuffer.startIdx = 0; + +} + +void AspectCommandDebugger::sendReply(QTcpSocket *socket, const QByteArray &payload) +{ + CommandHeader replyHeader; + + replyHeader.magic = MagicNumber; + replyHeader.size = payload.size(); + // Write header + socket->write(reinterpret_cast<const char *>(&replyHeader), sizeof(CommandHeader)); + // Write payload + socket->write(payload.constData(), payload.size()); +} + +void AspectCommandDebugger::executeCommand(const QString &command, + QTcpSocket *socket) +{ + // Only a single aspect is going to reply + const QVariant response = m_aspectEngine->executeCommand(command); + if (response.userType() == qMetaTypeId<AsynchronousCommandReply *>()) { // AsynchronousCommand + // Store the command | socket in a table + AsynchronousCommandReply *reply = response.value<AsynchronousCommandReply *>(); + // Command has already been completed + if (reply->isFinished()) { + sendReply(socket, reply->data()); + } else { // Command is not completed yet + QObject::connect(reply, &AsynchronousCommandReply::finished, + this, &AspectCommandDebugger::asynchronousReplyFinished); + m_asyncCommandToSocketEntries.insert(reply, socket); + } + } else { // Synchronous command + // and send response to client + QJsonObject reply; + reply.insert(QStringLiteral("command"), QJsonValue(command)); + // TO DO: convert QVariant to QJsonDocument/QByteArray + sendReply(socket, QJsonDocument(reply).toJson()); + + } +} + +} // Debug + +} // Qt3DCore + +QT_END_NAMESPACE + +#endif diff --git a/src/core/aspects/aspectcommanddebugger_p.h b/src/core/aspects/aspectcommanddebugger_p.h new file mode 100644 index 000000000..917019b6d --- /dev/null +++ b/src/core/aspects/aspectcommanddebugger_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef QT3D_JOBS_RUN_STATS + +#ifndef QT3DCORE_DEBUG_ASPECTCOMMANDDEBUGGER_H +#define QT3DCORE_DEBUG_ASPECTCOMMANDDEBUGGER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QTcpServer> + +QT_BEGIN_NAMESPACE + +namespace Qt3DCore { + +class QAspectEngine; + +namespace Debug { + +class AsynchronousCommandReply; + +class AspectCommandDebugger : public QTcpServer +{ + Q_OBJECT +public: + explicit AspectCommandDebugger(QObject *parent = nullptr); + + void initialize(); + void setAspectEngine(QAspectEngine *engine); + +private Q_SLOTS: + void asynchronousReplyFinished(AsynchronousCommandReply *reply); + +private: + void sendReply(QTcpSocket *socket, const QByteArray &data); + void onCommandReceived(QTcpSocket *socket); + void executeCommand(const QString &command, QTcpSocket *socket); + + QVector<QTcpSocket *> m_connections; + QAspectEngine *m_aspectEngine; + + struct ReadBuffer { + ReadBuffer(); + + QByteArray buffer; + int startIdx; + int endIdx; + }; + ReadBuffer m_readBuffer; + QHash<AsynchronousCommandReply *, QTcpSocket *> m_asyncCommandToSocketEntries; +}; + +} // Debug + +} // Qt3DCore + +QT_END_NAMESPACE + +#endif // QT3DCORE_DEBUG_ASPECTCOMMANDDEBUGGER_H + +#endif // QT3D_JOBS_RUN_STATS diff --git a/src/core/aspects/aspects.pri b/src/core/aspects/aspects.pri index 40bbfe739..773c736a3 100644 --- a/src/core/aspects/aspects.pri +++ b/src/core/aspects/aspects.pri @@ -5,7 +5,8 @@ SOURCES += \ $$PWD/qaspectengine.cpp \ $$PWD/qaspectfactory.cpp \ $$PWD/qaspectmanager.cpp \ - $$PWD/qaspectthread.cpp + $$PWD/qaspectthread.cpp \ + $$PWD/aspectcommanddebugger.cpp HEADERS += \ $$PWD/qabstractaspect.h \ @@ -14,6 +15,7 @@ HEADERS += \ $$PWD/qaspectengine_p.h \ $$PWD/qaspectfactory_p.h \ $$PWD/qaspectmanager_p.h \ - $$PWD/qaspectthread_p.h + $$PWD/qaspectthread_p.h \ + $$PWD/aspectcommanddebugger_p.h INCLUDEPATH += $$PWD diff --git a/src/core/aspects/qabstractaspect.cpp b/src/core/aspects/qabstractaspect.cpp index 25f1d3fd2..13195a798 100644 --- a/src/core/aspects/qabstractaspect.cpp +++ b/src/core/aspects/qabstractaspect.cpp @@ -323,6 +323,29 @@ void QAbstractAspect::onEngineShutdown() { } +namespace Debug { + +AsynchronousCommandReply::AsynchronousCommandReply(const QString &commandName, QObject *parent) + : QObject(parent) + , m_commandName(commandName) + , m_finished(false) +{ +} + +void AsynchronousCommandReply::setFinished(bool replyFinished) +{ + m_finished = replyFinished; + if (m_finished) + emit finished(this); +} + +void AsynchronousCommandReply::setData(const QByteArray &data) +{ + m_data = data; +} + +} // Debug + } // of namespace Qt3DCore diff --git a/src/core/aspects/qabstractaspect_p.h b/src/core/aspects/qabstractaspect_p.h index 8f17bee54..d83b25e60 100644 --- a/src/core/aspects/qabstractaspect_p.h +++ b/src/core/aspects/qabstractaspect_p.h @@ -72,6 +72,32 @@ class QAbstractAspectJobManager; class QChangeArbiter; class QServiceLocator; +namespace Debug { + +class AsynchronousCommandReply : public QObject +{ + Q_OBJECT +public: + explicit AsynchronousCommandReply(const QString &commandName, QObject *parent = nullptr); + + inline QByteArray data() const { return m_data; } + inline QString commandName() const { return m_commandName; } + inline bool isFinished() const { return m_finished; } + + void setFinished(bool finished); + void setData(const QByteArray &data); + +Q_SIGNALS: + void finished(AsynchronousCommandReply *reply); + +private: + QByteArray m_data; + QString m_commandName; + bool m_finished; +}; + +} // Debug + class QT3DCORE_PRIVATE_EXPORT QAbstractAspectPrivate : public QObjectPrivate , public QBackendNodeFactory diff --git a/src/core/core.pro b/src/core/core.pro index 74aa56e95..02cd8cc80 100644 --- a/src/core/core.pro +++ b/src/core/core.pro @@ -1,7 +1,7 @@ TARGET = Qt3DCore MODULE = 3dcore -QT = core-private gui-private +QT = core-private gui-private network # Qt3D is free of Q_FOREACH - make sure it stays that way: DEFINES += QT_NO_FOREACH |