aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/QtTaskTree/qbarriertask.cpp
blob: 303772f92e8bef13468d2977266d687c2b4b8e62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright (C) 2025 Jarek Kobus
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtTaskTree/qbarriertask.h>

#include <QtCore/private/qobject_p.h>

QT_BEGIN_NAMESPACE

using namespace QtTaskTree;

#define QT_TASKTREE_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond,  __FILE__, QT_STRINGIFY(__LINE__))
#define QT_TASKTREE_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_TASKTREE_STRING(#cond); action; } do {} while (0)

class QBarrierPrivate : public QObjectPrivate
{
public:
    std::optional<QtTaskTree::DoneResult> m_result;
    int m_limit = 1;
    int m_current = -1;
};

/*!
    \class QBarrier
    \inheaderfile qbarriertask.h
    \inmodule TaskTree
    \brief An asynchronous task that finishes on demand.
    \reentrant

    QBarrier waits for subsequent calls to \l advance() to reach
    the barrier's limit (1 by default). It finishes with
    \l {QtTaskTree::} {DoneResult::Success}.

    It's often used in QTaskTree recipes to hold the execution of subsequent
    sequential tasks until some other running task delivers
    some data which are needed to setup the subsequent tasks.
*/

/*!
    Constructs a QBarrier with a given \a parent.
*/
QBarrier::QBarrier(QObject *parent)
    : QObject(*new QBarrierPrivate, parent)
{}

QBarrier::~QBarrier() = default;

/*!
    Sets the limit to \a value. After it is started, the barrier finishes
    when the number of calls to \l advance() reaches the limit.
*/
void QBarrier::setLimit(int value)
{
    QT_TASKTREE_ASSERT(!isRunning(), return);
    QT_TASKTREE_ASSERT(value > 0, return);

    d_func()->m_limit = value;
}

/*!
    Returns the current limit of the barrier.
*/
int QBarrier::limit() const
{
    return d_func()->m_limit;
}

/*!
    Starts the barrier.
*/
void QBarrier::start()
{
    QT_TASKTREE_ASSERT(!isRunning(), return);
    d_func()->m_current = 0;
    d_func()->m_result.reset();
}

/*!
    Advances the barrier. If the number of calls to advance() reaches
    the barrier's limit, the barrier finishes with
    \l {QtTaskTree::} {DoneResult::Success}.
*/
void QBarrier::advance()
{
    // Calling advance on finished is OK
    QT_TASKTREE_ASSERT(isRunning() || d_func()->m_result, return);
    if (!isRunning()) // Can't advance not running barrier
        return;
    ++d_func()->m_current;
    if (d_func()->m_current == d_func()->m_limit)
        stopWithResult(DoneResult::Success);
}

/*!
    Stops the running barrier unconditionally with a given \a result.
    The barrier's limit is ignored.
*/
void QBarrier::stopWithResult(DoneResult result)
{
    // Calling stopWithResult on finished is OK
    QT_TASKTREE_ASSERT(isRunning() || d_func()->m_result, return);
    if (!isRunning()) // Can't advance not running barrier
        return;
    d_func()->m_current = -1;
    d_func()->m_result = result;
    Q_EMIT done(result, QPrivateSignal());
}

/*!
    Returns \c true if the barrier is currently running;
    otherwise returns \c false.
*/
bool QBarrier::isRunning() const
{
    return d_func()->m_current >= 0;
}

/*!
    Returns the current advance count of the barrier.
*/
int QBarrier::current() const
{
    return d_func()->m_current;
}

/*!
    Returns the result of barrier execution. If barrier wasn't started
    or it's still running, the returned optional is empty.
    Otherwise, it returns the result of the last execution.
*/
std::optional<DoneResult> QBarrier::result() const
{
    return d_func()->m_result;
}

/*!
    \fn void QBarrier::done(QtTaskTree::DoneResult result)

    This signal is emitted when the barrier finished,
    passing the final \a result of the execution.
*/

/*!
    \typedef QBarrierTask
    \relates QCustomTask

    Type alias for the QCustomTask<QBarrier>, to be used inside recipes.
*/

/*!
    \class QStartedBarrier
    \inheaderfile qbarriertask.h
    \inmodule TaskTree
    \brief A started QBarrier with a given Limit.
    \reentrant

    QStartedBarrier is a QBarrier with a given Limit,
    already started when constucted.
*/

/*!
    \fn template <int Limit = 1> QStartedBarrier<Limit>::QStartedBarrier(QObject *parent = nullptr)

    Creates started QBarrier with a given \a parent and Limit.
    The default Limit is 1.
*/

/*!
    \typedef QStoredMultiBarrier
    \relates QStartedBarrier

    Type alias for the QtTaskTree::Storage<QStartedBarrier<Limit>>,
    to be used inside recipes.
*/

/*!
    \typedef QStoredBarrier
    \relates QStartedBarrier

    Type alias for the QStoredMultiBarrier<1>, to be used inside recipes.
*/

namespace QtTaskTree {

/*!
    \fn template <int Limit> ExecutableItem barrierAwaiterTask(const QStoredMultiBarrier<Limit> &storedBarrier)

    Returns the awaiter task that finishes when passed \a storedBarrier
    is finished.

    \note The passed \a storedBarrier needs to be placed in the same recipe
          as a sibling item to the returned task or in any ancestor \l Group,
          otherwise you may expect a crash.
*/

/*!
    \fn template <typename Signal> ExecutableItem signalAwaiterTask(const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal)

    Returns the awaiter task that finishes when passed \a sender emits
    the \a signal.

    \note The connection to the passed \a sender and \a signal is established
          not when the returned task is created or placed into a recipe,
          but when the task is started by the QTaskTree. Ensure, the passed
          \a sender outlives any running task tree containing the returned task.
          If the \a signal was emitted before the task started, i.e. before
          the connection was established, the task will finish on first
          emission of the \a signal after the task started.
*/

/*!
    \typedef QtTaskTree::BarrierKickerGetter
    \relates QStartedBarrier

    Type alias for the function taking a QStoredBarrier and returning
    \l {QtTaskTree::} {ExecutableItem}, i.e.
    \c std::function<ExecutableItem(const QStoredBarrier &)>,
    to be used inside \l {QtTaskTree::} {When} constructor.
*/

class WhenPrivate : public QSharedData
{
public:
    WhenPrivate(const BarrierKickerGetter &kicker, WorkflowPolicy policy)
        : m_barrierKicker(kicker)
        , m_workflowPolicy(policy)
    {}
    BarrierKickerGetter m_barrierKicker;
    WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
};

/*!
    \class QtTaskTree::When
    \inheaderfile qbarriertask.h
    \inmodule TaskTree
    \brief An element delaying the execution of a body until barrier advance.
    \reentrant

    When is used to delay the execution of a \l Do body until related
    QStoredBarrier is advanced or until QCustomTask's signal is emitted.
    It is used in conjunction with \l Do element like: \c {When () >> Do {}}.

    \sa Do
*/

/*!
    Creates a delaying element, taking \a kicker returning ExecutableItem
    to be run in parallel with a \l Do body. The \l Do body is delayed
    until the QStoredBarrier passed to the \a kicker is advanced.
    The ExecutableItem returned by the \a kicker and the \l Do body
    will run in parallel using the \a policy.

    For example, if you want to delay the execution of the subsequent tasks
    until the QProcess is started, the recipe could look like:

    \code
        const auto kicker = [](const QStoredBarrier &barrier) {
            const auto onSetup = [barrier](QProcess &process) {
                QObject::connect(&process, &QProcess::started, barrier.activeStorage(), &QBarrier::advance);
                ... // Setup process program, arguments, environment, etc...
            };
            return QProcessTask(onSetup);
        };

        const Group recipe {
            When (kicker) >> Do {
                delayedTask1,
                ...
            }
        };
    \endcode

    When the above recipe is executed, the QTaskTree runs a QProcessTask
    in parallel with a \l Do body. A \l Do body is initially on hold -
    it will continue after the barrier passed to the kicker will advance.
    This will happen as soon as the QProcess is started. Since than,
    QProcess runs in parallel with the \l Do body.

    \sa Do
*/
When::When(const BarrierKickerGetter &kicker, WorkflowPolicy policy)
    : d(new WhenPrivate{kicker, policy})
{}

/*!
    \fn template <typename Task, typename Adapter, typename Deleter, typename Signal> When::When(const QCustomTask<Task, Adapter, Deleter> &customTask, Signal signal, QtTaskTree::WorkflowPolicy policy = QtTaskTree::WorkflowPolicy::StopOnError)

    Creates a delaying element, taking \a customTask and its \c Task's
    \a signal to be run in parallel with a \l Do body. The \l Do body is
    delayed until the \c Task's \a signal is emitted. The \a customTask
    and the \l Do body will run in parallel using the \a policy.

    The code from the other When's constructor could be simplified to:

    \code
        const auto onSetup = [barrier](QProcess &process) {
            ... // Setup process program, arguments, environment, etc...
        };

        const Group recipe {
            When (QProcessTask(onSetup), &QProcess::started) >> Do {
                delayedTask1,
                ...
            }
        };
    \endcode

    \note The \c Task type of the passed \a customTask needs to be derived
          from QObject.

    \sa Do
*/

When::~When() = default;
When::When(const When &other) = default;
When::When(When &&other) noexcept = default;
When &When::operator=(const When &other) = default;

/*!
    Combines \a whenItem with \a doItem body and returns a \l Group
    ready to be used in task tree recipes.
*/
Group operator>>(const When &whenItem, const Do &doItem)
{
    const QStoredBarrier barrier;

    return {
        barrier,
        parallel,
        workflowPolicy(whenItem.d->m_workflowPolicy),
        whenItem.d->m_barrierKicker(barrier),
        Group {
            barrierAwaiterTask(barrier),
            doItem.children()
        }
    };
}

} // namespace QtTaskTree

QT_END_NAMESPACE