Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest/QTest>
++ #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
++ std::vector<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.push_back(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
++ std::vector<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.push_back(job);
++ }
++ m_jobManager->enqueueJobs(jobList);
++
++ std::vector<QSharedPointer<Qt3DCore::QAspectJob> > jobList2;
++ for (int i = 0; i < jobCount; i++) {
++ QSharedPointer<TestAspectJob> job(new TestAspectJob(incrementFunctionCallCounter,
++ &callCounter, &value));
++ jobList2.push_back(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;
++ std::vector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
++
++ // WHEN
++ QSharedPointer<TestAspectJob> job1(new TestAspectJob(add2, &callCounter, &value));
++ jobList.push_back(job1);
++ QSharedPointer<TestAspectJob> job2(new TestAspectJob(multiplyBy2, &callCounter, &value));
++ job2->addDependency(job1);
++ jobList.push_back(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
++ std::vector<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.push_back(job1);
++ QSharedPointer<MassAspectJob> job2(new MassAspectJob(massTestFunction, &(data[i * 3 + 1])));
++ job2->addDependency(job1);
++ jobList.push_back(job2);
++ QSharedPointer<MassAspectJob> job3(new MassAspectJob(massTestFunction, &(data[i * 3 + 2])));
++ job3->addDependency(job2);
++ jobList.push_back(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"