forked from quickshell/quickshell
		
	feat: implement soft reloading
This commit is contained in:
		
							parent
							
								
									ba1e18a125
								
							
						
					
					
						commit
						362789fc46
					
				
					 11 changed files with 385 additions and 21 deletions
				
			
		| 
						 | 
					@ -27,6 +27,9 @@ qt_add_executable(qtshell
 | 
				
			||||||
	src/cpp/shell.cpp
 | 
						src/cpp/shell.cpp
 | 
				
			||||||
	src/cpp/variants.cpp
 | 
						src/cpp/variants.cpp
 | 
				
			||||||
	src/cpp/rootwrapper.cpp
 | 
						src/cpp/rootwrapper.cpp
 | 
				
			||||||
 | 
						src/cpp/proxywindow.cpp
 | 
				
			||||||
 | 
						src/cpp/scavenge.cpp
 | 
				
			||||||
 | 
						src/cpp/rootwrapper.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
qt_add_qml_module(qtshell URI QtShell)
 | 
					qt_add_qml_module(qtshell URI QtShell)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/cpp/proxywindow.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/cpp/proxywindow.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					#include "proxywindow.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmllist.h>
 | 
				
			||||||
 | 
					#include <qquickitem.h>
 | 
				
			||||||
 | 
					#include <qquickwindow.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ProxyWindowBase::~ProxyWindowBase() {
 | 
				
			||||||
 | 
						if (this->window != nullptr) {
 | 
				
			||||||
 | 
							this->window->deleteLater();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyWindowBase::earlyInit(QObject* old) {
 | 
				
			||||||
 | 
						auto* oldpw = qobject_cast<ProxyWindowBase*>(old);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (oldpw == nullptr || oldpw->window == nullptr) {
 | 
				
			||||||
 | 
							this->window = new QQuickWindow();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->window = oldpw->disownWindow();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QQuickWindow* ProxyWindowBase::disownWindow() {
 | 
				
			||||||
 | 
						auto data = this->data();
 | 
				
			||||||
 | 
						ProxyWindowBase::dataClear(&data);
 | 
				
			||||||
 | 
						data.clear(&data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto* window = this->window;
 | 
				
			||||||
 | 
						this->window = nullptr;
 | 
				
			||||||
 | 
						return window;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOLINTNEXTLINE
 | 
				
			||||||
 | 
					#define PROXYPROP(type, get, set)                                                                  \
 | 
				
			||||||
 | 
						type ProxyWindowBase::get() { return this->window->get(); }                                      \
 | 
				
			||||||
 | 
						void ProxyWindowBase::set(type value) { this->window->set(value); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PROXYPROP(bool, isVisible, setVisible);
 | 
				
			||||||
 | 
					PROXYPROP(qint32, width, setWidth);
 | 
				
			||||||
 | 
					PROXYPROP(qint32, height, setHeight);
 | 
				
			||||||
 | 
					PROXYPROP(QColor, color, setColor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// see:
 | 
				
			||||||
 | 
					// https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quick/items/qquickwindow.cpp
 | 
				
			||||||
 | 
					// https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quick/items/qquickitem.cpp
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// relevant functions are private so we call them via the property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QQmlListProperty<QObject> ProxyWindowBase::data() {
 | 
				
			||||||
 | 
						return QQmlListProperty<QObject>(
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    nullptr,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataAppend,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataCount,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataAt,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataClear,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataReplace,
 | 
				
			||||||
 | 
						    ProxyWindowBase::dataRemoveLast
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QQmlListProperty<QObject> ProxyWindowBase::dataBacker(QQmlListProperty<QObject>* prop) {
 | 
				
			||||||
 | 
						auto* that = static_cast<ProxyWindowBase*>(prop->object); // NOLINT
 | 
				
			||||||
 | 
						return that->window->property("data").value<QQmlListProperty<QObject>>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyWindowBase::dataAppend(QQmlListProperty<QObject>* prop, QObject* obj) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						backer.append(&backer, obj);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qsizetype ProxyWindowBase::dataCount(QQmlListProperty<QObject>* prop) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						return backer.count(&backer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* ProxyWindowBase::dataAt(QQmlListProperty<QObject>* prop, qsizetype i) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						return backer.at(&backer, i);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyWindowBase::dataClear(QQmlListProperty<QObject>* prop) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						backer.clear(&backer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyWindowBase::dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						backer.replace(&backer, i, obj);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyWindowBase::dataRemoveLast(QQmlListProperty<QObject>* prop) {
 | 
				
			||||||
 | 
						auto backer = dataBacker(prop);
 | 
				
			||||||
 | 
						backer.removeLast(&backer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyFloatingWindow::setVisible(bool value) {
 | 
				
			||||||
 | 
						this->geometryLocked |= value;
 | 
				
			||||||
 | 
						ProxyWindowBase::setVisible(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyFloatingWindow::setWidth(qint32 value) {
 | 
				
			||||||
 | 
						if (!this->geometryLocked) {
 | 
				
			||||||
 | 
							ProxyWindowBase::setWidth(value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ProxyFloatingWindow::setHeight(qint32 value) {
 | 
				
			||||||
 | 
						if (!this->geometryLocked) {
 | 
				
			||||||
 | 
							ProxyWindowBase::setHeight(value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/cpp/proxywindow.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/cpp/proxywindow.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcolor.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmllist.h>
 | 
				
			||||||
 | 
					#include <qqmlparserstatus.h>
 | 
				
			||||||
 | 
					#include <qquickwindow.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Proxy to an actual window exposing a limited property set with the ability to
 | 
				
			||||||
 | 
					// transfer it to a new window.
 | 
				
			||||||
 | 
					// Detaching a window and touching any property is a use after free.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// NOTE: setting an `id` in qml will point to the proxy window and not the real window so things
 | 
				
			||||||
 | 
					// like anchors dont work
 | 
				
			||||||
 | 
					class ProxyWindowBase: public Scavenger {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						Q_PROPERTY(bool visible READ isVisible WRITE setVisible);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 width READ width WRITE setWidth);
 | 
				
			||||||
 | 
						Q_PROPERTY(qint32 height READ height WRITE setHeight);
 | 
				
			||||||
 | 
						Q_PROPERTY(QColor color READ color WRITE setColor);
 | 
				
			||||||
 | 
						Q_PROPERTY(QQmlListProperty<QObject> data READ data);
 | 
				
			||||||
 | 
						Q_CLASSINFO("DefaultProperty", "data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						void earlyInit(QObject* old) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit ProxyWindowBase(QObject* parent = nullptr): Scavenger(parent) {}
 | 
				
			||||||
 | 
						~ProxyWindowBase() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ProxyWindowBase(ProxyWindowBase&) = delete;
 | 
				
			||||||
 | 
						ProxyWindowBase(ProxyWindowBase&&) = delete;
 | 
				
			||||||
 | 
						void operator=(ProxyWindowBase&) = delete;
 | 
				
			||||||
 | 
						void operator=(ProxyWindowBase&&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Disown the backing window and delete all its children.
 | 
				
			||||||
 | 
						QQuickWindow* disownWindow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool isVisible();
 | 
				
			||||||
 | 
						virtual void setVisible(bool value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qint32 width();
 | 
				
			||||||
 | 
						virtual void setWidth(qint32 value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qint32 height();
 | 
				
			||||||
 | 
						virtual void setHeight(qint32 value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QColor color();
 | 
				
			||||||
 | 
						void setColor(QColor value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QQmlListProperty<QObject> data();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						static QQmlListProperty<QObject> dataBacker(QQmlListProperty<QObject>* prop);
 | 
				
			||||||
 | 
						static void dataAppend(QQmlListProperty<QObject>* prop, QObject* obj);
 | 
				
			||||||
 | 
						static qsizetype dataCount(QQmlListProperty<QObject>* prop);
 | 
				
			||||||
 | 
						static QObject* dataAt(QQmlListProperty<QObject>* prop, qsizetype i);
 | 
				
			||||||
 | 
						static void dataClear(QQmlListProperty<QObject>* prop);
 | 
				
			||||||
 | 
						static void dataReplace(QQmlListProperty<QObject>* prop, qsizetype i, QObject* obj);
 | 
				
			||||||
 | 
						static void dataRemoveLast(QQmlListProperty<QObject>* prop);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QQuickWindow* window = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// qt attempts to resize the window but fails because wayland
 | 
				
			||||||
 | 
					// and only resizes the graphics context which looks terrible.
 | 
				
			||||||
 | 
					class ProxyFloatingWindow: public ProxyWindowBase {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						void setVisible(bool value) override;
 | 
				
			||||||
 | 
						void setWidth(qint32 value) override;
 | 
				
			||||||
 | 
						void setHeight(qint32 value) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						bool geometryLocked = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,12 @@
 | 
				
			||||||
#include <qqmlengine.h>
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
#include <qurl.h>
 | 
					#include <qurl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
#include "shell.hpp"
 | 
					#include "shell.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RootWrapper::RootWrapper(QUrl rootUrl):
 | 
					RootWrapper::RootWrapper(QUrl rootUrl):
 | 
				
			||||||
    QObject(nullptr), rootUrl(std::move(rootUrl)), engine(this) {
 | 
					    QObject(nullptr), rootUrl(std::move(rootUrl)), engine(this) {
 | 
				
			||||||
	this->reloadGraph();
 | 
						this->reloadGraph(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (this->activeRoot == nullptr) {
 | 
						if (this->activeRoot == nullptr) {
 | 
				
			||||||
		qCritical() << "could not create scene graph, exiting";
 | 
							qCritical() << "could not create scene graph, exiting";
 | 
				
			||||||
| 
						 | 
					@ -20,20 +21,24 @@ RootWrapper::RootWrapper(QUrl rootUrl):
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void RootWrapper::reloadGraph() {
 | 
					void RootWrapper::reloadGraph(bool hard) {
 | 
				
			||||||
	if (this->activeRoot != nullptr) {
 | 
						if (this->activeRoot != nullptr) {
 | 
				
			||||||
		this->engine.clearComponentCache();
 | 
							this->engine.clearComponentCache();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto component = QQmlComponent(&this->engine, this->rootUrl);
 | 
						auto component = QQmlComponent(&this->engine, this->rootUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SCAVENGE_PARENT = hard ? nullptr : this;
 | 
				
			||||||
	auto* obj = component.beginCreate(this->engine.rootContext());
 | 
						auto* obj = component.beginCreate(this->engine.rootContext());
 | 
				
			||||||
 | 
						SCAVENGE_PARENT = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (obj == nullptr) {
 | 
						if (obj == nullptr) {
 | 
				
			||||||
		qWarning() << "failed to create root component";
 | 
							qWarning() << "failed to create root component";
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto* qtsobj = qobject_cast<QtShell*>(obj);
 | 
						auto* newRoot = qobject_cast<QtShell*>(obj);
 | 
				
			||||||
	if (qtsobj == nullptr) {
 | 
						if (newRoot == nullptr) {
 | 
				
			||||||
		qWarning() << "root component was not a QtShell";
 | 
							qWarning() << "root component was not a QtShell";
 | 
				
			||||||
		delete obj;
 | 
							delete obj;
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
| 
						 | 
					@ -46,7 +51,7 @@ void RootWrapper::reloadGraph() {
 | 
				
			||||||
		this->activeRoot = nullptr;
 | 
							this->activeRoot = nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->activeRoot = qtsobj;
 | 
						this->activeRoot = newRoot;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void RootWrapper::changeRoot(QtShell* newRoot) {
 | 
					void RootWrapper::changeRoot(QtShell* newRoot) {
 | 
				
			||||||
| 
						 | 
					@ -61,4 +66,6 @@ void RootWrapper::changeRoot(QtShell* newRoot) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* RootWrapper::scavengeTargetFor(QObject* /* child */) { return this->activeRoot; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void RootWrapper::destroy() { this->deleteLater(); }
 | 
					void RootWrapper::destroy() { this->deleteLater(); }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,17 +6,20 @@
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
#include <qurl.h>
 | 
					#include <qurl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
#include "shell.hpp"
 | 
					#include "shell.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RootWrapper: public QObject {
 | 
					class RootWrapper: public QObject, virtual public Scavengeable {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit RootWrapper(QUrl rootUrl);
 | 
						explicit RootWrapper(QUrl rootUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void reloadGraph();
 | 
						void reloadGraph(bool hard);
 | 
				
			||||||
	void changeRoot(QtShell* newRoot);
 | 
						void changeRoot(QtShell* newRoot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject* scavengeTargetFor(QObject* child) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private slots:
 | 
					private slots:
 | 
				
			||||||
	void destroy();
 | 
						void destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/cpp/scavenge.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/cpp/scavenge.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlcomponent.h>
 | 
				
			||||||
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* SCAVENGE_PARENT = nullptr; // NOLINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Scavenger::classBegin() {
 | 
				
			||||||
 | 
						// prayers
 | 
				
			||||||
 | 
						if (this->parent() == nullptr) {
 | 
				
			||||||
 | 
							this->setParent(SCAVENGE_PARENT);
 | 
				
			||||||
 | 
							SCAVENGE_PARENT = nullptr;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto* parent = dynamic_cast<Scavengeable*>(this->parent());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject* old = nullptr;
 | 
				
			||||||
 | 
						if (parent != nullptr) {
 | 
				
			||||||
 | 
							old = parent->scavengeTargetFor(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->earlyInit(old);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* createComponentScavengeable(
 | 
				
			||||||
 | 
					    QObject& parent,
 | 
				
			||||||
 | 
					    QQmlComponent& component,
 | 
				
			||||||
 | 
					    QVariantMap& initialProperties
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						SCAVENGE_PARENT = &parent;
 | 
				
			||||||
 | 
						auto* instance = component.beginCreate(QQmlEngine::contextForObject(&parent));
 | 
				
			||||||
 | 
						SCAVENGE_PARENT = nullptr;
 | 
				
			||||||
 | 
						if (instance == nullptr) return nullptr;
 | 
				
			||||||
 | 
						if (instance->parent() != nullptr) instance->setParent(&parent);
 | 
				
			||||||
 | 
						component.setInitialProperties(instance, initialProperties);
 | 
				
			||||||
 | 
						component.completeCreate();
 | 
				
			||||||
 | 
						return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/cpp/scavenge.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/cpp/scavenge.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlcomponent.h>
 | 
				
			||||||
 | 
					#include <qqmlparserstatus.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern QObject* SCAVENGE_PARENT; // NOLINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Scavenger: public QObject, public QQmlParserStatus {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						Q_INTERFACES(QQmlParserStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit Scavenger(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
 | 
						~Scavenger() override = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Scavenger(Scavenger&) = delete;
 | 
				
			||||||
 | 
						Scavenger(Scavenger&&) = delete;
 | 
				
			||||||
 | 
						void operator=(Scavenger&) = delete;
 | 
				
			||||||
 | 
						void operator=(Scavenger&&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void classBegin() override;
 | 
				
			||||||
 | 
						void componentComplete() override {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// do early init, sometimes with a scavengeable target
 | 
				
			||||||
 | 
						virtual void earlyInit(QObject* old) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Scavengeable {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						Scavengeable() = default;
 | 
				
			||||||
 | 
						virtual ~Scavengeable() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Scavengeable(Scavengeable&) = delete;
 | 
				
			||||||
 | 
						Scavengeable(Scavengeable&&) = delete;
 | 
				
			||||||
 | 
						void operator=(Scavengeable&) = delete;
 | 
				
			||||||
 | 
						void operator=(Scavengeable&&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// return an old object that might have salvageable resources
 | 
				
			||||||
 | 
						virtual QObject* scavengeTargetFor(QObject* child) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* createComponentScavengeable(
 | 
				
			||||||
 | 
					    QObject& parent,
 | 
				
			||||||
 | 
					    QQmlComponent& component,
 | 
				
			||||||
 | 
					    QVariantMap& initialProperties
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
#include "shell.hpp"
 | 
					#include "shell.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
| 
						 | 
					@ -8,7 +9,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "rootwrapper.hpp"
 | 
					#include "rootwrapper.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void QtShell::reload() {
 | 
					void QtShell::reload(bool hard) {
 | 
				
			||||||
	auto* rootobj = QQmlEngine::contextForObject(this)->engine()->parent();
 | 
						auto* rootobj = QQmlEngine::contextForObject(this)->engine()->parent();
 | 
				
			||||||
	auto* root = qobject_cast<RootWrapper*>(rootobj);
 | 
						auto* root = qobject_cast<RootWrapper*>(rootobj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +18,23 @@ void QtShell::reload() {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	root->reloadGraph();
 | 
						root->reloadGraph(hard);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void QtShell::earlyInit(QObject* old) {
 | 
				
			||||||
 | 
						auto* oldshell = qobject_cast<QtShell*>(old);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (oldshell != nullptr) {
 | 
				
			||||||
 | 
							this->scavengeableChildren = std::move(oldshell->children);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* QtShell::scavengeTargetFor(QObject* /* child */) {
 | 
				
			||||||
 | 
						if (this->scavengeableChildren.length() > this->children.length()) {
 | 
				
			||||||
 | 
							return this->scavengeableChildren[this->children.length()];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QQmlListProperty<QObject> QtShell::components() {
 | 
					QQmlListProperty<QObject> QtShell::components() {
 | 
				
			||||||
| 
						 | 
					@ -36,4 +53,5 @@ QQmlListProperty<QObject> QtShell::components() {
 | 
				
			||||||
void QtShell::appendComponent(QQmlListProperty<QObject>* list, QObject* component) {
 | 
					void QtShell::appendComponent(QQmlListProperty<QObject>* list, QObject* component) {
 | 
				
			||||||
	auto* shell = static_cast<QtShell*>(list->object); // NOLINT
 | 
						auto* shell = static_cast<QtShell*>(list->object); // NOLINT
 | 
				
			||||||
	component->setParent(shell);
 | 
						component->setParent(shell);
 | 
				
			||||||
 | 
						shell->children.append(component);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,37 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qlist.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qqmlengine.h>
 | 
					#include <qqmlengine.h>
 | 
				
			||||||
#include <qqmllist.h>
 | 
					#include <qqmllist.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QtShell: public QObject {
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QtShell: public Scavenger, virtual public Scavengeable {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
	Q_PROPERTY(QQmlListProperty<QObject> components READ components FINAL);
 | 
						Q_PROPERTY(QQmlListProperty<QObject> components READ components FINAL);
 | 
				
			||||||
	Q_CLASSINFO("DefaultProperty", "components");
 | 
						Q_CLASSINFO("DefaultProperty", "components");
 | 
				
			||||||
	QML_ELEMENT;
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit QtShell(QObject* parent = nullptr): QObject(parent) {}
 | 
						explicit QtShell(QObject* parent = nullptr): Scavenger(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void earlyInit(QObject* old) override;
 | 
				
			||||||
 | 
						QObject* scavengeTargetFor(QObject* child) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QQmlListProperty<QObject> components();
 | 
						QQmlListProperty<QObject> components();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public slots:
 | 
					public slots:
 | 
				
			||||||
	void reload();
 | 
						void reload(bool hard = true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	static void appendComponent(QQmlListProperty<QObject>* list, QObject* component);
 | 
						static void appendComponent(QQmlListProperty<QObject>* list, QObject* component);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// track only the children assigned to `components` in order
 | 
				
			||||||
 | 
						QList<QObject*> children;
 | 
				
			||||||
 | 
						QList<QObject*> scavengeableChildren;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,17 +4,33 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Variants::earlyInit(QObject* old) {
 | 
				
			||||||
 | 
						auto* oldv = qobject_cast<Variants*>(old);
 | 
				
			||||||
 | 
						if (oldv != nullptr) {
 | 
				
			||||||
 | 
							this->scavengeableInstances = std::move(oldv->instances);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QObject* Variants::scavengeTargetFor(QObject* /* child */) {
 | 
				
			||||||
 | 
						if (this->activeScavengeVariant != nullptr) {
 | 
				
			||||||
 | 
							auto* r = this->scavengeableInstances.get(*this->activeScavengeVariant);
 | 
				
			||||||
 | 
							if (r != nullptr) return *r;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Variants::setVariants(QVariantList variants) {
 | 
					void Variants::setVariants(QVariantList variants) {
 | 
				
			||||||
	this->mVariants = std::move(variants);
 | 
						this->mVariants = std::move(variants);
 | 
				
			||||||
	qDebug() << "configurations updated:" << this->mVariants;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	this->updateVariants();
 | 
						this->updateVariants();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Variants::componentComplete() {
 | 
					void Variants::componentComplete() {
 | 
				
			||||||
	qDebug() << "configure ready";
 | 
						Scavenger::componentComplete();
 | 
				
			||||||
 | 
					 | 
				
			||||||
	this->updateVariants();
 | 
						this->updateVariants();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,14 +69,14 @@ void Variants::updateVariants() {
 | 
				
			||||||
				continue; // we dont need to recreate this one
 | 
									continue; // we dont need to recreate this one
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			auto* instance = this->mComponent->createWithInitialProperties(variant, nullptr);
 | 
								this->activeScavengeVariant = &variant;
 | 
				
			||||||
 | 
								auto* instance = createComponentScavengeable(*this, *this->mComponent, variant);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (instance == nullptr) {
 | 
								if (instance == nullptr) {
 | 
				
			||||||
				qWarning() << "failed to create variant with object" << variant;
 | 
									qWarning() << "failed to create variant with object" << variant;
 | 
				
			||||||
				continue;
 | 
									continue;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			instance->setParent(this);
 | 
					 | 
				
			||||||
			this->instances.insert(variant, instance);
 | 
								this->instances.insert(variant, instance);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,6 +91,17 @@ bool AwfulMap<K, V>::contains(const K& key) const {
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename K, typename V>
 | 
				
			||||||
 | 
					V* AwfulMap<K, V>::get(const K& key) {
 | 
				
			||||||
 | 
						for (auto& [k, v]: this->values) {
 | 
				
			||||||
 | 
							if (key == k) {
 | 
				
			||||||
 | 
								return &v;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename K, typename V>
 | 
					template <typename K, typename V>
 | 
				
			||||||
void AwfulMap<K, V>::insert(K key, V value) {
 | 
					void AwfulMap<K, V>::insert(K key, V value) {
 | 
				
			||||||
	this->values.push_back(QPair<K, V>(key, value));
 | 
						this->values.push_back(QPair<K, V>(key, value));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,22 +2,26 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qlist.h>
 | 
					#include <qlist.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qmap.h>
 | 
					#include <qmap.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qqmlcomponent.h>
 | 
					#include <qqmlcomponent.h>
 | 
				
			||||||
#include <qqmlparserstatus.h>
 | 
					#include <qqmlparserstatus.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "scavenge.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// extremely inefficient map
 | 
					// extremely inefficient map
 | 
				
			||||||
template <typename K, typename V>
 | 
					template <typename K, typename V>
 | 
				
			||||||
class AwfulMap {
 | 
					class AwfulMap {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	[[nodiscard]] bool contains(const K& key) const;
 | 
						[[nodiscard]] bool contains(const K& key) const;
 | 
				
			||||||
 | 
						[[nodiscard]] V* get(const K& key);
 | 
				
			||||||
	void insert(K key, V value); // assumes no duplicates
 | 
						void insert(K key, V value); // assumes no duplicates
 | 
				
			||||||
	bool remove(const K& key);   // returns true if anything was removed
 | 
						bool remove(const K& key);   // returns true if anything was removed
 | 
				
			||||||
	QList<QPair<K, V>> values;
 | 
						QList<QPair<K, V>> values;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Variants: public QObject, public QQmlParserStatus {
 | 
					class Variants: public Scavenger, virtual public Scavengeable {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
	Q_PROPERTY(QQmlComponent* component MEMBER mComponent);
 | 
						Q_PROPERTY(QQmlComponent* component MEMBER mComponent);
 | 
				
			||||||
	Q_PROPERTY(QVariantList variants MEMBER mVariants WRITE setVariants);
 | 
						Q_PROPERTY(QVariantList variants MEMBER mVariants WRITE setVariants);
 | 
				
			||||||
| 
						 | 
					@ -25,9 +29,11 @@ class Variants: public QObject, public QQmlParserStatus {
 | 
				
			||||||
	QML_ELEMENT;
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit Variants(QObject* parent = nullptr): QObject(parent) {}
 | 
						explicit Variants(QObject* parent = nullptr): Scavenger(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void earlyInit(QObject* old) override;
 | 
				
			||||||
 | 
						QObject* scavengeTargetFor(QObject* child) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void classBegin() override {};
 | 
					 | 
				
			||||||
	void componentComplete() override;
 | 
						void componentComplete() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
| 
						 | 
					@ -37,4 +43,8 @@ private:
 | 
				
			||||||
	QQmlComponent* mComponent = nullptr;
 | 
						QQmlComponent* mComponent = nullptr;
 | 
				
			||||||
	QVariantList mVariants;
 | 
						QVariantList mVariants;
 | 
				
			||||||
	AwfulMap<QVariantMap, QObject*> instances;
 | 
						AwfulMap<QVariantMap, QObject*> instances;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pointers may die post componentComplete.
 | 
				
			||||||
 | 
						AwfulMap<QVariantMap, QObject*> scavengeableInstances;
 | 
				
			||||||
 | 
						QVariantMap* activeScavengeVariant = nullptr;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue