Conversation
danvratil
left a comment
There was a problem hiding this comment.
Hi Jonah,
thanks a lot for digging into QML support and for submitting a patch! I like the general direction, this is something I've been thinking about myself, but the major drawback I see for this solution is the fact that you need to create a wrapper class every class that uses QCoro::Task<T> that you want to expose to QML.
The idea I had, and one which would also solve the problem with access to QJSEngine, is to make use of QQmlEngineExtensionPlugin. By overriding QQmlEngineExtensionPlugin::initializeEngine(QQmlEngine*) we would 1) get access to the engine pointer and 2) would register a QCoro QObject with awaitTask() method, which would be able to receive QCoro::Task<T> (inside a QVariant) and we would be able to wrap it into your QmlTask, which then allows attaching a then() continuation from JavaScript.
class QCoroQml : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QCoro::QmlTask awaitTask(const QVariant &task) {
// 1) validate task actually holds a QCoro::Task<??>
// 2) get `QCoro::Task<??>` from the variant
// 3) ???
// 4) return QmlTask wrapping the original coroutine
}
};The problem here is that you cannot extract QCoro::Task<T> from the QVariant to just wrap it into QQmlTask, since you don't know the T at runtime. The solution here would be to refactor QCoro::Task<T> to have a non-templated QCoro::TaskBase base-class (pointer to which we can get by reinterpret_cast<QCoro::TaskBase*>(variant.data())) and somehow (this is the part I haven't thought through yet) "bridge"/"wrap"/"convert"/??? it to the QmlTask.
What do you think of such approach?
Usage:
C++
class MyCppClass : public QObject {
Q_OBJECT
public:
...
Q_INVOKABLE QCoro::Task<int> myAsyncOperation();
};QML:
QCoro.awaitTask(myCppObject.myAsyncOperation()).then((result) => console.log(result))df39178 to
9aadd58
Compare
I quite like your proposed API, but I see a number of problems with implementing it. I can't really think of a way to map a Doing this conversion before going through QVariants allows to use templates and do everything automatically. For my usecase (use in applications), which I think is currently the one that most people share, it is not required to have functions that can be called from C++ and from QML. The QML engine extension is probably nevertheless a good idea, to
|
|
The approach I outlined works (see https://gist.github.com/danvratil/4636d2dc1b174275eb2d4e4f22cf4c90), but only in Qt6 - in Qt5 we would need to register QCoro::Task<> to the metatype system, which doesn't really work (QCoro::Task is not copyable). So for Qt6 we can easily do qCoro(myCppObject.myAsyncCall()).then((result) => console.log(result))For Qt5, if it's really not possible to work around the issue with metatypes, I'm fine to go your way, that is forcing QmlTask in C++ API. It would work in Qt6 too, but Qt6 would additionally allow the neat While you might be right, that it's mostly applications using QCoro now, that's not an excuse for not designing the API properly |
|
Thank you for the example code, I'll have a more in depth look later. Adding the qCoro() API for Qt6 seems like a good way to go |
|
I'm no metatype wizard either, I knew this worked for Qt6, because I already did this PoC a while ago, but I only found out now it doesn't work in Qt5 :-) Just to be clear, there's no need to build everything at once, it's fine to merge a smaller foundational code and work on the remaining bits later on, or leave it up to someone else (e.g. qCoro for Qt6) if some particular detail is of no interest or use to you. |
|
What would be the minimum amount of changes to this to consider it mergable? If needed, I can probably hack around the private API usage by storing a pointer to the QJSEngine in a global static. I would need to be documented that only one can be used at a time then. |
danvratil
left a comment
There was a problem hiding this comment.
I think the current solution is good enough for merging as v1, even with the private API usage in Qt6. The QmlTask can be used as you intended, I think it's OK and we can improve on it later iteratively.
Could you please also add documentation for QmlTask as part of the PR?
I would like to have some tests for the feature as well, but I'm fine with merging this without tests now, and adding them later (before the next release, though).
cb10c13 to
0743ceb
Compare
|
This is still missing documentation, I'll try to write that soon. |
Future work is QCoro integration See also: qcoro/qcoro#110 Contributes to: https://invent.kde.org/plasma-mobile/plasma-dialer/-/issues/36
f141b0e to
3745def
Compare
|
Thanks for adding the docs. Would you be willing to look into some basic tests? Otherwise I think it's OK to mark this as Ready for Review, so we can merge it and I can add in some tests later :) |
1c8d3b5 to
3acc542
Compare
|
There is now just one test failure left, apparently specific to windows and clang. I don't understand where it comes from yet, and expect it to be a Qt or clang bug. |
|
Looks like a Qt bug: https://bugreports.qt.io/browse/QTBUG-91768. I'm trying to reproduce on a Windows VM and build some minimal reproducer. |
danvratil
left a comment
There was a problem hiding this comment.
Thanks a lot, just a few small nitpicks after a detailed look, then we can merge it.
I'll try to reproduce the Qt bug in my Windows VM and see if there's any possible workaround (once I see where exactly the issue inside Qt is), but that's totally not a blocker for merging this.
c0637e5 to
2c26405
Compare
|
FTR: the bug with Clang on Windows has been fixed in Qt 6.4. |
|
awesome! 🎉 is it makes sense to introduce a new release? 🤔 |
|
Agreed, new release is long overdue. |
|
Using Qt private API is a very bad idea. They have no ABI compatibility between releases and GNU/Linux maintainers will need to rebuild this library after every Qt update (even minor). |
|
At this point it seems like the alternative is not having this feature. Distros can still just not build it, if nothing they want to package depends on it |
Don't build the entire QML module? I doubt this is a good idea. |
|
There's nothing else in it so far. |
|
Thanks for building this feature! For future reference, here is the Qt issue to make C++ returned QFuture usable with QML https://bugreports.qt.io/browse/QTBUG-101025. Maybe there can be some collaboration, so we do not have to use a private Qt api :) |
Contributes to #93
At this point the code allows to write the following:
While not as nice as proper support for the JavaScript async / await syntax, this allows to move forward on this.
However there are a number of problems with my implementation:
Although this is still a draft for the reasons mentioned above, I already want to gather some feedback.