From aa44b5066f3ccf9048b9158346aad7cae3f3dc5a Mon Sep 17 00:00:00 2001 From: Manolo Carrasco Date: Sat, 3 Oct 2026 14:48:54 +0200 Subject: [PATCH 1/2] plugin api: reload QML plugins without restarting The engines keep the code they compiled the first time a plugin ran, so editing a plugin had no effect until the application was restarted. --- framework/extensions/iextensionsuiengine.h | 4 ++++ .../extensions/internal/extensionsprovider.cpp | 5 +++++ .../extensions/internal/extensionsprovider.h | 2 ++ .../extensions/internal/extensionsuiengine.cpp | 15 +++++++++++++++ .../extensions/internal/extensionsuiengine.h | 5 +++-- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/framework/extensions/iextensionsuiengine.h b/framework/extensions/iextensionsuiengine.h index ed9cea9106..9b5b4aea39 100644 --- a/framework/extensions/iextensionsuiengine.h +++ b/framework/extensions/iextensionsuiengine.h @@ -35,5 +35,9 @@ class IExtensionsUiEngine : MODULE_CONTEXT_INTERFACE virtual QQmlEngine* qmlEngine() const = 0; virtual QQmlEngine* qmlEngineApiV1() const = 0; + + //! NOTE Drops what the engines have cached of the plugins on disk, so that + //! the next run of a plugin reads it again + virtual void clearComponentCache() = 0; }; } diff --git a/framework/extensions/internal/extensionsprovider.cpp b/framework/extensions/internal/extensionsprovider.cpp index 2ec2cb1cf0..9a864a2280 100644 --- a/framework/extensions/internal/extensionsprovider.cpp +++ b/framework/extensions/internal/extensionsprovider.cpp @@ -38,6 +38,11 @@ static Uri VIEWER_URI = Uri("muse://extensions/viewer"); void ExtensionsProvider::reloadExtensions() { + //! NOTE Without this, reloading picks up a plugin's manifest but keeps + //! running the code the engine compiled the first time, so edits to a + //! plugin only take effect after a restart. + extensionsUiEngine()->clearComponentCache(); + extensionsRegister()->reload(); } diff --git a/framework/extensions/internal/extensionsprovider.h b/framework/extensions/internal/extensionsprovider.h index 684fa0bda4..7483ef4900 100644 --- a/framework/extensions/internal/extensionsprovider.h +++ b/framework/extensions/internal/extensionsprovider.h @@ -28,6 +28,7 @@ #include "../iextensionsconfiguration.h" #include "../iextensionsregister.h" #include "../iextensionsprovider.h" +#include "../iextensionsuiengine.h" #include "interactive/iinteractive.h" #include "io/ifilesystem.h" @@ -37,6 +38,7 @@ class ExtensionsProvider : public IExtensionsProvider, public Contextable, publi GlobalInject configuration; GlobalInject extensionsRegister; GlobalInject fileSystem; + ContextInject extensionsUiEngine = { this }; ContextInject interactive = { this }; public: diff --git a/framework/extensions/internal/extensionsuiengine.cpp b/framework/extensions/internal/extensionsuiengine.cpp index 376b15af8f..765ce34d41 100644 --- a/framework/extensions/internal/extensionsuiengine.cpp +++ b/framework/extensions/internal/extensionsuiengine.cpp @@ -127,3 +127,18 @@ QQmlEngine* ExtensionsUiEngine::qmlEngineApiV1() const { return const_cast(this)->engineV1(); } + +void ExtensionsUiEngine::clearComponentCache() +{ + //! NOTE The engines cache every QML component they have compiled, so a plugin + //! edited on disk keeps running its old code until the cache is dropped. + //! The engines themselves stay: a plugin window left open was built by one of + //! them, and outlives the reload. + if (m_engine) { + m_engine->clearComponentCache(); + } + + if (m_engineV1) { + m_engineV1->clearComponentCache(); + } +} diff --git a/framework/extensions/internal/extensionsuiengine.h b/framework/extensions/internal/extensionsuiengine.h index cb15c6392a..88d5a56e40 100644 --- a/framework/extensions/internal/extensionsuiengine.h +++ b/framework/extensions/internal/extensionsuiengine.h @@ -49,8 +49,9 @@ class ExtensionsUiEngine : public QObject, public IExtensionsUiEngine, public Co ~ExtensionsUiEngine(); - QQmlEngine* qmlEngine() const; - QQmlEngine* qmlEngineApiV1() const; + QQmlEngine* qmlEngine() const override; + QQmlEngine* qmlEngineApiV1() const override; + void clearComponentCache() override; private: From 46d6417d480a197e676978421ccfc62b9a369eea Mon Sep 17 00:00:00 2001 From: Manolo Carrasco Date: Sat, 3 Oct 2026 14:48:54 +0200 Subject: [PATCH 2/2] Reload plugins through the extensions provider The button talked to the register directly, so a reload refreshed the manifests while the engines kept serving the old code. --- .../extensions/qml/Muse/Extensions/extensionslistmodel.cpp | 4 +++- .../extensions/qml/Muse/Extensions/extensionslistmodel.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/extensions/qml/Muse/Extensions/extensionslistmodel.cpp b/framework/extensions/qml/Muse/Extensions/extensionslistmodel.cpp index 0b83cccda8..071606a695 100644 --- a/framework/extensions/qml/Muse/Extensions/extensionslistmodel.cpp +++ b/framework/extensions/qml/Muse/Extensions/extensionslistmodel.cpp @@ -186,7 +186,9 @@ void ExtensionsListModel::editShortcut(const QString& extensionUri) void ExtensionsListModel::reloadPlugins() { - extensionsRegister()->reload(); + //! NOTE Through the provider: reloading the register alone leaves the QML + //! engines holding the code they compiled the first time + provider()->reloadExtensions(); } void ExtensionsListModel::removeExtension(const QString& uri) diff --git a/framework/extensions/qml/Muse/Extensions/extensionslistmodel.h b/framework/extensions/qml/Muse/Extensions/extensionslistmodel.h index fcd619fb1a..4521084fe4 100644 --- a/framework/extensions/qml/Muse/Extensions/extensionslistmodel.h +++ b/framework/extensions/qml/Muse/Extensions/extensionslistmodel.h @@ -33,6 +33,7 @@ #include "interactive/iinteractive.h" #include "extensions/iextensioninstaller.h" #include "extensions/iextensionsconfiguration.h" +#include "extensions/iextensionsprovider.h" #include "extensions/iextensionsregister.h" #include "shortcuts/ishortcutsregister.h" @@ -46,6 +47,7 @@ class ExtensionsListModel : public QAbstractListModel, public QQmlParserStatus, GlobalInject configuration; GlobalInject extensionsRegister; + ContextInject provider = { this }; ContextInject installer = { this }; ContextInject interactive = { this }; ContextInject shortcutsRegister = { this };