From 757d3200aaf5f519669d5984a374d8349659e6be Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 8 Dec 2020 16:48:19 +0200 Subject: [PATCH] QmlDesigner: Reset puppet if shader code changes If a shader file inside the project is modified, reset the puppets to update custom materials and effects that use the modified shader. Change-Id: Ie81accf5f3029c31b49b65b5e1b53e1259a9b4eb Reviewed-by: Thomas Hartmann --- .../designercore/include/nodeinstanceview.h | 14 +++ .../instances/nodeinstanceview.cpp | 101 ++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index 0e86ad11ee7..2027fc10bb8 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -39,10 +39,13 @@ #include #include #include +#include #include #include +QT_FORWARD_DECLARE_CLASS(QFileSystemWatcher) + namespace ProjectExplorer { class Target; } @@ -219,6 +222,8 @@ private: // functions QVariant modelNodePreviewImageDataToVariant(const ModelNodePreviewImageData &imageData); void updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image); + void updateWatcher(const QString &path); + private: QHash m_imageDataMap; @@ -236,7 +241,16 @@ private: // key: fileUrl value: (key: instance qml id, value: related tool states) QHash> m_edit3DToolStates; + std::function m_crashCallback{[this] { handleCrash(); }}; + + // We use QFileSystemWatcher directly instead of Utils::FileSystemWatcher as we want + // shader changes to be applied immediately rather than requiring reactivation of + // the creator application. + QFileSystemWatcher *m_fileSystemWatcher; + QTimer m_resetTimer; + QTimer m_updateWatcherTimer; + QSet m_pendingUpdateDirs; }; } // namespace ProxyNodeInstanceView diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index be7460827d0..a06fddbc663 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -100,6 +100,7 @@ #include #include #include +#include enum { debug = false @@ -135,8 +136,44 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager : m_connectionManager(connectionManager) , m_baseStatePreviewImage(QSize(100, 100), QImage::Format_ARGB32) , m_restartProcessTimerId(0) + , m_fileSystemWatcher(new QFileSystemWatcher(this)) { m_baseStatePreviewImage.fill(0xFFFFFF); + + // Interval > 0 is used for QFileSystemWatcher related timers to allow all notifications + // related to a single event to be received before we act. + m_resetTimer.setSingleShot(true); + m_resetTimer.setInterval(100); + QObject::connect(&m_resetTimer, &QTimer::timeout, [this] { + resetPuppet(); + }); + m_updateWatcherTimer.setSingleShot(true); + m_updateWatcherTimer.setInterval(100); + QObject::connect(&m_updateWatcherTimer, &QTimer::timeout, [this] { + for (const auto &path : qAsConst(m_pendingUpdateDirs)) + updateWatcher(path); + m_pendingUpdateDirs.clear(); + }); + + connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, + [this](const QString &path) { + const QSet pendingDirs = m_pendingUpdateDirs; + for (const auto &pendingPath : pendingDirs) { + if (path.startsWith(pendingPath)) { + // no need to add path, already handled by a pending parent path + return; + } else if (pendingPath.startsWith(path)) { + // Parent path to a pending path added, remove the pending path + m_pendingUpdateDirs.remove(pendingPath); + } + } + m_pendingUpdateDirs.insert(path); + m_updateWatcherTimer.start(); + + }); + connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] { + m_resetTimer.start(); + }); } @@ -210,6 +247,8 @@ void NodeInstanceView::modelAttached(Model *model) NodeInstance newStateInstance = instanceForModelNode(stateNode); activateState(newStateInstance); } + + updateWatcher({}); } void NodeInstanceView::modelAboutToBeDetached(Model * model) @@ -227,6 +266,11 @@ void NodeInstanceView::modelAboutToBeDetached(Model * model) m_activeStateInstance = NodeInstance(); m_rootNodeInstance = NodeInstance(); AbstractView::modelAboutToBeDetached(model); + m_resetTimer.stop(); + m_updateWatcherTimer.stop(); + m_pendingUpdateDirs.clear(); + m_fileSystemWatcher->removePaths(m_fileSystemWatcher->directories()); + m_fileSystemWatcher->removePaths(m_fileSystemWatcher->files()); } void NodeInstanceView::handleCrash() @@ -1704,4 +1748,61 @@ void NodeInstanceView::updatePreviewImageForNode(const ModelNode &modelNode, con emitModelNodelPreviewPixmapChanged(modelNode, pixmap); } +void NodeInstanceView::updateWatcher(const QString &path) +{ + QString rootPath; + QStringList oldFiles; + QStringList oldDirs; + QStringList newFiles; + QStringList newDirs; + + if (path.isEmpty()) { + // Do full update + rootPath = QFileInfo(model()->fileUrl().toLocalFile()).absolutePath(); + m_fileSystemWatcher->removePaths(m_fileSystemWatcher->directories()); + m_fileSystemWatcher->removePaths(m_fileSystemWatcher->files()); + } else { + rootPath = path; + const QStringList files = m_fileSystemWatcher->files(); + const QStringList dirs = m_fileSystemWatcher->directories(); + for (const auto &file : files) { + if (file.startsWith(path)) + oldFiles.append(file); + } + for (const auto &dir : dirs) { + if (dir.startsWith(path)) + oldDirs.append(dir); + } + } + + newDirs.append(rootPath); + + QDirIterator dirIterator(rootPath, {}, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) + newDirs.append(dirIterator.next()); + + // Common shader suffixes + static const QStringList filterList {"*.frag", "*.vert", + "*.glsl", "*.glslv", "*.glslf", + "*.vsh","*.fsh"}; + + QDirIterator fileIterator(rootPath, filterList, QDir::Files, QDirIterator::Subdirectories); + while (fileIterator.hasNext()) + newFiles.append(fileIterator.next()); + + if (oldDirs != newDirs) { + if (!oldDirs.isEmpty()) + m_fileSystemWatcher->removePaths(oldDirs); + if (!newDirs.isEmpty()) + m_fileSystemWatcher->addPaths(newDirs); + } + + if (newFiles != oldFiles) { + if (!oldFiles.isEmpty()) + m_fileSystemWatcher->removePaths(oldFiles); + if (!newFiles.isEmpty()) + m_fileSystemWatcher->addPaths(newFiles); + } +} + }