forked from simonbrunel/qtpromise
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
QPromise from Qt signal helpers (enhancement simonbrunel#11)
- Loading branch information
Showing
10 changed files
with
474 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
## Qt Signals | ||
|
||
QtPromise supports creating promises that are resolved or rejected by regular Qt signals. | ||
|
||
Use the [`qPromise()`](helpers/qpromise.md) helper to create a `QPromise<T>` resolved by a signal emitted from an object, passing on the signal's value if it has one: | ||
```cpp | ||
// signal: void Object::stringSignal(const QString&) | ||
QPromise<QString> p = qPromise( | ||
obj, &Object::stringSignal | ||
); | ||
p.then([](const QString& value) { | ||
// resolved with `value` from stringSignal | ||
}); | ||
``` | ||
|
||
A rejection signal is added to the promise by appending a second object/signal pair: | ||
```cpp | ||
// signal: void Object::intRejectSignal(int) | ||
QPromise<void> p = qPromise( | ||
obj, &Object::voidResolveSignal, | ||
obj, &Object::intRejectSignal, | ||
); | ||
p.fail([](int value) { | ||
// rejected with `value` from intRejectSignal | ||
}); | ||
``` | ||
|
||
Since promises require a rejection reason, void rejection signals need to be followed by a rejection value: | ||
```cpp | ||
// signal: void Object::voidRejectSignal() | ||
QPromise<void> p = qPromise( | ||
obj, &Object::voidResolveSignal, | ||
obj, &Object::voidRejectSignal, QString("Reject reason") | ||
); | ||
p.fail([](const QString& reason) { | ||
// rejected with `reason` assigned to voidRejectSignal | ||
}); | ||
``` | ||
|
||
`qPromise()` allows constructing promises from multiple rejection signals: | ||
```cpp | ||
QPromise<void> p = qPromise( | ||
obj, &Object::voidResolveSignal, | ||
obj, &Object::voidRejectSignal1, QString("Reason 1") | ||
obj, &Object::intRejectSignal2, | ||
obj, &Object::destroyed, QException() | ||
); | ||
p.fail([](const QString& reason) { | ||
// rejected with `reason` assigned to voidRejectSignal1 | ||
}).fail([](int value) { | ||
// rejected with `value` from intRejectSignal2 | ||
}).fail([](const QException&) { | ||
// QObject destroyed before promise resolved | ||
}); | ||
``` | ||
|
||
## Manually creating `QPromise<T>` from signals | ||
|
||
The `qPromise()` helper may not cover all possible use cases. If a situation requires a manual promise construction from signals, care must be taken to guarantee that all signals involved in the promise creation are disconnected after fulfillment. | ||
|
||
The easiest option is to simply delete the signal source after the promise is handled: | ||
```cpp | ||
// in promise returning method | ||
return QPromise<void>([&](const auto& resolve, const auto& reject) { | ||
QObject::connect(obj, &Object::signal, [=]() { | ||
// {... resolve/reject ...} | ||
object->deleteLater(); | ||
}); | ||
}); | ||
``` | ||
|
||
Sometimes the deletion of the signal source may not be possible. In this case, all signal connections must be tracked and disconnected manually. QtPromise provides the `QPromiseConnections` helper to handle this conveniently: | ||
```cpp | ||
// in promise returning method | ||
QPromiseConnections connections; | ||
return QPromise<void>([&](const auto& resolve, const auto& reject) { | ||
connections << QObject::connect(obj1, &Object::signal1, [=]() { | ||
// {... resolve/reject ...} | ||
}); | ||
connections << QObject::connect(obj2, &Object::signal2, [=]() { | ||
// {... resolve/reject ...} | ||
}); | ||
}.finally([=]() { | ||
connections.disconnect(); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#ifndef QTPROMISE_QPROMISESIGNALS_H | ||
#define QTPROMISE_QPROMISESIGNALS_H | ||
|
||
#include <QObject> | ||
#include <memory> | ||
#include "qpromise.h" | ||
|
||
|
||
namespace QtPromise { | ||
|
||
class QPromiseConnections : private std::shared_ptr<std::vector<QMetaObject::Connection>> | ||
{ | ||
using Connections = std::vector<QMetaObject::Connection>; | ||
using Base = std::shared_ptr<Connections>; | ||
|
||
public: | ||
QPromiseConnections() : Base(new Connections, [](Connections* c) { | ||
if (!c->empty()) { | ||
qWarning("QPromiseConnections: destroyed with unhandled connections"); | ||
disconnectConnections(c); | ||
} | ||
delete c; | ||
}) { } | ||
|
||
void operator<<(QMetaObject::Connection &&other) const | ||
{ | ||
get()->emplace_back(std::move(other)); | ||
} | ||
|
||
size_t count() const { return get()->size(); } | ||
|
||
void disconnect() const { disconnectConnections(get()); } | ||
|
||
private: | ||
static void disconnectConnections(Connections* c) Q_DECL_NOEXCEPT | ||
{ | ||
for (const auto& connection: *c) { QObject::disconnect(connection); } | ||
c->clear(); | ||
} | ||
}; | ||
|
||
} // namespace QtPromise | ||
|
||
|
||
namespace QtPromisePrivate { | ||
|
||
// Deduce promise types from Qt signals | ||
// TODO: Suppress QPrivateSignal trailing private signal args | ||
// TODO: Support deducing tuple from args once MSVC 2017 is required | ||
template<typename... Args> | ||
using TFromSignalArgs = Unqualified<typename ArgsTraits<Args...>::first>; | ||
|
||
template<typename... Args> | ||
using PromiseFromSignalArgs = typename QtPromise::QPromise<TFromSignalArgs<Args...>>; | ||
|
||
// Connect void signal to QPromiseResolve | ||
template<class Sender, typename SignalClass> | ||
void connectSignalToResolve(const QtPromise::QPromiseConnections& connections, | ||
const QtPromise::QPromiseResolve<void>& resolve, | ||
const Sender* sender, | ||
void (SignalClass::* signal)()) | ||
{ | ||
connections << QObject::connect(sender, signal, [=]() { | ||
resolve(); | ||
connections.disconnect(); | ||
}); | ||
} | ||
|
||
// Connect signal with one argument to QPromiseResolve | ||
template<class Sender, typename SignalClass, typename... SignalArgs, typename T> | ||
typename std::enable_if<(ArgsTraits<SignalArgs...>::count >= 1)>::type | ||
connectSignalToResolve(const QtPromise::QPromiseConnections& connections, | ||
const QtPromise::QPromiseResolve<T>& resolve, | ||
const Sender* sender, | ||
void (SignalClass::* signal)(SignalArgs... args)) | ||
{ | ||
connections << QObject::connect(sender, signal, [=](const T& value) { | ||
resolve(value); | ||
connections.disconnect(); | ||
}); | ||
} | ||
|
||
template<typename T> | ||
static inline void | ||
connectSignalsToReject(const QtPromise::QPromiseConnections&, | ||
const QtPromise::QPromiseReject<T>&) | ||
{ } | ||
|
||
// Connect void signal & reject reason to QPromiseReject | ||
template<typename T, class Sender, typename SignalClass, typename Reason, typename... Args> | ||
static inline void | ||
connectSignalsToReject(const QtPromise::QPromiseConnections& connections, | ||
const QtPromise::QPromiseReject<T>& reject, | ||
const Sender* sender, | ||
void (SignalClass::* signal)(), | ||
Reason&& reason, | ||
Args&&... args) | ||
{ | ||
// TODO: Capture `reason` by move/forward once QtPromise requires C++14 | ||
connections << QObject::connect(sender, signal, [=]() { | ||
reject(reason); | ||
connections.disconnect(); | ||
}); | ||
connectSignalsToReject(connections, reject, std::forward<Args>(args)...); | ||
} | ||
|
||
// Connect signal with one argument to QPromiseReject | ||
template<typename T, class Sender, typename SignalClass, typename... SignalArgs, typename... Args> | ||
static inline typename std::enable_if<(ArgsTraits<SignalArgs...>::count >= 1)>::type | ||
connectSignalsToReject(const QtPromise::QPromiseConnections& connections, | ||
const QtPromise::QPromiseReject<T>& reject, | ||
const Sender* sender, | ||
void (SignalClass::* signal)(SignalArgs...), | ||
Args&&... args) | ||
{ | ||
using Value = typename ArgsTraits<SignalArgs...>::first; | ||
connections << QObject::connect(sender, signal, [=](const Value& value) { | ||
reject(value); | ||
connections.disconnect(); | ||
}); | ||
connectSignalsToReject(connections, reject, std::forward<Args>(args)...); | ||
} | ||
|
||
} // namespace QtPromisePrivate | ||
|
||
|
||
namespace QtPromise { | ||
|
||
// QPromise<T> from resolve signal | ||
template<class Sender, typename SignalClass, typename... SignalArgs> | ||
static inline typename QtPromisePrivate::PromiseFromSignalArgs<SignalArgs...> | ||
qPromise(const Sender* sender, | ||
void (SignalClass::* signal)(SignalArgs...)) | ||
{ | ||
using T = typename QtPromisePrivate::PromiseFromSignalArgs<SignalArgs...>::Type; | ||
|
||
return QPromise<T>([&](const QPromiseResolve<T>& resolve) { | ||
QPromiseConnections connections; | ||
QtPromisePrivate::connectSignalToResolve(connections, resolve, sender, signal); | ||
}); | ||
} | ||
|
||
// QPromise<T> from resolve and reject signals | ||
template<class Sender, typename SignalClass, typename... SignalArgs, typename... Args> | ||
static inline QtPromisePrivate::PromiseFromSignalArgs<SignalArgs...> | ||
qPromise(const Sender* sender, | ||
void (SignalClass::* signal)(SignalArgs...), | ||
Args&&... args) | ||
{ | ||
using T = typename QtPromisePrivate::PromiseFromSignalArgs<SignalArgs...>::Type; | ||
|
||
return QPromise<T>([&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) { | ||
QPromiseConnections connections; | ||
QtPromisePrivate::connectSignalToResolve(connections, resolve, sender, signal); | ||
QtPromisePrivate::connectSignalsToReject(connections, reject, std::forward<Args>(args)...); | ||
}); | ||
} | ||
|
||
} // namespace QtPromise | ||
|
||
#endif // QTPROMISE_QPROMISESIGNALS_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ TEMPLATE = subdirs | |
SUBDIRS += \ | ||
benchmark \ | ||
future \ | ||
signals \ | ||
helpers \ | ||
qpromise \ | ||
requirements \ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
TARGET = tst_signals | ||
SOURCES += $$PWD/tst_signals.cpp | ||
|
||
include(../qtpromise.pri) |
Oops, something went wrong.