blob: a656c25c1450136623e15528dd10292e6ca6a415 [file] [log] [blame]
/****************************************************************************
**
** 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(this);
}
void tst_ThreadPooler::cleanupTestCase()
{
delete m_jobManager;
}
void tst_ThreadPooler::defaultPerThread()
{
// GIVEN
QAtomicInt callCounter;
int maxThreadCount = QThread::idealThreadCount();
callCounter.store(0);
// WHEN
m_jobManager->waitForPerThreadFunction(perThreadFunction, &callCounter);
// THEN
QVERIFY(maxThreadCount == callCounter.load());
}
void tst_ThreadPooler::defaultAspectQueue()
{
// GIVEN
QAtomicInt callCounter;
int value = 0; // Not used in this test
QVector<QSharedPointer<Qt3DCore::QAspectJob> > jobList;
callCounter.store(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.load());
}
/*
* 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.store(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.load());
}
/*
* 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.load();
}
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"