mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-14 08:45:36 +08:00
Issue #15 Created CPromise, a way to set the result of QFuture objects
This enables several new features: * singleShot can return a QFuture. * a version of invokeMethod that returns a QFuture. * CGenericDBusInterface::callDBusFuture, which returns a QFuture.
This commit is contained in:
@@ -12,12 +12,14 @@
|
|||||||
#define BLACKMISC_GENERICDBUSINTERFACE_H
|
#define BLACKMISC_GENERICDBUSINTERFACE_H
|
||||||
|
|
||||||
#include "logmessage.h"
|
#include "logmessage.h"
|
||||||
|
#include "blackmisc/promise.h"
|
||||||
#include <QDBusAbstractInterface>
|
#include <QDBusAbstractInterface>
|
||||||
#include <QDBusPendingCall>
|
#include <QDBusPendingCall>
|
||||||
#include <QDBusPendingReply>
|
#include <QDBusPendingReply>
|
||||||
#include <QDBusError>
|
#include <QDBusError>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMetaMethod>
|
#include <QMetaMethod>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#ifndef Q_MOC_RUN
|
#ifndef Q_MOC_RUN
|
||||||
/*!
|
/*!
|
||||||
@@ -102,6 +104,15 @@ namespace BlackMisc
|
|||||||
return pcw;
|
return pcw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Call DBus with asynchronous return as a future
|
||||||
|
template <typename Ret, typename... Args>
|
||||||
|
QFuture<Ret> callDBusFuture(QLatin1String method, Args&&... args)
|
||||||
|
{
|
||||||
|
auto sharedPromise = QSharedPointer<CPromise<Ret>>::create();
|
||||||
|
this->callDBusAsync(method, [ = ](auto pcw) { sharedPromise->setResult(QDBusPendingReply<Ret>(*pcw)); }, std::forward<Args>(args)...);
|
||||||
|
return sharedPromise->future();
|
||||||
|
}
|
||||||
|
|
||||||
//! Cancel all asynchronous DBus calls which are currently pending
|
//! Cancel all asynchronous DBus calls which are currently pending
|
||||||
//! \warning Don't call this from inside an async callback!
|
//! \warning Don't call this from inside an async callback!
|
||||||
void cancelAllPendingAsyncCalls()
|
void cancelAllPendingAsyncCalls()
|
||||||
|
|||||||
@@ -11,10 +11,14 @@
|
|||||||
#ifndef BLACKMISC_INVOKE_H
|
#ifndef BLACKMISC_INVOKE_H
|
||||||
#define BLACKMISC_INVOKE_H
|
#define BLACKMISC_INVOKE_H
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include "blackmisc/typetraits.h"
|
#include "blackmisc/typetraits.h"
|
||||||
#include "blackmisc/integersequence.h"
|
#include "blackmisc/integersequence.h"
|
||||||
|
#include "blackmisc/promise.h"
|
||||||
|
#include <QMetaObject>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace BlackMisc
|
namespace BlackMisc
|
||||||
{
|
{
|
||||||
@@ -59,6 +63,17 @@ namespace BlackMisc
|
|||||||
return invokeSlotImpl(std::forward<F>(func), object, std::forward_as_tuple(std::forward<Ts>(args)...), seq(), std::is_member_pointer<std::decay_t<F>>());
|
return invokeSlotImpl(std::forward<F>(func), object, std::forward_as_tuple(std::forward<Ts>(args)...), seq(), std::is_member_pointer<std::decay_t<F>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Like QMetaObject::invokeMethod but the return value is accessed through a QFuture, and extra arguments can be provided.
|
||||||
|
template <typename T, typename F, typename... Ts>
|
||||||
|
auto invokeMethod(T *object, F &&func, Ts &&... args)
|
||||||
|
{
|
||||||
|
const auto invoker = [](auto &&... x) { return Private::invokeSlot(std::forward<decltype(x)>(x)...); };
|
||||||
|
auto method = std::bind(invoker, std::forward<F>(func), object, std::forward<Ts>(args)...);
|
||||||
|
CPromise<decltype(std::move(method)())> promise;
|
||||||
|
QMetaObject::invokeMethod(object, [promise, method = std::move(method)]() mutable { promise.setResultFrom(std::move(method)); });
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//! \endcond
|
//! \endcond
|
||||||
}
|
}
|
||||||
|
|||||||
142
src/blackmisc/promise.h
Normal file
142
src/blackmisc/promise.h
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/* Copyright (C) 2017
|
||||||
|
* swift Project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
|
||||||
|
* or distributed except according to the terms contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! \file
|
||||||
|
|
||||||
|
#ifndef BLACKMISC_PROMISE_H
|
||||||
|
#define BLACKMISC_PROMISE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace BlackMisc
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace Private
|
||||||
|
{
|
||||||
|
//! \private Shared data for CPromise.
|
||||||
|
//! \details QFutureInterface is undocumented but public, see also:
|
||||||
|
//! http://lists.qt-project.org/pipermail/development/2015-July/022572.html
|
||||||
|
template <typename T>
|
||||||
|
class CPromiseData final : public QFutureInterface<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CPromiseData() { this->reportStarted(); }
|
||||||
|
~CPromiseData() { if (this->isRunning()) { this->cancel(); } }
|
||||||
|
|
||||||
|
CPromiseData(const CPromiseData &) = delete;
|
||||||
|
CPromiseData &operator =(const CPromiseData &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \private
|
||||||
|
template <typename T, typename F>
|
||||||
|
void doAfter(QFuture<T> future, QObject *context, F &&func)
|
||||||
|
{
|
||||||
|
QSharedPointer<QFutureWatcher<T>> watcher(new QFutureWatcher<T>, &QObject::deleteLater);
|
||||||
|
if (!context) { context = watcher.data(); }
|
||||||
|
QObject::connect(watcher.data(), &QFutureWatcher<T>::finished, context, [watcher, func = std::forward<F>(func)]() mutable
|
||||||
|
{
|
||||||
|
if (!watcher->isCanceled()) { func(watcher->future()); }
|
||||||
|
watcher.reset();
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
|
QCoreApplication::sendPostedEvents(watcher.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Connect a slot or function to be invoked in the given context when a QFuture is finished.
|
||||||
|
*/
|
||||||
|
template <typename T, typename F>
|
||||||
|
void doAfter(QFuture<T> future, QObject *context, F &&func)
|
||||||
|
{
|
||||||
|
Private::doAfter(future, context, std::forward<F>(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Connect a slot or function to be invoked in the given context when a void QFuture is finished.
|
||||||
|
*/
|
||||||
|
template <typename F>
|
||||||
|
void doAfter(QFuture<void> future, QObject *context, F &&func)
|
||||||
|
{
|
||||||
|
Private::doAfter(future, context, [func = std::forward<F>(func)](auto&&) { func(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* A promise-based interface to QFuture, similar to std::promise for std::future.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class CPromise
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Return a future that can be used to access the result.
|
||||||
|
QFuture<T> future() { return m_data->future(); }
|
||||||
|
|
||||||
|
//! Mark the result as cancelled.
|
||||||
|
void abandon() { m_data->cancel(); m_data->reportFinished(); }
|
||||||
|
|
||||||
|
//! Set the result value that will be made available through the future.
|
||||||
|
void setResult(const T &value) { m_data->reportFinished(&value); }
|
||||||
|
|
||||||
|
//! Set the result value from the given future. Will block if future is not ready.
|
||||||
|
template <typename U>
|
||||||
|
void setResult(QFuture<U> future) { future.waitForFinished(); future.isCanceled() ? abandon() : setResult(future.result()); }
|
||||||
|
|
||||||
|
//! When the given future is ready, use its result to set the result of this promise.
|
||||||
|
template <typename U>
|
||||||
|
void chainResult(QFuture<U> future) { doAfter(future, nullptr, [self = *this](auto &&f) mutable { self.setResult(f); }); }
|
||||||
|
|
||||||
|
//! Invoke a functor and use its return value to set the result.
|
||||||
|
//! \details Useful for uniform syntax in generic code where T could be void.
|
||||||
|
template <typename F>
|
||||||
|
void setResultFrom(F &&func) { setResult(std::forward<F>(func)()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<Private::CPromiseData<T>> m_data { new Private::CPromiseData<T> };
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Specialization of CPromise for void futures.
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
class CPromise<void>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Return a future that can be used to detect when the task is complete.
|
||||||
|
QFuture<void> future() { return m_data->future(); }
|
||||||
|
|
||||||
|
//! Mark the task as cancelled.
|
||||||
|
void abandon() { m_data->cancel(); m_data->reportFinished(); }
|
||||||
|
|
||||||
|
//! Mark the task as complete.
|
||||||
|
void setResult() { m_data->reportFinished(); }
|
||||||
|
|
||||||
|
//! Wait for the given future, then mark the task as complete.
|
||||||
|
template <typename U>
|
||||||
|
void setResult(QFuture<U> future) { future.waitForFinished(); future.isCanceled() ? abandon() : setResult(); }
|
||||||
|
|
||||||
|
//! When the given future is ready, mark this promise as complete.
|
||||||
|
template <typename U>
|
||||||
|
void chainResult(QFuture<U> future) { doAfter(future, nullptr, [this](auto &&f) { setResult(f); }); }
|
||||||
|
|
||||||
|
//! Invoke a functor and mark the task as complete.
|
||||||
|
//! \details Useful for uniform syntax in generic code where T could be void.
|
||||||
|
template <typename F>
|
||||||
|
void setResultFrom(F &&func) { std::forward<F>(func)(); setResult(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<Private::CPromiseData<void>> m_data { new Private::CPromiseData<void> };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -15,10 +15,12 @@
|
|||||||
#include "blackmisc/connectionguard.h"
|
#include "blackmisc/connectionguard.h"
|
||||||
#include "blackmisc/logcategorylist.h"
|
#include "blackmisc/logcategorylist.h"
|
||||||
#include "blackmisc/invoke.h"
|
#include "blackmisc/invoke.h"
|
||||||
|
#include "blackmisc/promise.h"
|
||||||
#include "blackmisc/stacktrace.h"
|
#include "blackmisc/stacktrace.h"
|
||||||
#include "blackmisc/identifiable.h"
|
#include "blackmisc/identifiable.h"
|
||||||
#include "blackmisc/variant.h"
|
#include "blackmisc/variant.h"
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
@@ -43,24 +45,25 @@ namespace BlackMisc
|
|||||||
/*!
|
/*!
|
||||||
* Starts a single-shot timer which will call a task in the thread of the given object when it times out.
|
* Starts a single-shot timer which will call a task in the thread of the given object when it times out.
|
||||||
*
|
*
|
||||||
* Differs from QTimer::singleShot in that this implementation interacts better with QObject::moveToThread.
|
* Differs from QTimer::singleShot in that this implementation interacts better with QObject::moveToThread,
|
||||||
|
* and returns a QFuture which can be used to detect when the task has finished or obtain its return value.
|
||||||
*/
|
*/
|
||||||
//! @{
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void singleShot(int msec, QObject *target, F &&task)
|
auto singleShot(int msec, QObject *target, F &&task)
|
||||||
{
|
{
|
||||||
|
CPromise<decltype(task())> promise;
|
||||||
QSharedPointer<QTimer> timer(new QTimer, [](QObject * o) { QMetaObject::invokeMethod(o, &QObject::deleteLater); });
|
QSharedPointer<QTimer> timer(new QTimer, [](QObject * o) { QMetaObject::invokeMethod(o, &QObject::deleteLater); });
|
||||||
timer->setSingleShot(true);
|
timer->setSingleShot(true);
|
||||||
timer->moveToThread(target->thread());
|
timer->moveToThread(target->thread());
|
||||||
QObject::connect(timer.data(), &QTimer::timeout, target, [trace = getStackTrace(), task = std::forward<F>(task), timer]() mutable
|
QObject::connect(timer.data(), &QTimer::timeout, target, [trace = getStackTrace(), task = std::forward<F>(task), timer, promise]() mutable
|
||||||
{
|
{
|
||||||
static_cast<void>(trace);
|
static_cast<void>(trace);
|
||||||
timer.clear();
|
timer.clear();
|
||||||
task();
|
promise.setResultFrom(task);
|
||||||
});
|
});
|
||||||
QMetaObject::invokeMethod(timer.data(), [t = timer.data(), msec] { t->start(msec); });
|
QMetaObject::invokeMethod(timer.data(), [t = timer.data(), msec] { t->start(msec); });
|
||||||
|
return promise.future();
|
||||||
}
|
}
|
||||||
//! @}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Just a subclass of QThread whose destructor waits for the thread to finish.
|
* Just a subclass of QThread whose destructor waits for the thread to finish.
|
||||||
|
|||||||
59
tests/blackmisc/testworker/testworker.cpp
Normal file
59
tests/blackmisc/testworker/testworker.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* Copyright (C) 2017
|
||||||
|
* swift Project Community / Contributors
|
||||||
|
*
|
||||||
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
||||||
|
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
|
||||||
|
* or distributed except according to the terms contained in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! \cond PRIVATE_TESTS
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \file
|
||||||
|
* \ingroup testblackmisc
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "blackmisc/worker.h"
|
||||||
|
#include "blackmisc/eventloop.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
using namespace BlackMisc;
|
||||||
|
|
||||||
|
namespace BlackMiscTest
|
||||||
|
{
|
||||||
|
//! Aviation classes basic tests
|
||||||
|
class CTestWorker : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Standard test case constructor
|
||||||
|
explicit CTestWorker(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
//! Testing single shot
|
||||||
|
void singleShot();
|
||||||
|
};
|
||||||
|
|
||||||
|
CTestWorker::CTestWorker(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
// void
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTestWorker::singleShot()
|
||||||
|
{
|
||||||
|
QFuture<int> future = BlackMisc::singleShot(0, this, []() { return 123; });
|
||||||
|
CEventLoop::processEventsFor(0);
|
||||||
|
QVERIFY2(future.isFinished(), "Future is finished after slot has returned");
|
||||||
|
QVERIFY2(future.result() == 123, "Future provides access to slot's return value");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//! main
|
||||||
|
BLACKTEST_APPLESS_MAIN(BlackMiscTest::CTestWorker);
|
||||||
|
|
||||||
|
#include "testworker.moc"
|
||||||
|
|
||||||
|
//! \endcond
|
||||||
27
tests/blackmisc/testworker/testworker.pro
Normal file
27
tests/blackmisc/testworker/testworker.pro
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
load(common_pre)
|
||||||
|
|
||||||
|
QT += core testlib
|
||||||
|
|
||||||
|
TARGET = testslot
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
CONFIG += blackconfig
|
||||||
|
CONFIG += blackmisc
|
||||||
|
CONFIG += testcase
|
||||||
|
CONFIG += no_testcase_installs
|
||||||
|
|
||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
DEPENDPATH += \
|
||||||
|
. \
|
||||||
|
$$SourceRoot/src \
|
||||||
|
$$SourceRoot/tests \
|
||||||
|
|
||||||
|
INCLUDEPATH += \
|
||||||
|
$$SourceRoot/src \
|
||||||
|
$$SourceRoot/tests \
|
||||||
|
|
||||||
|
SOURCES += testworker.cpp
|
||||||
|
|
||||||
|
DESTDIR = $$DestRoot/bin
|
||||||
|
|
||||||
|
load(common_post)
|
||||||
Reference in New Issue
Block a user