/**************************************************************************** ** ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QFUTURE_H #error Do not include qfuture_impl.h directly #endif #if 0 #pragma qt_sync_skip_header_check #pragma qt_sync_stop_processing #endif #include #include #include QT_BEGIN_NAMESPACE // // forward declarations // template class QFuture; template class QFutureInterface; namespace QtFuture { enum class Launch { Sync, Async, Inherit }; } namespace QtPrivate { template using EnableIfSameOrConvertible = std::enable_if_t || std::is_convertible_v>; template using EnableForVoid = std::enable_if_t>; template using EnableForNonVoid = std::enable_if_t>; template struct ResultTypeHelper { }; // The callable takes an argument of type Arg template struct ResultTypeHelper< F, Arg, typename std::enable_if_t, QFuture>>> { using ResultType = std::invoke_result_t, std::decay_t>; }; // The callable takes an argument of type QFuture template struct ResultTypeHelper< F, Arg, typename std::enable_if_t, QFuture>>> { using ResultType = std::invoke_result_t, QFuture>; }; // The callable takes an argument of type QFuture template struct ResultTypeHelper< F, void, typename std::enable_if_t, QFuture>>> { using ResultType = std::invoke_result_t, QFuture>; }; // The callable doesn't take argument template struct ResultTypeHelper< F, void, typename std::enable_if_t, QFuture>>> { using ResultType = std::invoke_result_t>; }; // Helpers to resolve argument types of callables. template struct ArgsType; template struct ArgsType { using First = Arg; static const bool HasExtraArgs = (sizeof...(Args) > 0); using AllArgs = std::conditional_t, std::decay_t...>, std::decay_t>; template static const bool CanInvokeWithArgs = std::is_invocable_v; }; template<> struct ArgsType<> { using First = void; static const bool HasExtraArgs = false; using AllArgs = void; template static const bool CanInvokeWithArgs = std::is_invocable_v; }; template struct ArgResolver : ArgResolver::operator())> { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template struct ArgResolver : public ArgsType { }; template using EnableIfInvocable = std::enable_if_t< QtPrivate::ArgResolver::template CanInvokeWithArgs>; template struct isTuple : std::false_type { }; template struct isTuple> : std::true_type { }; template inline constexpr bool isTupleV = isTuple::value; template class Continuation { public: Continuation(Function &&func, const QFuture &f, const QFutureInterface &p) : promise(p), parentFuture(f), function(std::forward(func)) { } virtual ~Continuation() = default; bool execute(); static void create(Function &&func, QFuture *f, QFutureInterface &p, QtFuture::Launch policy); static void create(Function &&func, QFuture *f, QFutureInterface &p, QThreadPool *pool); private: void fulfillPromiseWithResult(); void fulfillVoidPromise(); void fulfillPromiseWithVoidResult(); template void fulfillPromise(Args &&... args); protected: virtual void runImpl() = 0; void runFunction(); protected: QFutureInterface promise; QFuture parentFuture; Function function; }; template class SyncContinuation final : public Continuation { public: SyncContinuation(Function &&func, const QFuture &f, const QFutureInterface &p) : Continuation(std::forward(func), f, p) { } ~SyncContinuation() override = default; private: void runImpl() override { this->runFunction(); } }; template class AsyncContinuation final : public QRunnable, public Continuation { public: AsyncContinuation(Function &&func, const QFuture &f, const QFutureInterface &p, QThreadPool *pool = nullptr) : Continuation(std::forward(func), f, p), threadPool(pool) { this->promise.setRunnable(this); } ~AsyncContinuation() override = default; private: void runImpl() override // from Continuation { QThreadPool *pool = threadPool ? threadPool : QThreadPool::globalInstance(); pool->start(this); } void run() override // from QRunnable { this->runFunction(); } private: QThreadPool *threadPool; }; #ifndef QT_NO_EXCEPTIONS template class FailureHandler { public: static void create(Function &&function, QFuture *future, const QFutureInterface &promise); FailureHandler(Function &&func, const QFuture &f, const QFutureInterface &p) : promise(p), parentFuture(f), handler(std::forward(func)) { } public: void run(); private: template void handleException(); void handleAllExceptions(); private: QFutureInterface promise; QFuture parentFuture; Function handler; }; #endif template void Continuation::runFunction() { promise.reportStarted(); Q_ASSERT(parentFuture.isFinished()); #ifndef QT_NO_EXCEPTIONS try { #endif if constexpr (!std::is_void_v) { if constexpr (std::is_void_v) { fulfillPromiseWithVoidResult(); } else if constexpr (std::is_invocable_v) { fulfillPromiseWithResult(); } else { // This assert normally should never fail, this is to make sure // that nothing unexpected happend. static_assert(std::is_invocable_v>, "The continuation is not invocable with the provided arguments"); fulfillPromise(parentFuture); } } else { if constexpr (std::is_void_v) { if constexpr (std::is_invocable_v>) function(parentFuture); else function(); } else if constexpr (std::is_invocable_v) { fulfillVoidPromise(); } else { // This assert normally should never fail, this is to make sure // that nothing unexpected happend. static_assert(std::is_invocable_v>, "The continuation is not invocable with the provided arguments"); function(parentFuture); } } #ifndef QT_NO_EXCEPTIONS } catch (...) { promise.reportException(std::current_exception()); } #endif promise.reportFinished(); } template bool Continuation::execute() { Q_ASSERT(parentFuture.isFinished()); if (parentFuture.isCanceled()) { #ifndef QT_NO_EXCEPTIONS if (parentFuture.d.exceptionStore().hasException()) { // If the continuation doesn't take a QFuture argument, propagate the exception // to the caller, by reporting it. If the continuation takes a QFuture argument, // the user may want to catch the exception inside the continuation, to not // interrupt the continuation chain, so don't report anything yet. if constexpr (!std::is_invocable_v, QFuture>) { promise.reportStarted(); promise.reportException(parentFuture.d.exceptionStore().exception()); promise.reportFinished(); return false; } } else #endif { promise.reportStarted(); promise.reportCanceled(); promise.reportFinished(); return false; } } runImpl(); return true; } template void Continuation::create(Function &&func, QFuture *f, QFutureInterface &p, QtFuture::Launch policy) { Q_ASSERT(f); QThreadPool *pool = nullptr; bool launchAsync = (policy == QtFuture::Launch::Async); if (policy == QtFuture::Launch::Inherit) { launchAsync = f->d.launchAsync(); // If the parent future was using a custom thread pool, inherit it as well. if (launchAsync && f->d.threadPool()) { pool = f->d.threadPool(); p.setThreadPool(pool); } } Continuation *continuationJob = nullptr; if (launchAsync) { continuationJob = new AsyncContinuation( std::forward(func), *f, p, pool); } else { continuationJob = new SyncContinuation( std::forward(func), *f, p); } p.setLaunchAsync(launchAsync); auto continuation = [continuationJob, launchAsync]() mutable { bool isLaunched = continuationJob->execute(); // If continuation is successfully launched, AsyncContinuation will be deleted // by the QThreadPool which has started it. Synchronous continuation will be // executed immediately, so it's safe to always delete it here. if (!(launchAsync && isLaunched)) { delete continuationJob; continuationJob = nullptr; } }; f->d.setContinuation(std::move(continuation)); } template void Continuation::create(Function &&func, QFuture *f, QFutureInterface &p, QThreadPool *pool) { Q_ASSERT(f); auto continuationJob = new AsyncContinuation( std::forward(func), *f, p, pool); p.setLaunchAsync(true); p.setThreadPool(pool); auto continuation = [continuationJob]() mutable { bool isLaunched = continuationJob->execute(); // If continuation is successfully launched, AsyncContinuation will be deleted // by the QThreadPool which has started it. if (!isLaunched) { delete continuationJob; continuationJob = nullptr; } }; f->d.setContinuation(continuation); } template void Continuation::fulfillPromiseWithResult() { if constexpr (std::is_copy_constructible_v) fulfillPromise(parentFuture.result()); else fulfillPromise(parentFuture.takeResult()); } template void Continuation::fulfillVoidPromise() { if constexpr (std::is_copy_constructible_v) function(parentFuture.result()); else function(parentFuture.takeResult()); } template void Continuation::fulfillPromiseWithVoidResult() { if constexpr (std::is_invocable_v>) fulfillPromise(parentFuture); else fulfillPromise(); } template template void Continuation::fulfillPromise(Args &&... args) { if constexpr (std::is_copy_constructible_v) promise.reportResult(std::invoke(function, std::forward(args)...)); else promise.reportAndMoveResult(std::invoke(function, std::forward(args)...)); } template void fulfillPromise(QFutureInterface &promise, QFuture &future) { if constexpr (!std::is_void_v) { if constexpr (std::is_copy_constructible_v) promise.reportResult(future.result()); else promise.reportAndMoveResult(future.takeResult()); } } template void fulfillPromise(QFutureInterface &promise, Function &&handler) { if constexpr (std::is_void_v) handler(); else if constexpr (std::is_copy_constructible_v) promise.reportResult(handler()); else promise.reportAndMoveResult(handler()); } #ifndef QT_NO_EXCEPTIONS template void FailureHandler::create(Function &&function, QFuture *future, const QFutureInterface &promise) { Q_ASSERT(future); FailureHandler *failureHandler = new FailureHandler( std::forward(function), *future, promise); auto failureContinuation = [failureHandler]() mutable { failureHandler->run(); delete failureHandler; }; future->d.setContinuation(std::move(failureContinuation)); } template void FailureHandler::run() { Q_ASSERT(parentFuture.isFinished()); promise.reportStarted(); if (parentFuture.d.exceptionStore().hasException()) { using ArgType = typename QtPrivate::ArgResolver::First; if constexpr (std::is_void_v) { handleAllExceptions(); } else { handleException(); } } else { QtPrivate::fulfillPromise(promise, parentFuture); } promise.reportFinished(); } template template void FailureHandler::handleException() { try { parentFuture.d.exceptionStore().throwPossibleException(); } catch (const ArgType &e) { try { // Handle exceptions matching with the handler's argument type if constexpr (std::is_void_v) { handler(e); } else { if constexpr (std::is_copy_constructible_v) promise.reportResult(handler(e)); else promise.reportAndMoveResult(handler(e)); } } catch (...) { promise.reportException(std::current_exception()); } } catch (...) { // Exception doesn't match with handler's argument type, propagate // the exception to be handled later. promise.reportException(std::current_exception()); } } template void FailureHandler::handleAllExceptions() { try { parentFuture.d.exceptionStore().throwPossibleException(); } catch (...) { try { QtPrivate::fulfillPromise(promise, std::forward(handler)); } catch (...) { promise.reportException(std::current_exception()); } } } #endif // QT_NO_EXCEPTIONS template class CanceledHandler { public: static QFuture create(Function &&handler, QFuture *future, QFutureInterface promise) { Q_ASSERT(future); auto canceledContinuation = [parentFuture = *future, promise, handler = std::move(handler)]() mutable { promise.reportStarted(); if (parentFuture.isCanceled()) { #ifndef QT_NO_EXCEPTIONS if (parentFuture.d.exceptionStore().hasException()) { // Propagate the exception to the result future promise.reportException(parentFuture.d.exceptionStore().exception()); } else { try { #endif QtPrivate::fulfillPromise(promise, std::forward(handler)); #ifndef QT_NO_EXCEPTIONS } catch (...) { promise.reportException(std::current_exception()); } } #endif } else { QtPrivate::fulfillPromise(promise, parentFuture); } promise.reportFinished(); }; future->d.setContinuation(std::move(canceledContinuation)); return promise.future(); } }; } // namespace QtPrivate namespace QtFuture { template using ArgsType = typename QtPrivate::ArgResolver::AllArgs; template> static QFuture> connect(Sender *sender, Signal signal) { using ArgsType = ArgsType; QFutureInterface promise; promise.reportStarted(); using Connections = std::pair; auto connections = std::make_shared(); if constexpr (std::is_void_v) { connections->first = QObject::connect(sender, signal, sender, [promise, connections]() mutable { promise.reportFinished(); QObject::disconnect(connections->first); QObject::disconnect(connections->second); }); } else if constexpr (QtPrivate::isTupleV) { connections->first = QObject::connect(sender, signal, sender, [promise, connections](auto... values) mutable { promise.reportResult(std::make_tuple(values...)); promise.reportFinished(); QObject::disconnect(connections->first); QObject::disconnect(connections->second); }); } else { connections->first = QObject::connect(sender, signal, sender, [promise, connections](ArgsType value) mutable { promise.reportResult(value); promise.reportFinished(); QObject::disconnect(connections->first); QObject::disconnect(connections->second); }); } connections->second = QObject::connect(sender, &QObject::destroyed, sender, [promise, connections]() mutable { promise.reportCanceled(); promise.reportFinished(); QObject::disconnect(connections->first); QObject::disconnect(connections->second); }); return promise.future(); } } // namespace QtFuture QT_END_NAMESPACE