diff options
Diffstat (limited to 'examples/qtconcurrent/primecounter')
-rw-r--r-- | examples/qtconcurrent/primecounter/CMakeLists.txt | 41 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/doc/images/primecounter.png | bin | 0 -> 9732 bytes | |||
-rw-r--r-- | examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc | 89 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/main.cpp | 18 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/primecounter.cpp | 139 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/primecounter.h | 49 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/primecounter.pro | 9 | ||||
-rw-r--r-- | examples/qtconcurrent/primecounter/primecounter.ui | 177 |
8 files changed, 522 insertions, 0 deletions
diff --git a/examples/qtconcurrent/primecounter/CMakeLists.txt b/examples/qtconcurrent/primecounter/CMakeLists.txt new file mode 100644 index 0000000000..c8ac5f20e8 --- /dev/null +++ b/examples/qtconcurrent/primecounter/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(primecounter LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets) + +qt_standard_project_setup() + +qt_add_executable(primecounter + main.cpp + primecounter.ui + primecounter.cpp + primecounter.h +) + +set_target_properties(primecounter PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(primecounter PRIVATE + Qt6::Concurrent + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +install(TARGETS primecounter + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET primecounter + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/qtconcurrent/primecounter/doc/images/primecounter.png b/examples/qtconcurrent/primecounter/doc/images/primecounter.png Binary files differnew file mode 100644 index 0000000000..d611a507b5 --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/images/primecounter.png diff --git a/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc new file mode 100644 index 0000000000..de6c94b0cf --- /dev/null +++ b/examples/qtconcurrent/primecounter/doc/src/qtconcurrent-primecounter.qdoc @@ -0,0 +1,89 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example primecounter + \meta tags {widgets, threads} + \title Prime Counter + \ingroup qtconcurrentexamples + \examplecategory {Data Processing & I/O} + \brief Demonstrates how to monitor the progress of concurrent operations. + + The following example demonstrates how to create an interactive and + non-blocking QtWidgets application using the QFutureWatcher class and the + \l {Concurrent Filter-Reduce} {filteredReduced} functions from + \l {Qt Concurrent}. With this example, the user can create a QList of + integers that can be resized. The list will be automatically filled with + natural numbers starting from 1 up to n. The program will then check for + prime numbers within the list and display the total count of prime numbers + found. + + \image primecounter.png + + \include examples-run.qdocinc + + \section1 Setting up the connections + + The \l {Qt Concurrent} library provides the + \l {Concurrent Filter-Reduce} {filteredReduced} functions, which can operate + in two modes: + \l {QtConcurrent::ReduceOption} {OrderedReduce and UnorderedReduce}. In + \c OrderedReduce mode, the reducing function is called in the order of the + original sequence, whereas in \c UnorderedReduce mode, the elements are + accessed randomly. + + After configuring the UI with the desired elements, it is necessary to + connect them to the signals of the concurrent operations using the Qt + \l {Signals & Slots} mechanism. In this example, we use the QFutureWatcher + class to monitor the progress of the concurrent operations and provide the + signals required to implement the interactive GUI. + + \dots + \snippet primecounter/primecounter.cpp 1 + \dots + + The QFutureWatcher class plays a vital role in this example as it provides + the signals required to update the UI in response to changes in the + concurrent operations. + + \section1 Starting the concurrent operation + + After connecting all the \l {Signals & Slots}, and when the user presses + the QPushButton, the \c {start()} function is called. + + In the \c {start()} function, we call the + \l {Concurrent Filter-Reduce} {filteredReduced} function from Qt Concurrent + and set the future on the QFutureWatcher member. To ensure that this + operation runs truly concurrently, we specify a separate QThreadPool as the + first parameter. This approach also avoids any possible blocking in the + global thread pool. We pass the QList of integers as the container, a + static filter and reduce function, and finally the + \l {QtConcurrent::} {ReduceOption} flag. + + \dots + \snippet primecounter/primecounter.cpp 2 + \dots + + Let's examine the filter and reduce functions. These functions are declared + static in this example since they do not depend on any member variable. + However, they could easily be specified as lambdas or member functions. + + The filter function marks elements for subsequent reduction with the reduce + function. This implementation is a simple prime filter. As this function + takes a const reference as an argument, it allows thread-safe operation on + the container it operates on. + + \dots + \snippet primecounter/primecounter.cpp 3 + \dots + + The reduce function takes a modifiable reference of the same type as the + container it operates on as its first parameter. The second parameter is the + previously filtered element from the filter function. In this example, we + count the number of primes. + + \dots + \snippet primecounter/primecounter.cpp 4 + \dots + +*/ diff --git a/examples/qtconcurrent/primecounter/main.cpp b/examples/qtconcurrent/primecounter/main.cpp new file mode 100644 index 0000000000..acfd581341 --- /dev/null +++ b/examples/qtconcurrent/primecounter/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtWidgets/qapplication.h> +#include "primecounter.h" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("QtProject"); + app.setApplicationName(QApplication::translate("main", "Prime Counter")); + + PrimeCounter dialog; + dialog.setWindowTitle(QApplication::translate("main", "Prime Counter")); + dialog.show(); + + return app.exec(); +} diff --git a/examples/qtconcurrent/primecounter/primecounter.cpp b/examples/qtconcurrent/primecounter/primecounter.cpp new file mode 100644 index 0000000000..01bde5d87f --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "primecounter.h" +#include "ui_primecounter.h" + +PrimeCounter::PrimeCounter(QWidget *parent) + : QDialog(parent), stepSize(100000), ui(setupUi()) +{ + // Control the concurrent operation with the QFutureWatcher + //! [1] + connect(ui->pushButton, &QPushButton::clicked, + this, [this] { start(); }); + connect(&watcher, &QFutureWatcher<Element>::finished, + this, [this] { finish(); }); + connect(&watcher, &QFutureWatcher<Element>::progressRangeChanged, + ui->progressBar, &QProgressBar::setRange); + connect(&watcher, &QFutureWatcher<Element>::progressValueChanged, + ui->progressBar, &QProgressBar::setValue); + //! [1] +} + +PrimeCounter::~PrimeCounter() +{ + watcher.cancel(); + delete ui; +} + +//! [3] +bool PrimeCounter::filterFunction(const Element &element) +{ + // Filter for primes + if (element <= 1) + return false; + for (Element i = 2; i*i <= element; ++i) { + if (element % i == 0) + return false; + } + return true; +} +//! [3] + +//! [4] +void PrimeCounter::reduceFunction(Element &out, const Element &value) +{ + // Count the amount of primes. + Q_UNUSED(value); + ++out; +} +//! [4] + +//! [2] +void PrimeCounter::start() +{ + if (ui->pushButton->isChecked()) { + ui->comboBox->setEnabled(false); + ui->pushButton->setText(tr("Cancel")); + ui->labelResult->setText(tr("Calculating ...")); + ui->labelFilter->setText(tr("Selected Reduce Option: %1").arg(ui->comboBox->currentText())); + fillElementList(ui->horizontalSlider->value() * stepSize); + + timer.start(); + watcher.setFuture( + QtConcurrent::filteredReduced( + &pool, + elementList, + filterFunction, + reduceFunction, + currentReduceOpt | QtConcurrent::SequentialReduce)); +//! [2] + } else { + watcher.cancel(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->labelResult->setText(tr("")); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText(tr("Operation Canceled")); + } +} + +void PrimeCounter::finish() +{ + // The finished signal from the QFutureWatcher is also emitted when cancelling. + if (watcher.isCanceled()) + return; + + auto elapsedTime = timer.elapsed(); + ui->progressBar->setValue(0); + ui->comboBox->setEnabled(true); + ui->pushButton->setChecked(false); + ui->pushButton->setText(tr("Start")); + ui->labelFilter->setText( + tr("Filter '%1' took %2 ms to calculate").arg(ui->comboBox->currentText()) + .arg(elapsedTime)); + ui->labelResult->setText( + tr("Found %1 primes in the range of elements").arg(watcher.result())); +} + +void PrimeCounter::fillElementList(unsigned int count) +{ + // Fill elementList with values from [1, count] when starting the calculations. + auto prevSize = elementList.size(); + if (prevSize == count) + return; // Nothing to do here. + + auto startVal = elementList.empty() ? 1 : elementList.back() + 1; + elementList.resize(count); + if (elementList.begin() + prevSize < elementList.end()) + std::iota(elementList.begin() + prevSize, elementList.end(), startVal); +} + +Ui::PrimeCounter* PrimeCounter::setupUi() +{ + Ui::PrimeCounter *setupUI = new Ui::PrimeCounter; + setupUI->setupUi(this); + setModal(true); + + // Set up the slider + connect(setupUI->horizontalSlider, &QSlider::valueChanged, + this, [setupUI, this] (const int &pos) { + setupUI->labelResult->setText(""); + setupUI->labelSize->setText(tr("Elements in list: %1").arg(pos * stepSize)); + }); + setupUI->horizontalSlider->setValue(30); + + // Set up the combo box + setupUI->comboBox->insertItem(0, tr("Unordered Reduce"), QtConcurrent::UnorderedReduce); + setupUI->comboBox->insertItem(1, tr("Ordered Reduce"), QtConcurrent::OrderedReduce); + + auto comboBoxChange = [this, setupUI](int pos) { + currentReduceOpt = setupUI->comboBox->itemData(pos).value<QtConcurrent::ReduceOptions>(); + setupUI->labelFilter->setText(tr("Selected Reduce Option: %1") + .arg(setupUI->comboBox->currentText())); + }; + comboBoxChange(setupUI->comboBox->currentIndex()); + connect(setupUI->comboBox, &QComboBox::currentIndexChanged, this, comboBoxChange); + + return setupUI; +} diff --git a/examples/qtconcurrent/primecounter/primecounter.h b/examples/qtconcurrent/primecounter/primecounter.h new file mode 100644 index 0000000000..5b6d442cc8 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.h @@ -0,0 +1,49 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef PRIMECOUNTER_H +#define PRIMECOUNTER_H + +#include <QtWidgets/qdialog.h> +#include <QtCore/qfuturewatcher.h> +#include <QtConcurrent/qtconcurrentfilter.h> +#include <QtConcurrent/qtconcurrentreducekernel.h> + +QT_BEGIN_NAMESPACE +class QLabel; +class QProgressBar; +namespace Ui { +class PrimeCounter; +} + +QT_END_NAMESPACE + +class PrimeCounter : public QDialog +{ + Q_OBJECT + using Element = unsigned long long; +public: + explicit PrimeCounter(QWidget* parent = nullptr); + ~PrimeCounter() override; + +private: + static bool filterFunction(const Element &element); + static void reduceFunction(Element &out, const Element &value); + void fillElementList(unsigned int count); + Ui::PrimeCounter* setupUi(); + +private slots: + void start(); + void finish(); + +private: + QList<Element> elementList; + QFutureWatcher<Element> watcher; + QtConcurrent::ReduceOptions currentReduceOpt; + QElapsedTimer timer; + QThreadPool pool; + unsigned int stepSize; + Ui::PrimeCounter *ui; +}; + +#endif //PRIMECOUNTER_H diff --git a/examples/qtconcurrent/primecounter/primecounter.pro b/examples/qtconcurrent/primecounter/primecounter.pro new file mode 100644 index 0000000000..bb5a740688 --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.pro @@ -0,0 +1,9 @@ +QT += concurrent widgets + +SOURCES += main.cpp primecounter.cpp +HEADERS += primecounter.h + +target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/primecounter +INSTALLS += target + +FORMS += primecounter.ui diff --git a/examples/qtconcurrent/primecounter/primecounter.ui b/examples/qtconcurrent/primecounter/primecounter.ui new file mode 100644 index 0000000000..625462e05e --- /dev/null +++ b/examples/qtconcurrent/primecounter/primecounter.ui @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PrimeCounter</class> + <widget class="QDialog" name="PrimeCounter"> + <property name="windowModality"> + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>454</width> + <height>320</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>20</number> + </property> + <property name="topMargin"> + <number>20</number> + </property> + <property name="rightMargin"> + <number>20</number> + </property> + <property name="bottomMargin"> + <number>20</number> + </property> + <item> + <widget class="QLabel" name="labelInfo"> + <property name="font"> + <font> + <pointsize>11</pointsize> + <bold>false</bold> + </font> + </property> + <property name="text"> + <string>Select a reducing option and measure the speed</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>30</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout2"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QComboBox" name="comboBox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout1"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Start</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelFilter"> + <property name="text"> + <string>Filter Label</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout3"> + <item> + <widget class="QSlider" name="horizontalSlider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelSize"> + <property name="text"> + <string>size</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + <property name="indent"> + <number>10</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelResult"> + <property name="text"> + <string>Result Label</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |