| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** 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-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtTest/QtTest> |
| #include <QtCore/QThread> |
| #include <QtCore/QAtomicInt> |
| #include <QtCore/QMutexLocker> |
| #include <QtCore/QMutex> |
| #include <QtGui/QVector3D> |
| #include <QtGui/QMatrix4x4> |
| #include <QtCore/QElapsedTimer> |
| #include <QtCore/QTimer> |
| |
| #include <Qt3DCore/private/qaspectjobmanager_p.h> |
| #include <Qt3DCore/private/qabstractaspectjobmanager_p.h> |
| #include <Qt3DCore/private/qthreadpooler_p.h> |
| #include <Qt3DCore/qaspectjob.h> |
| #include <Qt3DCore/qt3dcore_global.h> |
| #include <qmath.h> |
| |
| // Add DEFINES += QT_BUILD_INTERNAL at least to Qt3d's core.pro |
| // when running these tests. It makes QAspectJobManager available. |
| |
| class tst_ThreadPooler : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| tst_ThreadPooler() {} |
| ~tst_ThreadPooler() {} |
| |
| private: |
| Qt3DCore::QAspectJobManager *m_jobManager; |
| |
| private Q_SLOTS: |
| void initTestCase(); |
| void cleanupTestCase(); |
| |
| void defaultPerThread(); |
| void defaultAspectQueue(); |
| void doubleAspectQueue(); |
| void dependencyAspectQueue(); |
| void massTest(); |
| void perThreadUniqueCall(); |
| }; |
| |
| typedef Qt3DCore::QAspectJobManager JobManager; |
| typedef void (*TestFunction)(QAtomicInt *, int *); |
| typedef void (*MassFunction)(QVector3D *data); |
| |
| void perThreadFunction(void *arg) |
| { |
| ((QAtomicInt *)arg)->ref(); |
| } |
| |
| // General test AspectJob |
| |
| class TestAspectJob : public Qt3DCore::QAspectJob |
| { |
| public: |
| TestAspectJob(TestFunction func, QAtomicInt *counter, int *value); |
| |
| void setMutex(QMutex *mutex); |
| |
| void run() override; |
| |
| private: |
| TestFunction m_func; |
| QAtomicInt *m_counter; |
| int *m_value; |
| QMutex *m_mutex; |
| }; |
| |
| TestAspectJob::TestAspectJob(TestFunction func, QAtomicInt *counter, int *value) |
| : m_func(func), |
| m_counter(counter), |
| m_value(value) |
| { |
| } |
| |
| void TestAspectJob::setMutex(QMutex *mutex) |
| { |
| m_mutex = mutex; |
| } |
| |
| void TestAspectJob::run() |
| { |
| m_func(m_counter, m_value); |
| } |
| |
| // Mass test AspectJob |
| |
| class MassAspectJob : public Qt3DCore::QAspectJob |
| { |
| public: |
| MassAspectJob(MassFunction func, QVector3D *data); |
| |
| void run() override; |
| |
| private: |
| MassFunction m_func; |
| QVector3D *m_data; |
| }; |
| |
| struct PerThreadUniqueData |
| { |
| |
| }; |
| |
| MassAspectJob::MassAspectJob(MassFunction func, QVector3D *data) |
| : m_func(func), |
| m_data(data) |
| { |
| } |
| |
| void MassAspectJob::run() |
| { |
| m_func(m_data); |
| } |
| |
| void incrementFunctionCallCounter(QAtomicInt *counter, int *value) |
| { |
| Q_UNUSED(value); |
| |
| counter->ref(); |
| } |
| |
| void add2(QAtomicInt *counter, int *value) |
| { |
| Q_UNUSED(counter); |
| |
| // Sleep for a while so that we see that multiply task really |
| // wait for us |
| QThread::currentThread()->msleep(400); |
| *value = *value + 2; |
| } |
| |
| void multiplyBy2(QAtomicInt *counter, int *value) |
| { |
| Q_UNUSED(counter); |
| |
| *value = *value * 2; |
| } |
| |
| void massTestFunction(QVector3D *data) |
| { |
| QVector3D point(4.5f, 4.5f, 4.5f); |
| |
| QMatrix4x4 matrix; |
| matrix.lookAt(QVector3D(10.0f, 1.5f, 2.0f), QVector3D(1.0f, -1.0f, 1.0f), |
| QVector3D(0.0f, 0.0f, 1.0f)); |
| QVector3D result = matrix.map(point); |
| data->setX(result.x()); |
| data->setY(result.y()); |
| data->setZ(result.z()); |
| } |
| |
| void tst_ThreadPooler::initTestCase() |
| { |
| m_jobManager = new JobManager(nullptr); |
| } |
| |
| void tst_ThreadPooler::cleanupTestCase() |
| { |
| delete m_jobManager; |
| } |
| |
| void tst_ThreadPooler::defaultPerThread() |
| { |
| // GIVEN |
| QAtomicInt callCounter; |
| int maxThreadCount = QThread::idealThreadCount(); |
| callCounter.storeRelaxed(0); |
| |
| // WHEN |
| m_jobManager->waitForPerThreadFunction(perThreadFunction, &callCounter); |
| |
| // THEN |
| QVERIFY(maxThreadCount == callCounter.loadRelaxed()); |
| } |
| |
| void tst_ThreadPooler::defaultAspectQueue() |
| { |
| // GIVEN |
| QAtomicInt callCounter; |
| int value = 0; // Not used in this test |
| QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList; |
| callCounter.storeRelaxed(0); |
| const int jobCount = 5; |
| |
| // WHEN |
| for (int i = 0; i < jobCount; i++) { |
| QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter, |
| &callCounter, &value)); |
| jobList.append(job); |
| } |
| m_jobManager->enqueueJobs(jobList); |
| m_jobManager->waitForAllJobs(); |
| |
| // THEN |
| QVERIFY(jobCount == callCounter.loadRelaxed()); |
| } |
| |
| /* |
| * Feeds two list of jobs to queue. The pooler should be able to add jobs on |
| * the second list to execution. Single call to wait finish. |
| */ |
| void tst_ThreadPooler::doubleAspectQueue() |
| { |
| // GIVEN |
| QAtomicInt callCounter; |
| int value = 0; // Not used in this test |
| QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList; |
| callCounter.storeRelaxed(0); |
| const int jobCount = 3; |
| |
| // WHEN |
| for (int i = 0; i < jobCount; i++) { |
| QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter, |
| &callCounter, &value)); |
| jobList.append(job); |
| } |
| m_jobManager->enqueueJobs(jobList); |
| |
| QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList2; |
| for (int i = 0; i < jobCount; i++) { |
| QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter, |
| &callCounter, &value)); |
| jobList2.append(job); |
| } |
| m_jobManager->enqueueJobs(jobList2); |
| |
| m_jobManager->waitForAllJobs(); |
| |
| // THEN |
| QVERIFY(jobCount * 2 == callCounter.loadRelaxed()); |
| } |
| |
| /* |
| * Default test for jobs that have dependencies. |
| */ |
| void tst_ThreadPooler::dependencyAspectQueue() |
| { |
| // GIVEN |
| QAtomicInt callCounter; // Not used in this test |
| int value = 2; |
| QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList; |
| |
| // WHEN |
| QSharedPointer<TestAspectJob> job1(new TestAspectJob(add2, &callCounter, &value)); |
| jobList.append(job1); |
| QSharedPointer<TestAspectJob> job2(new TestAspectJob(multiplyBy2, &callCounter, &value)); |
| job2->addDependency(job1); |
| jobList.append(job2); |
| m_jobManager->enqueueJobs(jobList); |
| m_jobManager->waitForAllJobs(); |
| |
| // THEN |
| // value should be (2+2)*2 = 8 |
| QVERIFY(value == 8); |
| } |
| |
| void tst_ThreadPooler::massTest() |
| { |
| // GIVEN |
| const int mass = 600; // 600 |
| QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList; |
| QVector3D data[3 * mass]; |
| |
| // WHEN |
| QElapsedTimer timer; |
| timer.start(); |
| |
| for (int i = 0; i < mass; i++) { |
| QSharedPointer<MassAspectJob> job1(new MassAspectJob(massTestFunction, &(data[i * 3 + 0]))); |
| jobList.append(job1); |
| QSharedPointer<MassAspectJob> job2(new MassAspectJob(massTestFunction, &(data[i * 3 + 1]))); |
| job2->addDependency(job1); |
| jobList.append(job2); |
| QSharedPointer<MassAspectJob> job3(new MassAspectJob(massTestFunction, &(data[i * 3 + 2]))); |
| job3->addDependency(job2); |
| jobList.append(job3); |
| } |
| |
| m_jobManager->enqueueJobs(jobList); |
| m_jobManager->waitForAllJobs(); |
| |
| // THEN |
| qDebug() << "timer.elapsed() = " << timer.elapsed() << " ms"; |
| } |
| |
| class PerThreadUniqueTester { |
| |
| public: |
| PerThreadUniqueTester() |
| { |
| m_globalAtomic.fetchAndStoreOrdered(0); |
| m_currentIndex.fetchAndStoreOrdered(0); |
| } |
| |
| int currentJobIndex() |
| { |
| return m_currentIndex.fetchAndAddOrdered(1); |
| } |
| |
| void updateGlobalAtomic(int index) |
| { |
| m_globalAtomic.fetchAndAddOrdered(qPow(3, index)); |
| } |
| |
| quint64 globalAtomicValue() const |
| { |
| return m_globalAtomic.loadRelaxed(); |
| } |
| |
| private: |
| QAtomicInteger<quint64> m_globalAtomic; |
| QAtomicInt m_currentIndex; |
| }; |
| |
| void perThreadFunctionUnique(void *arg) |
| { |
| PerThreadUniqueTester *tester = reinterpret_cast<PerThreadUniqueTester *>(arg); |
| tester->updateGlobalAtomic(tester->currentJobIndex()); |
| } |
| |
| void tst_ThreadPooler::perThreadUniqueCall() |
| { |
| // GIVEN |
| PerThreadUniqueTester tester; |
| const int maxThreads = QThread::idealThreadCount(); |
| quint64 maxValue = 0; |
| for (int i = 0; i < maxThreads; ++i) { |
| maxValue += qPow(3, i); |
| } |
| |
| // WHEN |
| m_jobManager->waitForPerThreadFunction(perThreadFunctionUnique, &tester); |
| |
| // THEN |
| QCOMPARE(maxValue, tester.globalAtomicValue()); |
| } |
| |
| QTEST_APPLESS_MAIN(tst_ThreadPooler) |
| |
| #include "tst_threadpooler.moc" |