feat: completely redesign hot reloader

The hot reloader previously attempted to figure out which parent a
component would attach to as it loaded. This was fairly error prone as
it was heuristic based and didn't work as soon as you split
definitions into multiple QML files.

The new hot reloader functions by first completely building the widget
tree, then applying the old tree to the first tree and pulling out
usable values. Proxy windows now wait to appear until being reloaded.

Additionally added support for `reloadableId` to help match a
Reloadable to its value in the previous widget tree.
This commit is contained in:
outfoxxed 2024-02-16 06:38:20 -08:00
parent d6ed717c39
commit 1da43be6c0
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
17 changed files with 518 additions and 442 deletions

View file

@ -5,50 +5,47 @@
#include <qcontainerfwd.h>
#include <qlogging.h>
#include <qobject.h>
#include <qqmlengine.h>
#include "scavenge.hpp"
#include "reload.hpp"
void Variants::earlyInit(QObject* old) {
auto* oldv = qobject_cast<Variants*>(old);
if (oldv != nullptr) {
this->scavengeableInstances = std::move(oldv->instances);
}
}
void Variants::onReload(QObject* oldInstance) {
auto* old = qobject_cast<Variants*>(oldInstance);
QObject* Variants::scavengeTargetFor(QObject* /* child */) {
// Attempt to find the set that most closely matches the current one.
// This is biased to the order of the scavenge list which should help in
// case of conflicts as long as variants have not been reordered.
for (auto& [variant, instanceObj]: this->instances.values) {
QObject* oldInstance = nullptr;
if (old != nullptr) {
auto& values = old->instances.values;
if (this->activeScavengeVariant != nullptr) {
auto& values = this->scavengeableInstances.values;
if (values.empty()) return nullptr;
int matchcount = 0;
int matchi = 0;
int i = 0;
for (auto& [valueSet, _]: values) {
int count = 0;
for (auto& [k, v]: this->activeScavengeVariant->toStdMap()) {
if (valueSet.contains(k) && valueSet.value(k) == v) {
count++;
int matchcount = 0;
int matchi = 0;
int i = 0;
for (auto& [valueSet, _]: values) {
int count = 0;
for (auto& [k, v]: variant.toStdMap()) {
if (valueSet.contains(k) && valueSet.value(k) == v) {
count++;
}
}
if (count > matchcount) {
matchcount = count;
matchi = i;
}
i++;
}
if (count > matchcount) {
matchcount = count;
matchi = i;
if (matchcount > 0) {
oldInstance = values.takeAt(matchi).second;
}
i++;
}
if (matchcount > 0) {
return values.takeAt(matchi).second;
}
auto* instance = qobject_cast<Reloadable*>(instanceObj);
if (instance != nullptr) instance->onReload(oldInstance);
else Reloadable::reloadChildrenRecursive(instanceObj, oldInstance);
}
return nullptr;
}
void Variants::setVariants(QVariantList variants) {
@ -57,7 +54,7 @@ void Variants::setVariants(QVariantList variants) {
}
void Variants::componentComplete() {
this->Scavenger::componentComplete();
this->Reloadable::componentComplete();
this->updateVariants();
}
@ -96,14 +93,18 @@ void Variants::updateVariants() {
continue; // we dont need to recreate this one
}
this->activeScavengeVariant = &variant;
auto* instance = createComponentScavengeable(*this, *this->mComponent, variant);
auto* instance = this->mComponent->createWithInitialProperties(
variant,
QQmlEngine::contextForObject(this)
);
if (instance == nullptr) {
qWarning() << this->mComponent->errorString().toStdString().c_str();
qWarning() << "failed to create variant with object" << variant;
continue;
}
instance->setParent(this);
this->instances.insert(variant, instance);
}