#include "reload.hpp" #include #include #include #include #include "generation.hpp" void Reloadable::componentComplete() { this->engineGeneration = EngineGeneration::findObjectGeneration(this); if (this->engineGeneration != nullptr) { // When called this way there is no chance a reload will have old data, // but this will at least help prevent weird behaviors due to never getting a reload. if (this->engineGeneration->reloadComplete) { // Delayed due to Component.onCompleted running after QQmlParserStatus::componentComplete. QTimer::singleShot(0, this, &Reloadable::onReloadFinished); // This only matters for preventing the above timer from UAFing the generation, // so it isn't connected anywhere else. QObject::connect( this->engineGeneration, &QObject::destroyed, this, &Reloadable::onGenerationDestroyed ); } else { QObject::connect( this->engineGeneration, &EngineGeneration::reloadFinished, this, &Reloadable::onReloadFinished ); } } } void Reloadable::reload(QObject* oldInstance) { if (this->reloadComplete) return; this->onReload(oldInstance); this->reloadComplete = true; if (this->engineGeneration != nullptr) { QObject::disconnect( this->engineGeneration, &EngineGeneration::reloadFinished, this, &Reloadable::onReloadFinished ); } } void Reloadable::onReloadFinished() { this->reload(nullptr); } void Reloadable::onGenerationDestroyed() { this->engineGeneration = nullptr; } void ReloadPropagator::onReload(QObject* oldInstance) { auto* old = qobject_cast(oldInstance); for (auto i = 0; i < this->mChildren.length(); i++) { auto* newChild = qobject_cast(this->mChildren.at(i)); if (newChild != nullptr) { auto* oldChild = old == nullptr || old->mChildren.length() <= i ? nullptr : qobject_cast(old->mChildren.at(i)); newChild->reload(oldChild); } else { Reloadable::reloadRecursive(newChild, oldInstance); } } } QQmlListProperty ReloadPropagator::data() { return QQmlListProperty( this, nullptr, &ReloadPropagator::appendComponent, nullptr, nullptr, nullptr, nullptr, nullptr ); } void ReloadPropagator::appendComponent(QQmlListProperty* list, QObject* obj) { auto* self = static_cast(list->object); // NOLINT obj->setParent(self); self->mChildren.append(obj); } void Reloadable::reloadRecursive(QObject* newObj, QObject* oldRoot) { auto* reloadable = qobject_cast(newObj); if (reloadable != nullptr) { QObject* oldInstance = nullptr; if (oldRoot != nullptr && !reloadable->mReloadableId.isEmpty()) { oldInstance = Reloadable::getChildByReloadId(oldRoot, reloadable->mReloadableId); } // pass handling to the child's onReload, which should call back into reloadRecursive, // with its oldInstance becoming the new oldRoot. reloadable->reload(oldInstance); } else if (newObj != nullptr) { Reloadable::reloadChildrenRecursive(newObj, oldRoot); } } void Reloadable::reloadChildrenRecursive(QObject* newRoot, QObject* oldRoot) { for (auto* child: newRoot->children()) { Reloadable::reloadRecursive(child, oldRoot); } } QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId) { for (auto* child: parent->children()) { auto* reloadable = qobject_cast(child); if (reloadable != nullptr) { if (reloadable->mReloadableId == reloadId) return reloadable; // if not then don't check its children as thats a seperate reload scope. } else { auto* reloadable = Reloadable::getChildByReloadId(child, reloadId); if (reloadable != nullptr) return reloadable; } } return nullptr; } void PostReloadHook::postReloadTree(QObject* root) { for (auto* child: root->children()) { PostReloadHook::postReloadTree(child); } if (auto* self = dynamic_cast(root)) { self->onPostReload(); } }