Skip to content

Commit bcc3d42

Browse files
committed
core: switch to custom incubation controller
This change requires more QtPrivate usage but eliminates generation or cleanup related window incubation controller bugs. Additionally it enables async loads prior to rendering windows.
1 parent eecc2f8 commit bcc3d42

7 files changed

Lines changed: 166 additions & 28 deletions

File tree

changelog/next.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ set shell id.
3636
- Fixed hyprland active toplevel not resetting after window closes.
3737
- Fixed hyprland ipc window names and titles being reversed.
3838
- Fixed missing signals for system tray item title and description updates.
39+
- Fixed asynchronous loaders not working after reload.
40+
- Fixed asynchronous loaders not working before window creation.
3941

4042
## Packaging Changes
4143

src/core/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ qt_add_qml_module(quickshell-core
5151

5252
install_qml_module(quickshell-core)
5353

54-
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
54+
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets)
5555

5656
qs_module_pch(quickshell-core SET large)
5757

src/core/generation.cpp

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
4949
this->engine->addImportPath("qs:@/");
5050

5151
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
52-
this->engine->setIncubationController(&this->delayedIncubationController);
52+
this->incubationController.initLoop();
53+
this->engine->setIncubationController(&this->incubationController);
5354

5455
this->engine->addImageProvider("icon", new IconImageProvider());
5556
this->engine->addImageProvider("qsimage", new QsImageProvider());
@@ -134,7 +135,7 @@ void EngineGeneration::onReload(EngineGeneration* old) {
134135
// new generation acquires it then incubators will hang intermittently
135136
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
136137
old->incubationControllersLocked = true;
137-
old->assignIncubationController();
138+
old->updateIncubationMode();
138139
}
139140

140141
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
@@ -288,29 +289,18 @@ void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
288289

289290
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
290291
this->trackedWindows.append(window);
291-
this->assignIncubationController();
292+
this->updateIncubationMode();
292293
}
293294

294295
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
295296
this->trackedWindows.removeAll(static_cast<QQuickWindow*>(object)); // NOLINT
296-
this->assignIncubationController();
297+
this->updateIncubationMode();
297298
}
298299

299-
void EngineGeneration::assignIncubationController() {
300-
QQmlIncubationController* controller = &this->delayedIncubationController;
301-
302-
for (auto* window: this->trackedWindows) {
303-
if (auto* wctl = window->incubationController()) {
304-
controller = wctl;
305-
break;
306-
}
307-
}
308-
309-
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
310-
<< this
311-
<< "fallback:" << (controller == &this->delayedIncubationController);
312-
313-
this->engine->setIncubationController(controller);
300+
void EngineGeneration::updateIncubationMode() {
301+
// If we're in a situation with only hidden but tracked windows this might be wrong,
302+
// but it seems to at least work.
303+
this->incubationController.setIncubationMode(!this->trackedWindows.empty());
314304
}
315305

316306
EngineGeneration* EngineGeneration::currentGeneration() {

src/core/generation.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class EngineGeneration: public QObject {
6565
QFileSystemWatcher* watcher = nullptr;
6666
QVector<QString> deletedWatchedFiles;
6767
QVector<QString> extraWatchedFiles;
68-
DelayedQmlIncubationController delayedIncubationController;
68+
QsIncubationController incubationController;
6969
bool reloadComplete = false;
7070
QuickshellGlobal* qsgInstance = nullptr;
7171

@@ -89,7 +89,7 @@ private slots:
8989

9090
private:
9191
void postReload();
92-
void assignIncubationController();
92+
void updateIncubationMode();
9393
QVector<QQuickWindow*> trackedWindows;
9494
bool incubationControllersLocked = false;
9595
QHash<const void*, EngineGenerationExt*> extensions;

src/core/incubator.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#include "incubator.hpp"
22

3+
#include <private/qsgrenderloop_p.h>
4+
#include <qabstractanimation.h>
5+
#include <qguiapplication.h>
36
#include <qlogging.h>
7+
#include <qloggingcategory.h>
8+
#include <qminmax.h>
9+
#include <qnamespace.h>
10+
#include <qobject.h>
11+
#include <qobjectdefs.h>
412
#include <qqmlincubator.h>
13+
#include <qscreen.h>
514
#include <qtmetamacros.h>
615

716
#include "logcat.hpp"
@@ -15,3 +24,112 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
1524
default: break;
1625
}
1726
}
27+
28+
void QsIncubationController::initLoop() {
29+
auto* app = static_cast<QGuiApplication*>(QGuiApplication::instance()); // NOLINT
30+
this->renderLoop = QSGRenderLoop::instance();
31+
32+
QObject::connect(
33+
app,
34+
&QGuiApplication::screenAdded,
35+
this,
36+
&QsIncubationController::updateIncubationTime
37+
);
38+
39+
QObject::connect(
40+
app,
41+
&QGuiApplication::screenRemoved,
42+
this,
43+
&QsIncubationController::updateIncubationTime
44+
);
45+
46+
this->updateIncubationTime();
47+
48+
QObject::connect(
49+
this->renderLoop,
50+
&QSGRenderLoop::timeToIncubate,
51+
this,
52+
&QsIncubationController::incubate
53+
);
54+
55+
QAnimationDriver* animationDriver = this->renderLoop->animationDriver();
56+
if (animationDriver) {
57+
QObject::connect(
58+
animationDriver,
59+
&QAnimationDriver::stopped,
60+
this,
61+
&QsIncubationController::animationStopped
62+
);
63+
} else {
64+
qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot "
65+
"be used to trigger incubation.";
66+
}
67+
}
68+
69+
void QsIncubationController::setIncubationMode(bool render) {
70+
if (render == this->followRenderloop) return;
71+
this->followRenderloop = render;
72+
73+
if (render) {
74+
qCDebug(logIncubator) << "Incubation mode changed: render loop driven";
75+
} else {
76+
qCDebug(logIncubator) << "Incubation mode changed: event loop driven";
77+
}
78+
79+
if (!render && this->incubatingObjectCount()) this->incubateLater();
80+
}
81+
82+
void QsIncubationController::timerEvent(QTimerEvent* /*event*/) {
83+
this->killTimer(this->timerId);
84+
this->timerId = 0;
85+
this->incubate();
86+
}
87+
88+
void QsIncubationController::incubateLater() {
89+
if (this->followRenderloop) {
90+
if (this->timerId != 0) {
91+
this->killTimer(this->timerId);
92+
this->timerId = 0;
93+
}
94+
95+
// Incubate again at the end of the event processing queue
96+
QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection);
97+
} else if (this->timerId == 0) {
98+
// Wait for a while before processing the next batch. Using a
99+
// timer to avoid starvation of system events.
100+
this->timerId = this->startTimer(this->incubationTime);
101+
}
102+
}
103+
104+
void QsIncubationController::incubate() {
105+
if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) {
106+
if (!this->followRenderloop) {
107+
this->incubateFor(10);
108+
if (this->incubatingObjectCount()) this->incubateLater();
109+
} else if (this->renderLoop->interleaveIncubation()) {
110+
this->incubateFor(this->incubationTime);
111+
} else {
112+
this->incubateFor(this->incubationTime * 2);
113+
if (this->incubatingObjectCount()) this->incubateLater();
114+
}
115+
}
116+
}
117+
118+
void QsIncubationController::animationStopped() { this->incubate(); }
119+
120+
void QsIncubationController::incubatingObjectCountChanged(int count) {
121+
if (count
122+
&& (!this->followRenderloop
123+
|| (this->renderLoop && !this->renderLoop->interleaveIncubation())))
124+
{
125+
this->incubateLater();
126+
}
127+
}
128+
129+
void QsIncubationController::updateIncubationTime() {
130+
auto* screen = QGuiApplication::primaryScreen();
131+
if (!screen) return;
132+
133+
// 1/3 frame on primary screen
134+
this->incubationTime = qMax(1, static_cast<int>(1000 / screen->refreshRate() / 3));
135+
}

src/core/incubator.hpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <qobject.h>
4+
#include <qpointer.h>
45
#include <qqmlincubator.h>
56
#include <qtmetamacros.h>
67

@@ -25,7 +26,37 @@ class QsQmlIncubator
2526
void failed();
2627
};
2728

28-
class DelayedQmlIncubationController: public QQmlIncubationController {
29-
// Do nothing.
30-
// This ensures lazy loaders don't start blocking before onReload creates windows.
29+
class QSGRenderLoop;
30+
31+
class QsIncubationController
32+
: public QObject
33+
, public QQmlIncubationController {
34+
Q_OBJECT
35+
36+
public:
37+
void initLoop();
38+
void setIncubationMode(bool render);
39+
void incubateLater();
40+
41+
protected:
42+
void timerEvent(QTimerEvent* event) override;
43+
44+
public slots:
45+
void incubate();
46+
void animationStopped();
47+
void updateIncubationTime();
48+
49+
protected:
50+
void incubatingObjectCountChanged(int count) override;
51+
52+
private:
53+
// QPointer did not work with forward declarations prior to 6.7
54+
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
55+
QPointer<QSGRenderLoop> renderLoop = nullptr;
56+
#else
57+
QSGRenderLoop* renderLoop = nullptr;
58+
#endif
59+
int incubationTime = 0;
60+
int timerId = 0;
61+
bool followRenderloop = false;
3162
};

src/core/lazyloader.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@
8282
/// > Notably, @@Variants does not corrently support asynchronous
8383
/// > loading, meaning using it inside a LazyLoader will block similarly to not
8484
/// > having a loader to start with.
85-
///
86-
/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
87-
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
8885
class LazyLoader: public Reloadable {
8986
Q_OBJECT;
9087
/// The fully loaded item if the loader is @@loading or @@active, or `null`

0 commit comments

Comments
 (0)