1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qthreadpooler_p.h" |
41 | #include <QtCore/QDebug> |
42 | |
43 | QT_BEGIN_NAMESPACE |
44 | |
45 | namespace Qt3DCore { |
46 | |
47 | QThreadPooler::QThreadPooler(QObject *parent) |
48 | : QObject(parent) |
49 | , m_futureInterface(nullptr) |
50 | , m_mutex() |
51 | , m_taskCount(0) |
52 | , m_threadPool(QThreadPool::globalInstance()) |
53 | , m_totalRunJobs(0) |
54 | { |
55 | m_threadPool->setMaxThreadCount(QThreadPooler::maxThreadCount()); |
56 | // Ensures that threads will never be recycled |
57 | m_threadPool->setExpiryTimeout(-1); |
58 | } |
59 | |
60 | QThreadPooler::~QThreadPooler() |
61 | { |
62 | // Wait till all tasks are finished before deleting mutex |
63 | QMutexLocker locker(&m_mutex); |
64 | locker.unlock(); |
65 | } |
66 | |
67 | void QThreadPooler::enqueueTasks(const QVector<RunnableInterface *> &tasks) |
68 | { |
69 | // The caller have to set the mutex |
70 | const QVector<RunnableInterface *>::const_iterator end = tasks.cend(); |
71 | |
72 | m_totalRunJobs = 0; |
73 | for (QVector<RunnableInterface *>::const_iterator it = tasks.cbegin(); |
74 | it != end; ++it) { |
75 | |
76 | // Only AspectTaskRunnables are checked for dependencies. |
77 | static const auto hasDependencies = [](RunnableInterface *task) -> bool { |
78 | return (task->type() == RunnableInterface::RunnableType::AspectTask) |
79 | && (static_cast<AspectTaskRunnable *>(task)->m_dependerCount > 0); |
80 | }; |
81 | |
82 | if (!hasDependencies(*it) && !(*it)->reserved()) { |
83 | (*it)->setReserved(true); |
84 | if ((*it)->isRequired()) { |
85 | (*it)->setPooler(this); |
86 | m_threadPool->start(runnable: (*it)); |
87 | } else { |
88 | skipTask(task: *it); |
89 | } |
90 | } |
91 | } |
92 | } |
93 | |
94 | void QThreadPooler::skipTask(RunnableInterface *task) |
95 | { |
96 | enqueueDepencies(task); |
97 | |
98 | if (currentCount() == 0) { |
99 | if (m_futureInterface) { |
100 | m_futureInterface->reportFinished(); |
101 | delete m_futureInterface; |
102 | } |
103 | m_futureInterface = nullptr; |
104 | } |
105 | |
106 | delete task; // normally gets deleted by threadpool |
107 | } |
108 | |
109 | void QThreadPooler::enqueueDepencies(RunnableInterface *task) |
110 | { |
111 | release(); |
112 | |
113 | if (task->type() == RunnableInterface::RunnableType::AspectTask) { |
114 | AspectTaskRunnable *aspectTask = static_cast<AspectTaskRunnable *>(task); |
115 | const auto &dependers = aspectTask->m_dependers; |
116 | for (auto it = dependers.begin(); it != dependers.end(); ++it) { |
117 | AspectTaskRunnable *dependerTask = static_cast<AspectTaskRunnable *>(*it); |
118 | if (--dependerTask->m_dependerCount == 0) { |
119 | if (!dependerTask->reserved()) { |
120 | dependerTask->setReserved(true); |
121 | if ((*it)->isRequired()) { |
122 | dependerTask->setPooler(this); |
123 | m_threadPool->start(runnable: dependerTask); |
124 | } else { |
125 | skipTask(task: *it); |
126 | } |
127 | } |
128 | } |
129 | } |
130 | } |
131 | } |
132 | |
133 | void QThreadPooler::taskFinished(RunnableInterface *task) |
134 | { |
135 | const QMutexLocker locker(&m_mutex); |
136 | |
137 | m_totalRunJobs++; |
138 | |
139 | enqueueDepencies(task); |
140 | |
141 | if (currentCount() == 0) { |
142 | if (m_futureInterface) { |
143 | m_futureInterface->reportFinished(); |
144 | delete m_futureInterface; |
145 | } |
146 | m_futureInterface = nullptr; |
147 | } |
148 | } |
149 | |
150 | QFuture<void> QThreadPooler::mapDependables(QVector<RunnableInterface *> &taskQueue) |
151 | { |
152 | const QMutexLocker locker(&m_mutex); |
153 | |
154 | if (!m_futureInterface) |
155 | m_futureInterface = new QFutureInterface<void>(); |
156 | if (!taskQueue.empty()) |
157 | m_futureInterface->reportStarted(); |
158 | |
159 | acquire(add: taskQueue.size()); |
160 | enqueueTasks(tasks: taskQueue); |
161 | |
162 | return QFuture<void>(m_futureInterface); |
163 | } |
164 | |
165 | int QThreadPooler::waitForAllJobs() |
166 | { |
167 | future().waitForFinished(); |
168 | return m_totalRunJobs; |
169 | } |
170 | |
171 | QFuture<void> QThreadPooler::future() |
172 | { |
173 | const QMutexLocker locker(&m_mutex); |
174 | |
175 | if (!m_futureInterface) |
176 | return QFuture<void>(); |
177 | else |
178 | return QFuture<void>(m_futureInterface); |
179 | } |
180 | |
181 | void QThreadPooler::acquire(int add) |
182 | { |
183 | // The caller have to set the mutex |
184 | |
185 | m_taskCount.fetchAndAddOrdered(valueToAdd: add); |
186 | } |
187 | |
188 | void QThreadPooler::release() |
189 | { |
190 | // The caller have to set the mutex |
191 | |
192 | m_taskCount.fetchAndAddOrdered(valueToAdd: -1); |
193 | } |
194 | |
195 | int QThreadPooler::currentCount() const |
196 | { |
197 | // The caller have to set the mutex |
198 | |
199 | return m_taskCount.loadRelaxed(); |
200 | } |
201 | |
202 | int QThreadPooler::maxThreadCount() |
203 | { |
204 | static int threadCount = 0; |
205 | |
206 | if (threadCount == 0) { |
207 | threadCount = QThread::idealThreadCount(); |
208 | const QByteArray maxThreadCount = qgetenv(varName: "QT3D_MAX_THREAD_COUNT" ); |
209 | if (!maxThreadCount.isEmpty()) { |
210 | bool conversionOK = false; |
211 | const int maxThreadCountValue = maxThreadCount.toInt(ok: &conversionOK); |
212 | if (conversionOK) |
213 | threadCount = std::min(a: threadCount, b: maxThreadCountValue); |
214 | } |
215 | } |
216 | |
217 | return threadCount; |
218 | } |
219 | |
220 | } // namespace Qt3DCore |
221 | |
222 | QT_END_NAMESPACE |
223 | |