From 10cfd2fdc33d79dc8534a22748f3be7aaed841e4 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 27 Mar 2024 17:46:44 +0000 Subject: [PATCH 01/26] feat: controller screen rendering --- .github/workflows/build-checks.yml | 1 + .github/workflows/build.yml | 3 + CMakeLists.txt | 13 + res/controllers/Dummy Device Screen.hid.xml | 22 + res/controllers/DummyDeviceDefaultScreen.qml | 306 ++++++ src/controllers/controller.cpp | 20 +- src/controllers/controller.h | 26 +- src/controllers/controllermanager.cpp | 4 +- src/controllers/controllerscreenpreview.cpp | 60 ++ src/controllers/controllerscreenpreview.h | 30 + src/controllers/dlgprefcontroller.cpp | 80 ++ src/controllers/dlgprefcontroller.h | 12 + src/controllers/dlgprefcontrollerdlg.ui | 473 +++++---- src/controllers/legacycontrollermapping.h | 124 ++- .../legacycontrollermappingfilehandler.cpp | 139 ++- .../legacycontrollermappingfilehandler.h | 22 +- src/controllers/midi/midicontroller.cpp | 12 +- src/controllers/midi/midicontroller.h | 2 +- .../rendering/controllerrenderingengine.cpp | 400 ++++++++ .../rendering/controllerrenderingengine.h | 102 ++ .../scripting/controllerscriptenginebase.cpp | 142 ++- .../scripting/controllerscriptenginebase.h | 61 ++ .../legacy/controllerscriptenginelegacy.cpp | 608 +++++++++++- .../legacy/controllerscriptenginelegacy.h | 58 +- src/coreservices.cpp | 43 + src/coreservices.h | 3 + src/qml/qmlapplication.cpp | 18 - .../controller_mapping_file_handler_test.cpp | 936 ++++++++++++++++++ .../controller_mapping_validation_test.cpp | 79 +- src/test/controller_mapping_validation_test.h | 39 +- src/test/controllerrenderingengine_test.cpp | 49 + .../controllerscriptenginelegacy_test.cpp | 133 ++- src/test/helpers/log_test.cpp | 22 + src/test/helpers/log_test.h | 28 + src/util/cmdlineargs.cpp | 7 + src/util/cmdlineargs.h | 4 + tools/README | 20 + tools/dummy_hid_device.cpp | 253 +++++ 38 files changed, 4104 insertions(+), 250 deletions(-) create mode 100644 res/controllers/Dummy Device Screen.hid.xml create mode 100755 res/controllers/DummyDeviceDefaultScreen.qml create mode 100644 src/controllers/controllerscreenpreview.cpp create mode 100644 src/controllers/controllerscreenpreview.h create mode 100644 src/controllers/rendering/controllerrenderingengine.cpp create mode 100644 src/controllers/rendering/controllerrenderingengine.h create mode 100644 src/test/controller_mapping_file_handler_test.cpp create mode 100644 src/test/controllerrenderingengine_test.cpp create mode 100644 src/test/helpers/log_test.cpp create mode 100644 src/test/helpers/log_test.h create mode 100644 tools/dummy_hid_device.cpp diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index ac80912878c0..2582c697c2bc 100644 --- a/.github/workflows/build-checks.yml +++ b/.github/workflows/build-checks.yml @@ -94,6 +94,7 @@ jobs: -DCMAKE_BUILD_TYPE=Debug \ -DOPTIMIZE=off \ -DQT6=ON \ + -DQML=ON \ -DCOVERAGE=ON \ -DWARNINGS_FATAL=OFF \ -DDEBUG_ASSERTIONS_FATAL=OFF \ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd50f1d182a3..4b7a25cf4cd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,7 @@ jobs: -DMACOS_BUNDLE=ON -DMODPLUG=ON -DQT6=ON + -DQML=OFF -DWAVPACK=ON -DVCPKG_TARGET_TRIPLET=x64-osx-min1015-release -DVCPKG_DEFAULT_HOST_TRIPLET=x64-osx-min1015-release @@ -71,6 +72,7 @@ jobs: -DMACOS_BUNDLE=ON -DMODPLUG=ON -DQT6=ON + -DQML=OFF -DWAVPACK=ON -DVCPKG_TARGET_TRIPLET=arm64-osx-min1100-release -DVCPKG_DEFAULT_HOST_TRIPLET=x64-osx-min1015-release @@ -101,6 +103,7 @@ jobs: -DMEDIAFOUNDATION=ON -DMODPLUG=ON -DQT6=ON + -DQML=OFF -DWAVPACK=ON -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_DEFAULT_HOST_TRIPLET=x64-windows-release diff --git a/CMakeLists.txt b/CMakeLists.txt index 2530efeff706..4948b3214228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1497,6 +1497,12 @@ if (NOT QML) src/control/controlmodel.cpp src/control/controlsortfiltermodel.cpp ) +else() + target_sources(mixxx-lib PRIVATE + # The following source depends of QML being available but aren't part of the new QML UI + src/controllers/rendering/controllerrenderingengine.cpp + src/controllers/controllerscreenpreview.cpp + ) endif() if(QOPENGL) target_sources(mixxx-lib PRIVATE @@ -2167,8 +2173,15 @@ add_executable(mixxx-test src/test/wpushbutton_test.cpp src/test/wwidgetstack_test.cpp src/util/moc_included_test.cpp + src/test/helpers/log_test.cpp ) target_precompile_headers(mixxx-test REUSE_FROM mixxx-lib) +if (QML) + target_sources(mixxx-test PRIVATE + src/test/controller_mapping_file_handler_test.cpp + src/test/controllerrenderingengine_test.cpp + ) +endif() find_package(GTest CONFIG REQUIRED) set_target_properties(mixxx-test PROPERTIES AUTOMOC ON) target_link_libraries(mixxx-test PRIVATE mixxx-lib mixxx-gitinfostore GTest::gtest GTest::gmock) diff --git a/res/controllers/Dummy Device Screen.hid.xml b/res/controllers/Dummy Device Screen.hid.xml new file mode 100644 index 000000000000..90526cc1a33f --- /dev/null +++ b/res/controllers/Dummy Device Screen.hid.xml @@ -0,0 +1,22 @@ + + + + Dummy Device (Screens) + A. Colombier + Dummy device screens + + + + + + + + + + + + + + + + diff --git a/res/controllers/DummyDeviceDefaultScreen.qml b/res/controllers/DummyDeviceDefaultScreen.qml new file mode 100755 index 000000000000..82e44949cd8d --- /dev/null +++ b/res/controllers/DummyDeviceDefaultScreen.qml @@ -0,0 +1,306 @@ +import QtQuick 2.15 +import QtQuick.Window 2.3 +import QtQuick.Scene3D 2.14 + +import QtQuick.Controls 2.15 +import QtQuick.Shapes 1.11 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.15 + +import Qt5Compat.GraphicalEffects + +import Mixxx 1.0 as Mixxx +import Mixxx.Controls 1.0 as MixxxControls + +import "." as Skin + +Item { + id: root + + required property string screenId + property color fontColor: Qt.rgba(242/255,242/255,242/255, 1) + property color smallBoxBorder: Qt.rgba(44/255,44/255,44/255, 1) + + property string group: "[Channel1]" + property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) + + function init(controlerName, isDebug) { + console.log(`Screen ${root.screenId} has started`) + switch (root.screenId) { + case "jog": + loader.sourceComponent = jog + break; + default: + loader.sourceComponent = main + } + } + + function shutdown() { + console.log(`Screen ${root.screenId} is stopping`) + loader.sourceComponent = splash + } + + // function transformFrame(input: ArrayBuffer, timestamp: date) { + function transformFrame(input, timestamp) { + return new ArrayBuffer(0); + } + + Mixxx.ControlProxy { + group: root.group + key: "track_loaded" + + onValueChanged: (value) => { + deckPlayer = Mixxx.PlayerManager.getPlayer(root.group) + } + } + + Timer { + id: channelchange + + interval: 2000 + repeat: true + running: true + + onTriggered: { + root.group = root.group === "[Channel1]" ? "[Channel2]" : "[Channel1]" + deckPlayer = Mixxx.PlayerManager.getPlayer(root.group) + } + } + + Component { + id: splash + Rectangle { + color: "black" + anchors.fill: parent + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: "../images/templates/logo_mixxx.png" + } + } + } + + Component { + id: jog + + Rectangle { + anchors.fill: parent + color: "black" + + Image { + id: artwork + anchors.fill: parent + visible: deckPlayer.trackLocationUrl.toString().length !== 0 + + source: deckPlayer.coverArtUrl ?? "../images/templates/logo_mixxx.png" + height: 100 + width: 100 + fillMode: Image.PreserveAspectFit + } + + Text { + visible: deckPlayer.trackLocationUrl.toString().length === 0 + + text: qsTr("No Track Loaded") + font.pixelSize: 12 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: "white" + } + } + } + + Component { + id: main + + Rectangle { + id: debugValue + anchors.fill: parent + color: 'black' + + antialiasing: true + + ColumnLayout { + id: column + anchors.fill: parent + anchors.leftMargin: 0 + anchors.rightMargin: 0 + anchors.topMargin: 0 + anchors.bottomMargin: 0 + spacing: 6 + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + Repeater { + id: debugColor + + model: [ + "black", + "white", + "red", + "green", + "blue", + Qt.rgba(0, 1, 1), + ] + + Rectangle { + required property var modelData + + color: modelData + Layout.fillWidth: true + height: 80 + } + } + } + + RowLayout { + anchors.leftMargin: 6 + anchors.rightMargin: 6 + anchors.topMargin: 6 + anchors.bottomMargin: 6 + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 6 + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + Text { + text: qsTr("Group") + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + Text { + text: `${root.group}` + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + } + + RowLayout { + anchors.leftMargin: 6 + anchors.rightMargin: 6 + anchors.topMargin: 6 + anchors.bottomMargin: 6 + + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 6 + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + Text { + text: qsTr("Widget") + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + + Skin.HotcueButton { + anchors.fill: parent + + hotcueNumber: 1 + group: root.group + } + } + } + + Repeater { + model: [{ + controllerKey: "beatloop_size", + title: "Beatloop Size" + }, { + controllerKey: "track_samples", + title: "Track sample" + }, { + controllerKey: "track_samplerate", + title: "Track sample rate" + }, { + controllerKey: "playposition", + title: "Play position" + }, { + controllerKey: "rate_ratio", + title: "Rate ratio" + }, { + controllerKey: "waveform_zoom", + title: "Waveform zoom" + } + ] + + RowLayout { + id: row + anchors.leftMargin: 6 + anchors.rightMargin: 6 + anchors.topMargin: 6 + anchors.bottomMargin: 6 + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 6 + required property var modelData + + Mixxx.ControlProxy { + id: mixxxValue + group: root.group + key: modelData.controllerKey + } + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + Text { + text: qsTr(modelData.title) + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + + Rectangle { + color: 'transparent' + Layout.fillWidth: true + Layout.fillHeight: true + Text { + text: `${mixxxValue.value}` + font.pixelSize: 24 + font.family: "Noto Sans" + font.letterSpacing: -1 + color: fontColor + } + } + } + } + } + } + } + Loader { + id: loader + anchors.fill: parent + sourceComponent: splash + } +} diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index e37933858d18..df160e5124cc 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -5,6 +5,7 @@ #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "moc_controller.cpp" +#include "util/cmdlineargs.h" #include "util/screensaver.h" namespace { @@ -43,7 +44,8 @@ void Controller::startEngine() qCWarning(m_logBase) << "Controller: Engine already exists! Restarting:"; stopEngine(); } - m_pScriptEngineLegacy = new ControllerScriptEngineLegacy(this, m_logBase); + m_pScriptEngineLegacy = std::make_shared(this, m_logBase); + emit engineStarted(m_pScriptEngineLegacy); } void Controller::stopEngine() { @@ -52,11 +54,11 @@ void Controller::stopEngine() { qCWarning(m_logBase) << "Controller::stopEngine(): No engine exists!"; return; } - delete m_pScriptEngineLegacy; - m_pScriptEngineLegacy = nullptr; + m_pScriptEngineLegacy.reset(); + emit engineStopped(); } -bool Controller::applyMapping() { +bool Controller::applyMapping(const QString& resourcePath) { qCInfo(m_logBase) << "Applying controller mapping..."; const std::shared_ptr pMapping = cloneMapping(); @@ -78,6 +80,13 @@ bool Controller::applyMapping() { m_pScriptEngineLegacy->setScriptFiles(scriptFiles); m_pScriptEngineLegacy->setSettings(pMapping->getSettings()); +#ifdef MIXXX_USE_QML + m_pScriptEngineLegacy->setLibraryDirectories(pMapping->getLibraryDirectories()); + m_pScriptEngineLegacy->setInfoScrens(pMapping->getInfoScreens()); + m_pScriptEngineLegacy->setResourcePath(resourcePath); +#else + Q_UNUSED(resourcePath); +#endif return m_pScriptEngineLegacy->initialize(); } @@ -124,7 +133,8 @@ void Controller::receive(const QByteArray& data, mixxx::Duration timestamp) { triggerActivity(); int length = data.size(); - if (m_logInput().isDebugEnabled()) { + if (CmdlineArgs::Instance() + .getControllerDebug()) { // Formatted packet display QString message = QString("t:%2, %3 bytes:\n") .arg(timestamp.formatMillisWithUnit(), diff --git a/src/controllers/controller.h b/src/controllers/controller.h index cd6e0fa59908..25c46d8cab02 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -53,12 +53,22 @@ class Controller : public QObject { return m_bLearning; } + inline std::shared_ptr getScriptEngine() const { + return m_pScriptEngineLegacy; + } + virtual bool matchMapping(const MappingInfo& mapping) = 0; signals: /// Emitted when the controller is opened or closed. void openChanged(bool bOpen); + /// Emitted when the controller has started a new engine. + void engineStarted(std::shared_ptr engine); + + /// Emitted when the controller has stopped its engine. + void engineStopped(); + // Making these slots protected/private ensures that other parts of Mixxx can // only signal them which allows us to use no locks. protected slots: @@ -70,7 +80,7 @@ class Controller : public QObject { // this if they have an alternate way of handling such data.) virtual void receive(const QByteArray& data, mixxx::Duration timestamp); - virtual bool applyMapping(); + virtual bool applyMapping(const QString& resourcePath); // Puts the controller in and out of learning mode. void startLearning(); @@ -98,10 +108,6 @@ class Controller : public QObject { // were required to specify it. virtual void send(const QList& data, unsigned int length = 0); - // This must be reimplemented by sub-classes desiring to send raw bytes to a - // controller. - virtual void sendBytes(const QByteArray& data) = 0; - // To be called in sub-class' open() functions after opening the device but // before starting any input polling/processing. virtual void startEngine(); @@ -113,9 +119,6 @@ class Controller : public QObject { // To be called when receiving events void triggerActivity(); - inline ControllerScriptEngineLegacy* getScriptEngine() const { - return m_pScriptEngineLegacy; - } inline void setDeviceCategory(const QString& deviceCategory) { m_sDeviceCategory = deviceCategory; } @@ -135,6 +138,11 @@ class Controller : public QObject { const RuntimeLoggingCategory m_logInput; const RuntimeLoggingCategory m_logOutput; + public slots: + // This must be reimplemented by sub-classes desiring to send raw bytes to a + // controller. + virtual void sendBytes(const QByteArray& data) = 0; + private: // but used by ControllerManager virtual int open() = 0; @@ -150,7 +158,7 @@ class Controller : public QObject { } private: - ControllerScriptEngineLegacy* m_pScriptEngineLegacy; + std::shared_ptr m_pScriptEngineLegacy; // Verbose and unique description of device type, defaults to empty QString m_sDeviceCategory; diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 7dbac102ef32..073ee897cbb5 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -300,7 +300,7 @@ void ControllerManager::slotSetUpDevices() { qWarning() << "There was a problem opening" << name; continue; } - pController->applyMapping(); + pController->applyMapping(m_pConfig->getResourcePath()); } pollIfAnyControllersOpen(); @@ -392,7 +392,7 @@ void ControllerManager::openController(Controller* pController) { // If successfully opened the device, apply the mapping and save the // preference setting. if (result == 0) { - pController->applyMapping(); + pController->applyMapping(m_pConfig->getResourcePath()); // Update configuration to reflect controller is enabled. m_pConfig->setValue( diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp new file mode 100644 index 000000000000..b9b1cd475bd5 --- /dev/null +++ b/src/controllers/controllerscreenpreview.cpp @@ -0,0 +1,60 @@ +#include "controllers/controllerscreenpreview.h" + +#include +#include + +#include "moc_controllerscreenpreview.cpp" +#include "util/time.h" + +ControllerScreenPreview::ControllerScreenPreview( + QWidget* parent, const LegacyControllerMapping::ScreenInfo& screen) + : QWidget(parent), + m_screenInfo(screen), + m_pFrame(make_parented(this)), + m_pStat(make_parented("- FPS", this)), + m_frameDurationHistoryIdx(0), + m_lastFrameTimespamp(mixxx::Time::elapsed()) { + size_t frameDurationHistoryLenght = sizeof(m_frameDurationHistory) / sizeof(uint); + memset(m_frameDurationHistory, 0, frameDurationHistoryLenght); + m_pFrame->setFixedSize(screen.size); + setMaximumWidth(screen.size.width()); + m_pStat->setAlignment(Qt::AlignRight); + auto pLayout = make_parented(this); + auto pBottomLayout = new QHBoxLayout(); + pLayout->addWidget(m_pFrame); + pBottomLayout->addWidget(make_parented( + QString("\"%0\"") + .arg(m_screenInfo.identifier.isEmpty() + ? QStringLiteral("Unnamed") + : m_screenInfo.identifier), + this)); + pBottomLayout->addWidget(m_pStat); + pLayout->addItem(pBottomLayout); + pLayout->addItem(new QSpacerItem( + 1, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); +} +void ControllerScreenPreview::updateFrame( + const LegacyControllerMapping::ScreenInfo& screen, const QImage& frame) { + if (m_screenInfo.identifier != screen.identifier) { + return; + } + size_t frameDurationHistoryLenght = sizeof(m_frameDurationHistory) / sizeof(uint); + auto currentTimestamp = mixxx::Time::elapsed(); + m_frameDurationHistory[m_frameDurationHistoryIdx++] = + (currentTimestamp - m_lastFrameTimespamp).toIntegerMillis(); + m_frameDurationHistoryIdx %= frameDurationHistoryLenght; + + double durationSinceLastFrame = 0.0; + for (uint8_t i = 0; i < frameDurationHistoryLenght; i++) { + durationSinceLastFrame += (double)m_frameDurationHistory[i]; + } + durationSinceLastFrame /= (double)frameDurationHistoryLenght; + + if (durationSinceLastFrame > 0.0) { + m_pStat->setText(QString("FPS: %0/%1") + .arg((int)(1000.0 / durationSinceLastFrame)) + .arg(m_screenInfo.target_fps)); + } + m_pFrame->setPixmap(QPixmap::fromImage(frame)); + m_lastFrameTimespamp = currentTimestamp; +} diff --git a/src/controllers/controllerscreenpreview.h b/src/controllers/controllerscreenpreview.h new file mode 100644 index 000000000000..eaa16840240e --- /dev/null +++ b/src/controllers/controllerscreenpreview.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include "util/duration.h" +#include "util/parented_ptr.h" + +#define CONTROLLER_SCREEN_PREVIEW_FRAME_HISTORY_SIZE 5 + +/// Widget to preview controller screen +class ControllerScreenPreview : public QWidget { + Q_OBJECT + public: + ControllerScreenPreview(QWidget* parent, + const LegacyControllerMapping::ScreenInfo& screen); + public slots: + void updateFrame(const LegacyControllerMapping::ScreenInfo& screen, const QImage& frame); + + private: + LegacyControllerMapping::ScreenInfo m_screenInfo; + + parented_ptr m_pFrame; + parented_ptr m_pStat; + uint8_t m_frameDurationHistoryIdx; + uint m_frameDurationHistory[CONTROLLER_SCREEN_PREVIEW_FRAME_HISTORY_SIZE]; + + mixxx::Duration m_lastFrameTimespamp; +}; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 49bf0788393c..387f7bb50902 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -13,13 +13,18 @@ #include "controllers/controllermappinginfoenumerator.h" #include "controllers/controlleroutputmappingtablemodel.h" #include "controllers/controlpickermenu.h" +#ifdef MIXXX_USE_QML +#include "controllers/controllerscreenpreview.h" +#endif #include "controllers/defs_controllers.h" #include "controllers/dlgcontrollerlearning.h" #include "controllers/midi/legacymidicontrollermapping.h" #include "controllers/midi/midicontroller.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "defs_urls.h" #include "moc_dlgprefcontroller.cpp" #include "preferences/usersettings.h" +#include "util/cmdlineargs.h" #include "util/desktophelper.h" #include "util/parented_ptr.h" #include "util/string.h" @@ -109,6 +114,20 @@ DlgPrefController::DlgPrefController( &ControllerManager::mappingApplied, this, &DlgPrefController::enableWizardAndIOTabs); +#ifdef MIXXX_USE_QML + if (CmdlineArgs::Instance() + .getControllerPreviewScreens()) { + connect(m_pController, + &Controller::engineStarted, + this, + QOverload>::of( + &DlgPrefController::slotShowPreviewScreens)); + connect(m_pController, + &Controller::engineStopped, + this, + QOverload<>::of(&DlgPrefController::slotShowPreviewScreens)); + } +#endif // Open script file links connect(m_ui.labelLoadedMappingScriptFileLinks, @@ -406,6 +425,24 @@ QString DlgPrefController::mappingFileLinks( linkList << scriptFileLink; } + +#ifdef MIXXX_USE_QML + for (const auto& qmlLibrary : pMapping->getLibraryDirectories()) { + QString scriptFileLink = coloredLinkString( + m_pLinkColor, + qmlLibrary.dirinfo.fileName(), + qmlLibrary.dirinfo.absoluteFilePath()); + if (!qmlLibrary.dirinfo.exists()) { + scriptFileLink += + QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); + } else if (qmlLibrary.dirinfo.absoluteFilePath().startsWith( + systemMappingPath)) { + scriptFileLink += builtinFileSuffix; + } + + linkList << scriptFileLink; + } +#endif return linkList.join("
"); } @@ -629,6 +666,7 @@ void DlgPrefController::slotMappingSelected(int chosenIndex) { } m_ui.groupBoxSettings->setVisible(false); + m_ui.groupBoxScreens->setVisible(false); } else { // User picked a mapping m_ui.chkEnabledDevice->setEnabled(true); @@ -832,6 +870,35 @@ void DlgPrefController::initTableView(QTableView* pTable) { pTable->setAlternatingRowColors(true); } +#ifdef MIXXX_USE_QML +void DlgPrefController::slotShowPreviewScreens( + std::shared_ptr scriptEngine) { + qDeleteAll(m_ui.groupBoxScreens->findChildren("", Qt::FindDirectChildrenOnly)); + + if (!m_pMapping) { + return; + } + + m_ui.groupBoxScreens->setVisible(scriptEngine != nullptr); + if (!scriptEngine) { + return; + } + + auto screens = m_pMapping->getInfoScreens(); + + for (const LegacyControllerMapping::ScreenInfo& screen : qAsConst(screens)) { + ControllerScreenPreview* pPreviewScreen = + new ControllerScreenPreview(m_ui.groupBoxScreens, screen); + m_ui.groupBoxScreens->layout()->addWidget(pPreviewScreen); + + connect(scriptEngine.get(), + &ControllerScriptEngineLegacy::previewRenderedScreen, + pPreviewScreen, + &ControllerScreenPreview::updateFrame); + } +} +#endif + void DlgPrefController::slotShowMapping(std::shared_ptr pMapping) { m_ui.labelLoadedMapping->setText(mappingName(pMapping)); m_ui.labelLoadedMappingDescription->setText(mappingDescription(pMapping)); @@ -875,6 +942,19 @@ void DlgPrefController::slotShowMapping(std::shared_ptr m_pMapping = pMapping; } +#ifdef MIXXX_USE_QML + if (pMapping && + CmdlineArgs::Instance() + .getControllerPreviewScreens() && + pMapping && + !pMapping->getInfoScreens().isEmpty()) { + slotShowPreviewScreens(m_pController->getScriptEngine()); + } else +#endif + { + m_ui.groupBoxScreens->setVisible(false); + } + // Inputs tab ControllerInputMappingTableModel* pInputModel = new ControllerInputMappingTableModel(this, diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 9a3fbd7e6ed4..9f93b960729f 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -18,6 +18,9 @@ class ControllerOutputMappingTableModel; class ControlPickerMenu; class DlgControllerLearning; class MappingInfoEnumerator; +#ifdef MIXXX_USE_QML +class ControllerScriptEngineLegacy; +#endif /// Configuration dialog for a single DJ controller class DlgPrefController : public DlgPreferencePage { @@ -61,6 +64,15 @@ class DlgPrefController : public DlgPreferencePage { void slotStopLearning(); void enableWizardAndIOTabs(bool enable); +#ifdef MIXXX_USE_QML + // Onboard screen controller + void slotShowPreviewScreens(std::shared_ptr scriptEngine); + // Wrapper used on shutdown + void slotShowPreviewScreens() { + slotShowPreviewScreens(nullptr); + } +#endif + // Input mappings void addInputMapping(); void showLearningWizard(); diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index 9b296011d893..e7e80bdc73c0 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -6,8 +6,8 @@ 0 0 - 507 - 437 + 902 + 591 @@ -26,116 +26,16 @@ 0 + + + 0 + 0 + + Controller Setup - - - - - - 0 - 0 - - - - - - - - - - - 0 - 0 - - - - - 50 - 50 - - - - - 50 - 50 - - - - (icon) - - - - - - - - - - (Warning message goes here) - - - true - - - true - - - Qt::TextBrowserInteraction - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Load Mapping: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - comboBoxMapping - - - - - - - true - - - - 0 - 0 - - - - - - - (device category goes here) - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + @@ -152,73 +52,8 @@ - - - - Click to start the Controller Learning wizard. - - - - - - Learning Wizard (MIDI Only) - - - false - - - false - - - - - - - true - - - - 0 - 0 - - - - - 14 - 75 - true - - - - Controller Name - - - - - - - Enabled - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 40 - - - - - + Mapping Info @@ -431,6 +266,31 @@ + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Load Mapping: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + comboBoxMapping + + + @@ -448,6 +308,269 @@ + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + true + + + + 0 + 0 + + + + + + + (device category goes here) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Load Mapping: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + comboBoxMapping + + + + + + + true + + + + 0 + 0 + + + + + + + (device category goes here) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Click to start the Controller Learning wizard. + + + + + + Learning Wizard (MIDI Only) + + + false + + + false + + + + + + + Enabled + + + + + + + Enabled + + + + + + + true + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Controller Name + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + (icon) + + + + + + + + + + (Warning message goes here) + + + true + + + true + + + Qt::TextBrowserInteraction + + + + + + + + + + Click to start the Controller Learning wizard. + + + + + + Learning Wizard (MIDI Only) + + + false + + + false + + + + + + + true + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Controller Name + + + + + + + Screens preview + + + + diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 45fe89df2604..1ea5e844379c 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -4,11 +4,18 @@ #include #include +#include +#include +#include +#include +#include +#include #include "controllers/legacycontrollersettings.h" #include "controllers/legacycontrollersettingslayout.h" #include "defs_urls.h" #include "preferences/usersettings.h" +#include "util/assert.h" /// This class represents a controller mapping, containing the data elements that /// make it up. @@ -42,13 +49,21 @@ class LegacyControllerMapping { virtual std::shared_ptr clone() const = 0; struct ScriptFileInfo { + enum Type { + JAVASCRIPT, +#ifdef MIXXX_USE_QML + QML, +#endif + }; + ScriptFileInfo() : builtin(false) { } QString name; - QString functionPrefix; + QString identifier; QFileInfo file; + Type type; bool builtin; }; @@ -65,19 +80,65 @@ class LegacyControllerMapping { }; Q_DECLARE_FLAGS(DeviceDirections, DeviceDirection) +#ifdef MIXXX_USE_QML + struct QMLModuleInfo { + QMLModuleInfo(const QFileInfo& aDirinfo, + bool isBuiltin) + : dirinfo(aDirinfo), + builtin(isBuiltin) { + } + + QFileInfo dirinfo; + bool builtin; + }; + + struct ScreenInfo { + ScreenInfo(const QString& aIdentifier, + const QSize& aSize, + uint aTargetFps, + uint aSplashOff, + QImage::Format aPixelFormat, + std::endian anEndian, + bool isReversedColor, + bool isRawData) + : identifier(aIdentifier), + size(aSize), + target_fps(aTargetFps), + splash_off(aSplashOff), + pixelFormat(aPixelFormat), + endian(anEndian), + reversedColor(isReversedColor), + rawData(isRawData) { + } + + QString identifier; + QSize size; + uint target_fps; + uint splash_off; + QImage::Format pixelFormat; + std::endian endian; + bool reversedColor; + bool rawData; + }; +#endif + /// Adds a script file to the list of controller scripts for this mapping. /// @param filename Name of the script file to add - /// @param functionprefix The script's function prefix (or empty string) + /// @param identifier The script's function prefix with Javascript OR the + /// screen identifier this QML should be run for (or empty string) /// @param file A FileInfo object pointing to the script file + /// @param type A ScriptFileInfo::Type the specify script file type /// @param builtin If this is true, the script won't be written to the XML - void addScriptFile(const QString& name, - const QString& functionprefix, + virtual void addScriptFile(const QString& name, + const QString& identifier, const QFileInfo& file, + ScriptFileInfo::Type type = ScriptFileInfo::Type::JAVASCRIPT, bool builtin = false) { ScriptFileInfo info; info.name = name; - info.functionPrefix = functionprefix; + info.identifier = identifier; info.file = file; + info.type = type; info.builtin = builtin; m_scripts.append(info); setDirty(true); @@ -143,6 +204,55 @@ class LegacyControllerMapping { return m_deviceDirection; } +#ifdef MIXXX_USE_QML + /// Adds a custom QML module file to the list of controller modules for this mapping. + /// @param dirinfo A FileInfo of the directory or QML module + /// @param builtin If this is true, the script won't be written to the XML + virtual void addLibraryDirectory(const QFileInfo& dirinfo, + bool builtin = false) { + m_modules.append(QMLModuleInfo( + dirinfo, + builtin)); + setDirty(true); + } + + const QList& getLibraryDirectories() const { + return m_modules; + } + + /// @brief Adds a screen info where QML will be rendered. + /// @param identifier The screen identifier + /// @param size the size of the screen + /// @param targetFps the maximum FPS to render + /// @param splashoff the rendering grace time given when the screen is requested to shutdown + /// @param pixelFormat the pixel encoding format + /// @param endian the pixel endian format + /// @param reversedColor whether or not the RGB is swapped BGR + /// @param rawData whether or not the screen is allowed to reserve bare data, not transformed + virtual void addScreenInfo(const QString& identifier, + const QSize& size, + uint targetFps = 30, + uint splashoff = 50, + QImage::Format pixelFormat = QImage::Format_RGB32, + std::endian endian = std::endian::little, + bool reversedColor = false, + bool rawData = false) { + m_screens.append(ScreenInfo(identifier, + size, + targetFps, + splashoff, + pixelFormat, + endian, + reversedColor, + rawData)); + setDirty(true); + } + + const QList& getInfoScreens() const { + return m_screens; + } +#endif + void setDirty(bool bDirty) { m_bDirty = bDirty; } @@ -290,4 +400,8 @@ class LegacyControllerMapping { std::unique_ptr m_settingsLayout; QList m_scripts; DeviceDirections m_deviceDirection; +#ifdef MIXXX_USE_QML + QList m_modules; + QList m_screens; +#endif }; diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index b8c6ab94321b..5fc958638896 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -1,5 +1,7 @@ #include "controllers/legacycontrollermappingfilehandler.h" +#include + #include "controllers/defs_controllers.h" #include "controllers/midi/legacymidicontrollermappingfilehandler.h" #include "util/xml.h" @@ -8,8 +10,45 @@ #include "controllers/hid/legacyhidcontrollermappingfilehandler.h" #endif +#ifdef MIXXX_USE_QML +QMap LegacyControllerMappingFileHandler::kSupportedPixelFormat = { + {"RBG", QImage::Format_RGB888}, + {"RBGA", QImage::Format_RGBA8888}, + {"RGB565", QImage::Format_RGB16}, +}; + +QMap LegacyControllerMappingFileHandler::kEndianFormat = { + {"big", std::endian::big}, + {"little", std::endian::little}, +}; +#endif namespace { +#ifdef MIXXX_USE_QML + +/// Find a module directory (QML) in the mapping or system path. +/// +/// @param mapping The controller mapping the module directory belongs to. +/// @param dirname The module directory name. +/// @param systemMappingsPath The system mappings path to use as fallback. +/// @return Returns a QFileInfo object. If the script was not found in either +/// of the search directories, the QFileInfo object might point to a +/// non-existing file. +QFileInfo findLibraryPath( + std::shared_ptr mapping, + const QString& dirname, + const QDir& systemMappingsPath) { + // Always try to load module directory from the mapping's directory first + QFileInfo dir = QFileInfo(mapping->dirPath().absoluteFilePath(dirname)); + + // If the module directory does not exist, try to find it in the fallback dir + if (!dir.isDir()) { + dir = QFileInfo(systemMappingsPath.absoluteFilePath(dirname)); + } + return dir; +} +#endif + /// Find script file in the mapping or system path. /// /// @param mapping The controller mapping the script belongs to. @@ -230,17 +269,109 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( mapping->addScriptFile(REQUIRED_SCRIPT_FILE, "", findScriptFile(mapping, REQUIRED_SCRIPT_FILE, systemMappingsPath), + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true); // Look for additional ones while (!scriptFile.isNull()) { - QString functionPrefix = scriptFile.attribute("functionprefix", ""); QString filename = scriptFile.attribute("filename", ""); QFileInfo file = findScriptFile(mapping, filename, systemMappingsPath); - - mapping->addScriptFile(filename, functionPrefix, file); + if (file.suffix() == "qml") { +#ifdef MIXXX_USE_QML + QString identifier = scriptFile.attribute("identifier", ""); + mapping->addScriptFile(filename, + identifier, + file, + LegacyControllerMapping::ScriptFileInfo::Type::QML); +#else + qWarning() << "Unsupported render scene. Mixxx isn't built with QML support"; + return; +#endif + } else { + QString functionPrefix = scriptFile.attribute("functionprefix", ""); + mapping->addScriptFile(filename, + functionPrefix, + file, + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT); + } scriptFile = scriptFile.nextSiblingElement("file"); } + +#ifdef MIXXX_USE_QML + // Build a list of QML files to load + QDomElement screen = controller.firstChildElement("screens") + .firstChildElement("screen"); + + // Look for additional ones + while (!screen.isNull()) { + QString identifier = screen.attribute("identifier", ""); + uint targetFps = screen.attribute("targetFps", "30").toUInt(); + QString pixelFormatName = screen.attribute("pixelType", "RBG"); + QString endianName = screen.attribute("endian", "little"); + QString reversedColor = screen.attribute("reversed", "false").toLower(); + QString rawData = screen.attribute("raw", "false").toLower(); + uint splashOff = screen.attribute("splashoff", "0").toUInt(); + + if (!targetFps || targetFps > s_maxTargetFps) { + qWarning() << "Invalid target FPS. Target FPS must be between 1 and" << s_maxTargetFps; + return; + } + + if (splashOff > s_maxSplashOffDuration) { + qWarning() << QString( + "Invalid splashoff duration. Splashoff duration must be " + "between 0 and %1. Clamping to %2") + .arg(s_maxSplashOffDuration) + .arg(s_maxSplashOffDuration); + splashOff = s_maxSplashOffDuration; + } + + if (!kSupportedPixelFormat.contains(pixelFormatName)) { + qWarning() << "Unsupported pixel format" << pixelFormatName; + return; + } + + if (!kEndianFormat.contains(endianName)) { + qWarning() << "Unknown endiant format" << endianName; + return; + } + + QImage::Format pixelFormat = kSupportedPixelFormat.value(pixelFormatName); + std::endian endian = kEndianFormat.value(endianName); + + uint width = screen.attribute("width", "0").toUInt(); + uint height = screen.attribute("height", "0").toUInt(); + + if (!width || !height) { + qWarning() << "Invalid screen size. Screen size must have a width " + "and height above 1 pixel"; + return; + } + + qDebug() << "Adding screen " << identifier; + mapping->addScreenInfo(identifier, + QSize(width, height), + targetFps, + splashOff, + pixelFormat, + endian, + reversedColor == "yes" || reversedColor == "true" || reversedColor == "1", + rawData == "yes" || rawData == "true" || rawData == "1"); + screen = screen.nextSiblingElement("screen"); + } + // Build a list of QML files to load + QDomElement qmlLibrary = controller.firstChildElement("qmllibraries") + .firstChildElement("library"); + + // Look for additional ones + while (!qmlLibrary.isNull()) { + QString libFilename = qmlLibrary.attribute("path", ""); + QFileInfo path = findLibraryPath(mapping, libFilename, systemMappingsPath); + qDebug() << "Adding QML directory " << libFilename; + mapping->addLibraryDirectory(path); + qmlLibrary = qmlLibrary.nextSiblingElement("library"); + } +#endif } bool LegacyControllerMappingFileHandler::writeDocument( @@ -324,7 +455,7 @@ QDomDocument LegacyControllerMappingFileHandler::buildRootWithScripts( continue; } qDebug() << "writing script block for" << filename; - QString functionPrefix = script.functionPrefix; + QString functionPrefix = script.identifier; QDomElement scriptFile = doc.createElement("file"); scriptFile.setAttribute("filename", filename); diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index 73e5eea584c3..a3a5c8ffb77c 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -3,6 +3,11 @@ #include #include #include +#ifdef MIXXX_USE_QML +#include +#include +#include +#endif class QFileInfo; class QDir; @@ -48,10 +53,10 @@ class LegacyControllerMappingFileHandler { void parseMappingSettings(const QDomElement& root, LegacyControllerMapping* mapping) const; - /// Adds script files from XML to the LegacyControllerMapping. + /// Adds script files and QML scenes from XML to the LegacyControllerMapping. /// /// This function parses the supplied QDomElement structure, finds the - /// matching script files inside the search paths and adds them to + /// matching script files and QML scenes inside the search paths and adds them to /// LegacyControllerMapping. /// /// @param root The root node of the XML document for the mapping. @@ -67,6 +72,13 @@ class LegacyControllerMappingFileHandler { bool writeDocument(const QDomDocument& root, const QString& fileName) const; +#ifdef MIXXX_USE_QML + // Maximum target frame per request for a a screen controller + static constexpr int s_maxTargetFps = 240; + // Maximum time allowed for a screen to run a splash off animation + static constexpr int s_maxSplashOffDuration = 3000; +#endif + private: /// @brief Recursively parse setting definition and layout information /// within a setting node @@ -84,5 +96,11 @@ class LegacyControllerMappingFileHandler { const QString& filePath, const QDir& systemMappingPath) = 0; +#ifdef MIXXX_USE_QML + static QMap kSupportedPixelFormat; + static QMap kEndianFormat; + + friend class ControllerRenderingEngineTest; +#endif friend class LegacyControllerMappingSettingsTest_parseSettingBlock_Test; }; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 98f635d7d3f7..6f4d9a85ff19 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -56,9 +56,9 @@ bool MidiController::matchMapping(const MappingInfo& mapping) { return false; } -bool MidiController::applyMapping() { +bool MidiController::applyMapping(const QString& resourcePath) { // Handles the engine - bool result = Controller::applyMapping(); + bool result = Controller::applyMapping(resourcePath); // Only execute this code if this is an output device if (isOutputDevice()) { @@ -271,8 +271,8 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, MidiOpCode opCode = MidiUtils::opCodeFromStatus(status); if (mapping.options.testFlag(MidiOption::Script)) { - ControllerScriptEngineLegacy* pEngine = getScriptEngine(); - if (pEngine == nullptr) { + auto pEngine = getScriptEngine(); + if (!pEngine) { return; } @@ -531,8 +531,8 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, mixxx::Duration timestamp) { // Custom script handler if (mapping.options.testFlag(MidiOption::Script)) { - ControllerScriptEngineLegacy* pEngine = getScriptEngine(); - if (pEngine == nullptr) { + auto pEngine = getScriptEngine(); + if (!pEngine) { return; } pEngine->handleIncomingData(data); diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 9af7887515af..75b061f45a65 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -62,7 +62,7 @@ class MidiController : public Controller { int close() override; private slots: - bool applyMapping() override; + bool applyMapping(const QString& resourcePath) override; void learnTemporaryInputMappings(const MidiInputMappings& mappings); void clearTemporaryInputMappings(); diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp new file mode 100644 index 000000000000..089ac342f602 --- /dev/null +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -0,0 +1,400 @@ +#include "controllers/rendering/controllerrenderingengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controllers/controller.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" +#include "moc_controllerrenderingengine.cpp" +#include "qml/qmlwaveformoverview.h" +#include "util/cmdlineargs.h" +#include "util/time.h" + +QMutex ControllerRenderingEngine::s_glMutex = QMutex(); + +ControllerRenderingEngine::ControllerRenderingEngine( + const LegacyControllerMapping::ScreenInfo& info, + ControllerScriptEngineBase* parent) + : QObject(), + m_screenInfo(info), + m_GLDataFormat(GL_RGBA), + m_GLDataType(GL_UNSIGNED_BYTE), + m_isValid(true), + m_pControllerEngine(parent) { + switch (m_screenInfo.pixelFormat) { + case QImage::Format_RGB16: + m_GLDataFormat = GL_RGB; + m_GLDataType = m_screenInfo.reversedColor + ? GL_UNSIGNED_SHORT_5_6_5_REV + : GL_UNSIGNED_SHORT_5_6_5; + break; + case QImage::Format_RGB888: + m_GLDataFormat = m_screenInfo.reversedColor ? GL_BGR : GL_RGB; + m_GLDataType = GL_UNSIGNED_BYTE; + break; + case QImage::Format_RGBA8888: + m_GLDataFormat = m_screenInfo.reversedColor ? GL_BGRA : GL_RGBA; + m_GLDataType = GL_UNSIGNED_BYTE; + break; + default: + m_isValid = false; + DEBUG_ASSERT(!"Unsupported format"); + } + + if (!m_isValid) + return; + + prepare(); +} + +void ControllerRenderingEngine::prepare() { + m_pThread = std::make_unique(); + m_pThread->setObjectName("ControllerScreenRenderer"); + + moveToThread(m_pThread.get()); + connect(this, + &ControllerRenderingEngine::setupRequested, + this, + &ControllerRenderingEngine::setup); + connect(this, + &ControllerRenderingEngine::sendRequested, + this, + &ControllerRenderingEngine::send); + connect(this, + &ControllerRenderingEngine::stopRequested, + this, + &ControllerRenderingEngine::finish); + + m_pThread->start(QThread::NormalPriority); +} + +ControllerRenderingEngine::~ControllerRenderingEngine() { + VERIFY_OR_DEBUG_ASSERT(!m_fbo) { + qWarning() << "The ControllerEngine is being deleted but hasn't been " + "cleaned up. Brace for impact"; + }; +} + +void ControllerRenderingEngine::start() { + VERIFY_OR_DEBUG_ASSERT(!thread()->isFinished() && !thread()->isInterruptionRequested()) { + qWarning() << "Render thread has or ir about to terminate. Cannot " + "start this render anymore."; + return; + } + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); +} +bool ControllerRenderingEngine::isRunning() const { + return m_pThread && m_pThread->isRunning(); +} + +void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngine) { + m_isValid = false; + VERIFY_OR_DEBUG_ASSERT(QThread::currentThread() != thread()) { + qWarning() << "Unable to setup OpenGL rendering context from the same " + "thread as the render object"; + return; + } + emit setupRequested(qmlEngine); + + const auto lock = lockMutex(&m_mutex); + if (!m_quickWindow) { + m_waitCondition.wait(&m_mutex); + } + if (m_isValid) { + m_renderControl->prepareThread(m_pThread.get()); + } +} + +void ControllerRenderingEngine::requestSend(Controller* controller, const QByteArray& frame) { + emit sendRequested(controller, frame); +} + +void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { + QSurfaceFormat format; + format.setSamples(16); + format.setDepthBufferSize(16); + format.setStencilBufferSize(8); + + const auto lock = lockMutex(&s_glMutex); + + m_context = std::make_unique(); + m_context->setFormat(format); + VERIFY_OR_DEBUG_ASSERT(m_context->create()) { + qWarning() << "Unable to initialize controller screen rendering. Giving up"; + m_waitCondition.wakeAll(); + return; + } + connect(m_context.get(), + &QOpenGLContext::aboutToBeDestroyed, + this, + &ControllerRenderingEngine::finish, + Qt::BlockingQueuedConnection); + + m_offscreenSurface = std::make_unique(); + m_offscreenSurface->setFormat(m_context->format()); + + VERIFY_OR_DEBUG_ASSERT(QMetaObject::invokeMethod( + qApp, + [this] { + m_offscreenSurface->create(); + }, + // This invocation will block the current thread! + Qt::BlockingQueuedConnection) && + m_offscreenSurface->isValid()) { + qWarning() << "Unable to create the OffscreenSurface for controller " + "screen rendering. Giving up"; + m_offscreenSurface.reset(); + m_waitCondition.wakeAll(); + return; + } + + m_renderControl = std::make_unique(this); + m_quickWindow = std::make_unique(m_renderControl.get()); + + if (!qmlEngine->incubationController()) + qmlEngine->setIncubationController(m_quickWindow->incubationController()); + + m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height()); + + m_isValid = true; + m_waitCondition.wakeAll(); +} + +void ControllerRenderingEngine::finish() { + m_isValid = false; + disconnect(this); + + const auto lock = lockMutex(&s_glMutex); + + if (m_context && m_context->isValid()) { + disconnect(m_context.get(), + &QOpenGLContext::aboutToBeDestroyed, + this, + &ControllerRenderingEngine::finish); + m_context->makeCurrent(m_offscreenSurface.get()); + m_renderControl.reset(); + + std::shared_ptr pOffscreenSurface = std::move(m_offscreenSurface); + QMetaObject::invokeMethod( + qApp, + [pOffscreenSurface] { + pOffscreenSurface->destroy(); + }); + m_quickWindow.reset(); + + // Free the engine and FBO + m_fbo.reset(); + + m_context->doneCurrent(); + } + m_context.reset(); + m_pThread->quit(); +} + +void ControllerRenderingEngine::renderFrame() { + if (!m_isValid) + return; + + VERIFY_OR_DEBUG_ASSERT(m_offscreenSurface->isValid()) { + qWarning() << "OffscreenSurface isn't valid anymore."; + finish(); + return; + }; + VERIFY_OR_DEBUG_ASSERT(m_context->isValid()) { + qWarning() << "GLContext isn't valid anymore."; + finish(); + return; + }; + + auto lock = lockMutex(&s_glMutex); + + VERIFY_OR_DEBUG_ASSERT(m_context->makeCurrent(m_offscreenSurface.get())) { + qWarning() << "Couldn't make the GLContext current to the OffscreenSurface."; + lock.unlock(); + finish(); + return; + }; + + if (!m_fbo) { + VERIFY_OR_DEBUG_ASSERT(QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) { + qWarning() << "OpenGL doesn't support FBO"; + lock.unlock(); + finish(); + return; + }; + + m_fbo = std::make_unique( + m_screenInfo.size, QOpenGLFramebufferObject::CombinedDepthStencil); + + GLenum glError; + glError = m_context->functions()->glGetError(); + + VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { + qWarning() << "GLError: " << glError; + lock.unlock(); + finish(); + return; + }; + + VERIFY_OR_DEBUG_ASSERT(m_fbo->isValid()) { + qWarning() << "Failed to initialize FBO"; + lock.unlock(); + finish(); + return; + }; + + m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(m_context.get())); + + VERIFY_OR_DEBUG_ASSERT(m_renderControl->initialize()) { + qWarning() << "Failed to initialize redirected Qt Quick rendering"; + lock.unlock(); + finish(); + return; + }; + + m_quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(m_fbo->texture(), + m_screenInfo.size)); + + m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height()); + } + + m_nextFrameStart = mixxx::Time::elapsed(); + + m_renderControl->beginFrame(); + + if (m_pControllerEngine) { + m_pControllerEngine->pause(); + } + + m_renderControl->polishItems(); + + VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { + qWarning() << "Couldn't sync the render control."; + // m_waitCondition.wakeAll(); + lock.unlock(); + finish(); + if (m_pControllerEngine) { + m_pControllerEngine->resume(); + } + + return; + }; + + if (m_pControllerEngine) { + m_pControllerEngine->resume(); + } + QImage fboImage(m_screenInfo.size, m_screenInfo.pixelFormat); + + VERIFY_OR_DEBUG_ASSERT(m_fbo->bind()) { + qWarning() << "Couldn't bind the FBO."; + } + GLenum glError; + m_context->functions()->glFlush(); + glError = m_context->functions()->glGetError(); + VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { + qWarning() << "GLError: " << glError; + lock.unlock(); + finish(); + return; + } + if (m_screenInfo.endian != std::endian::native) { + m_context->functions()->glPixelStorei(GL_PACK_SWAP_BYTES, GL_TRUE); + } + glError = m_context->functions()->glGetError(); + VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { + qWarning() << "GLError: " << glError; + lock.unlock(); + finish(); + return; + } + + QDateTime timestamp = QDateTime::currentDateTime(); + m_renderControl->render(); + m_renderControl->endFrame(); + + while (m_context->functions()->glGetError()) + ; + m_context->functions()->glReadPixels(0, + 0, + m_screenInfo.size.width(), + m_screenInfo.size.height(), + m_GLDataFormat, + m_GLDataType, + fboImage.bits()); + glError = m_context->functions()->glGetError(); + VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { + qWarning() << "GLError: " << glError; + lock.unlock(); + finish(); + return; + } + VERIFY_OR_DEBUG_ASSERT(!fboImage.isNull()) { + qWarning() << "Screen frame is null!"; + } + VERIFY_OR_DEBUG_ASSERT(m_fbo->release()) { + qDebug() << "Couldn't release the FBO."; + } + + fboImage.mirror(false, true); + + emit frameRendered(m_screenInfo, fboImage, timestamp); + + m_context->doneCurrent(); +} + +bool ControllerRenderingEngine::stop() { + emit stopRequested(); + return m_pThread->wait(); +} + +void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { + if (!frame.isEmpty()) { + controller->sendBytes(frame); + } + + if (CmdlineArgs::Instance() + .getControllerDebug()) { + auto endOfRender = mixxx::Time::elapsed(); + qDebug() << "Fame took " + << (endOfRender - m_nextFrameStart).formatMillisWithUnit() + << " and frame has" << frame.size() << "bytes"; + } + + m_nextFrameStart += mixxx::Duration::fromSeconds(1.0 / (double)m_screenInfo.target_fps); + + auto durationToWaitBeforeFrame = (m_nextFrameStart - mixxx::Time::elapsed()); + auto msecToWaitBeforeFrame = durationToWaitBeforeFrame.toIntegerMillis(); + + if (msecToWaitBeforeFrame > 0) { + if (CmdlineArgs::Instance() + .getControllerDebug()) { + qDebug() << "Waiting for " + << durationToWaitBeforeFrame.formatMillisWithUnit() + << " before rendering next frame"; + } + QTimer::singleShot(msecToWaitBeforeFrame, + Qt::PreciseTimer, + this, + &ControllerRenderingEngine::renderFrame); + } else { + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } +} + +bool ControllerRenderingEngine::event(QEvent* event) { + if (event->type() == QEvent::UpdateRequest) { + renderFrame(); + return true; + } + + return QObject::event(event); +} diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h new file mode 100644 index 000000000000..f07c0bf7fb0d --- /dev/null +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -0,0 +1,102 @@ +#pragma once + +#include + +#include +#include +#include + +#include "controllers/legacycontrollermapping.h" +#include "controllers/scripting/controllerscriptenginebase.h" +#include "preferences/configobject.h" +#include "util/time.h" + +class Controller; +class QOffscreenSurface; +class QOpenGLContext; +class QOpenGLFramebufferObject; +class QQmlEngine; +class QQuickRenderControl; +class QQuickWindow; +class QThread; + +/// @brief This class is used to host the rendering of a screen controller, +/// using and existing QML Engine running under a ControllerScriptEngineBase. +class ControllerRenderingEngine : public QObject { + Q_OBJECT + public: + ControllerRenderingEngine(const LegacyControllerMapping::ScreenInfo& info, + ControllerScriptEngineBase* parent); + ~ControllerRenderingEngine(); + + bool event(QEvent* event) override; + + const QSize& size() const { + return m_screenInfo.size; + } + + bool isValid() const { + return m_isValid; + } + + bool isRunning() const; + + QQuickWindow* quickWindow() const { + return m_quickWindow.get(); + } + + const LegacyControllerMapping::ScreenInfo& info() const { + return m_screenInfo; + } + + public slots: + virtual void requestSend(Controller* controller, const QByteArray& frame); + void requestSetup(std::shared_ptr qmlEngine); + void start(); + virtual bool stop(); + + private slots: + void finish(); + void renderFrame(); + void setup(std::shared_ptr qmlEngine); + void send(Controller* controller, const QByteArray& frame); + + signals: + void frameRendered(const LegacyControllerMapping::ScreenInfo& screeninfo, + QImage frame, + const QDateTime& timestamp); + void setupRequested(std::shared_ptr engine); + void stopRequested(); + void sendRequested(Controller* controller, const QByteArray& frame); + + private: + virtual void prepare(); + + mixxx::Duration m_nextFrameStart; + + LegacyControllerMapping::ScreenInfo m_screenInfo; + + std::unique_ptr m_pThread; + + std::unique_ptr m_context; + std::unique_ptr m_offscreenSurface; + std::unique_ptr m_renderControl; + std::unique_ptr m_quickWindow; + + std::unique_ptr m_fbo; + + GLenum m_GLDataFormat; + GLenum m_GLDataType; + + bool m_isValid; + + // These mutexes components are used to ensure internal object synchronicity + QWaitCondition m_waitCondition; + QMutex m_mutex; + + ControllerScriptEngineBase* m_pControllerEngine; + + // This static mutex is used to ensure exclusive access to OpenGL operation + // from each of the ControllerRenderingEngine instances + static QMutex s_glMutex; +}; diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 346c6658f582..9a6d3ca7c916 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -5,7 +5,14 @@ #include "controllers/controller.h" #include "controllers/scripting/colormapperjsproxy.h" #include "errordialoghandler.h" +#ifdef MIXXX_USE_QML +#include +#endif + #include "moc_controllerscriptenginebase.cpp" +#ifdef MIXXX_USE_QML +#include "qml/asyncimageprovider.h" +#endif #include "util/cmdlineargs.h" ControllerScriptEngineBase::ControllerScriptEngineBase( @@ -15,11 +22,27 @@ ControllerScriptEngineBase::ControllerScriptEngineBase( m_pController(controller), m_logger(logger), m_bAbortOnWarning(false), +#ifdef MIXXX_USE_QML + m_bQmlMode(false), +#endif m_bTesting(false) { // Handle error dialog buttons qRegisterMetaType("QMessageBox::StandardButton"); } +#ifdef MIXXX_USE_QML +void ControllerScriptEngineBase::registerTrackCollectionManager( + std::shared_ptr pTrackCollectionManager) { + s_pTrackCollectionManager = std::move(pTrackCollectionManager); +} + +void ControllerScriptEngineBase::handleQMLErrors(const QList& qmlErrors) { + for (const QQmlError& error : std::as_const(qmlErrors)) { + showQMLExceptionDialog(error, m_bErrorsAreFatal); + } +} +#endif + bool ControllerScriptEngineBase::initialize() { VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine) { return false; @@ -28,9 +51,32 @@ bool ControllerScriptEngineBase::initialize() { m_bAbortOnWarning = CmdlineArgs::Instance().getControllerAbortOnWarning(); // Create the Script Engine - m_pJSEngine = std::make_shared(this); +#ifdef MIXXX_USE_QML + if (!m_bQmlMode) { +#endif + m_pJSEngine = std::make_shared(this); - m_pJSEngine->installExtensions(QJSEngine::ConsoleExtension); + m_pJSEngine->installExtensions(QJSEngine::ConsoleExtension); +#ifdef MIXXX_USE_QML + } else { + auto pQmlEngine = std::make_shared(this); + pQmlEngine->addImportPath(QStringLiteral(":/mixxx.org/imports")); + if (s_pTrackCollectionManager) { + mixxx::qml::AsyncImageProvider* pImageProvider = new mixxx::qml::AsyncImageProvider( + s_pTrackCollectionManager); + pQmlEngine->addImageProvider(mixxx::qml::AsyncImageProvider::kProviderName, + pImageProvider); + } else { + DEBUG_ASSERT(!"TrackCollectionManager is missing"); + qWarning() << "TrackCollectionManager hasn't been registered yet"; + } + connect(pQmlEngine.get(), + &QQmlEngine::warnings, + this, + &ControllerScriptEngineBase::handleQMLErrors); + m_pJSEngine = std::move(pQmlEngine); + } +#endif QJSValue engineGlobalObject = m_pJSEngine->globalObject(); @@ -125,6 +171,98 @@ void ControllerScriptEngineBase::showScriptExceptionDialog( } } +#ifdef MIXXX_USE_QML +void ControllerScriptEngineBase::setCanPause(bool canPause) { + auto lock = lockMutex(&m_pauseMutex); + m_canPause = canPause; + + if (m_canPause) { + connect(this, + &ControllerScriptEngineBase::pauseRequested, + this, + &ControllerScriptEngineBase::doPause, + Qt::UniqueConnection); + } else { + lock.unlock(); + QCoreApplication::processEvents(); + lock.relock(); + + disconnect(this, + &ControllerScriptEngineBase::pauseRequested, + this, + &ControllerScriptEngineBase::doPause); + + m_isPaused = false; + m_isPausedCondition.wakeAll(); + } +} +bool ControllerScriptEngineBase::pause() { + const auto lock = lockMutex(&m_pauseMutex); + + if (m_canPause && !m_isPaused) { + emit pauseRequested(); + } + // qDebug() << "Pause requested by" << QThread::currentThread(); + + while (m_canPause && !m_isPaused) { + if (!m_isPausedCondition.wait(&m_pauseMutex, 1000)) { + qWarning() << "Pause request timed out!"; + return false; + } + } + // qDebug() << "Pause granted to" << QThread::currentThread(); + return !m_canPause || m_isPaused; +} +void ControllerScriptEngineBase::resume() { + const auto lock = lockMutex(&m_pauseMutex); + + // qDebug() << "Resume triggered by" << QThread::currentThread(); + + m_isPaused = false; + m_isPausedCondition.wakeAll(); +} +void ControllerScriptEngineBase::doPause() { + const auto lock = lockMutex(&m_pauseMutex); + + m_isPaused = true; + m_isPausedCondition.wakeAll(); + // qDebug() << "Paused of" << QThread::currentThread(); + + while (m_canPause && m_isPaused) { + VERIFY_OR_DEBUG_ASSERT(m_isPausedCondition.wait(&m_pauseMutex, 1000)) { + qWarning() << "Main GUI pause timed out!"; + m_isPaused = false; + }; + } + m_isPausedCondition.wakeAll(); + // qDebug() << "Resume of" << QThread::currentThread(); +} + +void ControllerScriptEngineBase::showQMLExceptionDialog( + const QQmlError& error, bool bFatalError) { + VERIFY_OR_DEBUG_ASSERT(error.isValid()) { + return; + } + + QString filename = error.url().isLocalFile() ? error.url().toLocalFile() + : error.url().toString(); + + if (filename.isEmpty()) { + filename = QStringLiteral(""); + } + QString errorText = QString("Uncaught exception: %1:%2: %3") + .arg(filename) + .arg(error.line()) + .arg(error.description()); + + qCWarning(m_logger) << "ControllerScriptHandlerBase:" << errorText; + + if (!m_bDisplayingExceptionDialog) { + scriptErrorDialog(errorText, errorText, bFatalError); + } +} +#endif + void ControllerScriptEngineBase::logOrThrowError(const QString& errorMessage) { if (m_bAbortOnWarning) { throwJSError(errorMessage); diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 85deba677f13..b83f41240ee8 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -2,12 +2,18 @@ #include #include +#include +#include +#include #include #include "util/runtimeloggingcategory.h" class Controller; class QJSEngine; +#ifdef MIXXX_USE_QML +class TrackCollectionManager; +#endif /// ControllerScriptEngineBase manages the JavaScript engine for controller scripts. /// ControllerScriptModuleEngine implements the current system using JS modules. @@ -26,6 +32,10 @@ class ControllerScriptEngineBase : public QObject { /// Shows a UI dialog notifying of a script evaluation error. /// Precondition: QJSValue.isError() == true void showScriptExceptionDialog(const QJSValue& evaluationResult, bool bFatal = false); +#ifdef MIXXX_USE_QML + /// Precondition: QML.isValid() == true + void showQMLExceptionDialog(const QQmlError& evaluationResult, bool bFatal = false); +#endif void throwJSError(const QString& message); bool willAbortOnWarning() const { @@ -40,13 +50,30 @@ class ControllerScriptEngineBase : public QObject { return m_bTesting; } +#ifdef MIXXX_USE_QML + static void registerTrackCollectionManager( + std::shared_ptr pTrackCollectionManager); +#endif + protected: virtual void shutdown(); void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); void logOrThrowError(const QString& errorMessage); +#ifdef MIXXX_USE_QML + inline void setQMLMode(bool qmlFlag) { + m_bQmlMode = qmlFlag; + } + inline void setErrorsAreFatal(bool errorsAreFatal) { + m_bErrorsAreFatal = errorsAreFatal; + } +#endif + bool m_bDisplayingExceptionDialog; +#ifdef MIXXX_USE_QML + bool m_bErrorsAreFatal; +#endif std::shared_ptr m_pJSEngine; Controller* m_pController; @@ -54,13 +81,47 @@ class ControllerScriptEngineBase : public QObject { bool m_bAbortOnWarning; +#ifdef MIXXX_USE_QML + bool m_bQmlMode; +#endif bool m_bTesting; +#ifdef MIXXX_USE_QML + private: + static inline std::shared_ptr s_pTrackCollectionManager; + + /// Pause the GUI main thread. Pause is required by rendering + /// thread (https://doc.qt.io/qt-6/qquickrendercontrol.html#sync). This + /// offscreen render thread to pause the main "GUI thread" for onboard + /// screens + /// The documentation isn't completely clear about this, but after + /// testing, it appears that the "GUI main thread" is the thread where the QML + /// engine leaves in (also the main thread if we were using a + /// QMLApplication, which isn't the case here) + QWaitCondition m_isPausedCondition; + QMutex m_pauseMutex; + bool m_isPaused{false}; + bool m_canPause{false}; + + public slots: + bool pause(); + void resume(); + void setCanPause(bool canPause); + private slots: + void doPause(); + + signals: + void pauseRequested(); +#endif + protected slots: void reload(); private slots: void errorDialogButton(const QString& key, QMessageBox::StandardButton button); +#ifdef MIXXX_USE_QML + void handleQMLErrors(const QList& qmlErrors); +#endif friend class ColorMapperJSProxy; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 66cb55b4a06e..4673cf38b0d0 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -1,12 +1,34 @@ #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#ifdef MIXXX_USE_QML +#include +#include +#include +#include +#include +#endif + #include "control/controlobject.h" #include "controllers/controller.h" +#ifdef MIXXX_USE_QML +#include "controllers/rendering/controllerrenderingengine.h" +#endif #include "controllers/scripting/colormapperjsproxy.h" #include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" #include "moc_controllerscriptenginelegacy.cpp" +#ifdef MIXXX_USE_QML +#include "util/assert.h" +#include "util/cmdlineargs.h" + +QByteArray + ControllerScriptEngineLegacy::kScreenTranformFunctionUntypedSignature = + QMetaObject::normalizedSignature( + "transformFrame(QVariant,QVariant)"); +QByteArray ControllerScriptEngineLegacy::kScreenTranformFunctionTypedSignature = + QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); +#endif ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( Controller* controller, const RuntimeLoggingCategory& logger) @@ -14,13 +36,41 @@ ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, + [this](const QString& changedFile) { + qDebug() << "File" << changedFile << "has been changed."; + // This is to prevent double-reload when a file is updated twice + // in a row as part of the normal saving process. See note in + // QFileSystemWatcher::fileChanged documentation. + if (m_fileWatcher.removePath(changedFile)) { + reload(); + } + }); +#ifdef MIXXX_USE_QML + connect(&m_fileWatcher, + &QFileSystemWatcher::directoryChanged, + this, &ControllerScriptEngineLegacy::reload); +#endif } ControllerScriptEngineLegacy::~ControllerScriptEngineLegacy() { shutdown(); } +void ControllerScriptEngineLegacy::watchFilePath(const QString& path) { + if (m_fileWatcher.files().contains(path) || m_fileWatcher.directories().contains(path)) { + qDebug() << "File" << path << "is already being watch for controller auto-reload"; + return; + } + + if (!m_fileWatcher.addPath(path)) { + qCWarning(m_logger) << "Failed to watch script file" + << path; + } else { + qDebug() << "Watching file" << path << "for controller auto-reload"; + } +} + bool ControllerScriptEngineLegacy::callFunctionOnObjects( const QList& scriptFunctionPrefixes, const QString& function, @@ -54,6 +104,82 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( success = false; } } +#ifdef MIXXX_USE_QML + if (m_bQmlMode) { + QHashIterator> i(m_rootItems); + while (i.hasNext()) { + i.next(); + const QMetaObject* metaObject = i.value()->metaObject(); + QStringList argList; + for (int i = 0; i < args.size(); i++) { + argList << "QVariant"; + } + int methodIdx = + metaObject->indexOfMethod(QString("%1(%2)") + .arg(function, argList.join(',')) + .toUtf8()); + if (methodIdx == -1) { + qCWarning(m_logger) << "QML Scene " << i.key() << "has no" + << function << " method"; + continue; + } + QMetaMethod method = metaObject->method(methodIdx); + qCDebug(m_logger) << "Executing" + << function << "on QML Scene " << i.key(); + + VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { + qWarning() << "Controller JS engine has an unhandled error. Discarding."; + qDebug() << "Controller JS error is:" << m_pJSEngine->catchError().toString(); + } + + switch (args.size()) { + case 0: + success &= method.invoke(i.value().get(), + Qt::DirectConnection); + break; + case 1: + success &= method.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, args[0].toVariant())); + break; + case 2: + success &= method.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, args[0].toVariant()), + Q_ARG(QVariant, args[1].toVariant())); + break; + case 3: + success &= method.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, args[0].toVariant()), + Q_ARG(QVariant, args[1].toVariant()), + Q_ARG(QVariant, args[2].toVariant())); + break; + case 4: + success &= method.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, args[0].toVariant()), + Q_ARG(QVariant, args[1].toVariant()), + Q_ARG(QVariant, args[2].toVariant()), + Q_ARG(QVariant, args[3].toVariant())); + break; + default: + qDebug() << "Trying to call a controller lifecycle method with " + "more than 5 args. Ignoring extra args"; + [[fallthrough]]; + case 5: + success &= method.invoke(i.value().get(), + Qt::DirectConnection, + Q_ARG(QVariant, args[0].toVariant()), + Q_ARG(QVariant, args[1].toVariant()), + Q_ARG(QVariant, args[2].toVariant()), + Q_ARG(QVariant, args[3].toVariant()), + Q_ARG(QVariant, args[4].toVariant())); + break; + } + } + } +#endif return success; } @@ -90,6 +216,25 @@ QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( return wrappedFunction; } +#ifdef MIXXX_USE_QML +void ControllerScriptEngineLegacy::setLibraryDirectories( + const QList& directories) { + const QStringList paths = m_fileWatcher.files(); + if (!paths.isEmpty()) { + m_fileWatcher.removePaths(paths); + } + + m_libraryDirectories = directories; +} +void ControllerScriptEngineLegacy::setInfoScrens( + const QList& screens) { + m_rootItems.clear(); + m_renderingScreens.clear(); + m_transformScreenFrameFunctions.clear(); + m_infoScreens = screens; +} +#endif + void ControllerScriptEngineLegacy::setScriptFiles( const QList& scripts) { const QStringList paths = m_fileWatcher.files(); @@ -97,6 +242,17 @@ void ControllerScriptEngineLegacy::setScriptFiles( m_fileWatcher.removePaths(paths); } m_scriptFiles = scripts; + +#ifdef MIXXX_USE_QML + for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { + if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::QML) { + setQMLMode(true); + return; + } + } + + setQMLMode(false); +#endif } void ControllerScriptEngineLegacy::setSettings( @@ -116,6 +272,57 @@ bool ControllerScriptEngineLegacy::initialize() { return false; } +#ifdef MIXXX_USE_QML + // During the initialisation, any QML errors are considered fatal + setErrorsAreFatal(true); + QMap> availableScreens; + + if (m_bQmlMode) { + for (const LegacyControllerMapping::ScreenInfo& screen : std::as_const(m_infoScreens)) { + VERIFY_OR_DEBUG_ASSERT(!availableScreens.contains(screen.identifier)) { + qWarning() << "A controller screen already contains the " + "identifier " + << screen.identifier; + return false; + } + availableScreens.insert(screen.identifier, + std::make_shared(screen, this)); + + if (!availableScreens.value(screen.identifier)->isValid()) { + qWarning() << QString( + "Unable to start the screen render for %1.") + .arg(screen.identifier); + return false; + } + + if (m_bTesting) { + continue; + } + + // Rename the ControllerRenderingEngine with the actual screen + // identifier to help debbuging + availableScreens.value(screen.identifier) + ->thread() + ->setObjectName( + QString("CtrlScreen_%1").arg(screen.identifier)); + availableScreens.value(screen.identifier) + ->requestSetup( + std::dynamic_pointer_cast(m_pJSEngine)); + + if (!availableScreens.value(screen.identifier)->isValid()) { + qWarning() << QString( + "Unable to setup the screen render for %1.") + .arg(screen.identifier); + availableScreens.value(screen.identifier)->stop(); + return false; + } + } + } else if (!m_infoScreens.isEmpty()) { + qWarning() << "Controller mapping has screen definitions but no QML " + "files to render on it. Ignoring."; + } +#endif + // Binary data is passed from the Controller as a QByteArray, which // QJSEngine::toScriptValue converts to an ArrayBuffer in JavaScript. // ArrayBuffer cannot be accessed with the [] operator in JS; it needs @@ -139,15 +346,87 @@ bool ControllerScriptEngineLegacy::initialize() { engineGlobalObject.setProperty( "engine", m_pJSEngine->newQObject(legacyScriptInterface)); +#ifdef MIXXX_USE_QML + if (m_bQmlMode) { + for (const LegacyControllerMapping::QMLModuleInfo& module : + std::as_const(m_libraryDirectories)) { + auto path = module.dirinfo.absoluteFilePath(); + QDirIterator it(path, + QStringList() << "*.qml", + QDir::Files, + QDirIterator::Subdirectories); + while (it.hasNext()) { + watchFilePath(it.next()); + } + watchFilePath(path); + auto pQmlEngine = std::dynamic_pointer_cast(m_pJSEngine); + pQmlEngine->addImportPath(path); + qWarning() << pQmlEngine->importPathList(); + } + } else if (!m_libraryDirectories.isEmpty()) { + qWarning() << "Controller mapping has QML library definitions but no " + "QML files to use it. Ignoring."; + } + + // If we encounter a failure while loading a scene, we will need to properly + // stop the screen threads before shutting down. + bool sceneBindingHasFailure = false; +#endif for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { - if (!evaluateScriptFile(script.file)) { - shutdown(); - return false; +#ifdef MIXXX_USE_QML + if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT) { +#endif + if (!evaluateScriptFile(script.file)) { + shutdown(); + return false; + } + if (!script.identifier.isEmpty()) { + m_scriptFunctionPrefixes.append(script.identifier); + } +#ifdef MIXXX_USE_QML + } else { + if (script.identifier.isEmpty()) { + while (!availableScreens.isEmpty()) { + QString screenIdentifier(availableScreens.firstKey()); + if (!bindSceneToScreen(script, + screenIdentifier, + availableScreens.take(screenIdentifier))) { + sceneBindingHasFailure = true; + } + } + } else { + if (!availableScreens.contains(script.identifier)) { + qCritical() << "Not screen" << script.identifier << "found!"; + + sceneBindingHasFailure = true; + break; + } + if (!bindSceneToScreen(script, + script.identifier, + availableScreens.take(script.identifier))) { + sceneBindingHasFailure = true; + } + } } - if (!script.functionPrefix.isEmpty()) { - m_scriptFunctionPrefixes.append(script.functionPrefix); + } + + if (!availableScreens.isEmpty()) { + if (!sceneBindingHasFailure) { + qWarning() + << "Found screen with no QML scene able to run on it. Ignoring" + << availableScreens.size() << "screens"; + } + + while (!availableScreens.isEmpty()) { + auto orphanScreen = availableScreens.take(availableScreens.firstKey()); + std::move(orphanScreen)->deleteLater(); } } + if (sceneBindingHasFailure) { + shutdown(); + return false; +#endif + } // For testing, do not actually initialize the scripts, just check for // syntax errors above. @@ -171,20 +450,247 @@ bool ControllerScriptEngineLegacy::initialize() { controllerName, m_logger().isDebugEnabled(), }; - if (!callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true)) { + +#ifdef MIXXX_USE_QML + setCanPause(true); + for (const auto& pScreen : qAsConst(m_renderingScreens)) { + pScreen->start(); + } +#endif + + if ( + !callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true)) { shutdown(); return false; } +#ifdef MIXXX_USE_QML + // At runtime, QML errors aren't considered fatal anymore now that the engine has started + setErrorsAreFatal(false); +#endif + + return true; +} + +#ifdef MIXXX_USE_QML +void ControllerScriptEngineLegacy::extractTranformFunction( + const QMetaObject* metaObject, const QString& screenIdentifier) { + VERIFY_OR_DEBUG_ASSERT(metaObject) { + qWarning() << "Invalid meta object for screen" << screenIdentifier + << "It may be that an unhandled issue occurred when imnporting the scene."; + return; + } + + QMetaMethod tranformFunction; + bool typed = false; + int methodIdx = metaObject->indexOfMethod(kScreenTranformFunctionUntypedSignature); + + if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { + qDebug() << "QML Scene for screen" << screenIdentifier + << "has no valid untyped transformFrame method."; + methodIdx = metaObject->indexOfMethod(kScreenTranformFunctionTypedSignature); + typed = true; + } + + tranformFunction = metaObject->method(methodIdx); + + if (!tranformFunction.isValid()) { + qDebug() << "QML Scene for screen" << screenIdentifier + << "has no valid typed transformFrame method. The frame data will be sent " + "untransformed"; + QStringList methods; + for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) { + methods << QString::fromLatin1(metaObject->method(i).methodSignature()); + } + qDebug() << "Found methods are: " << methods.join(", "); + } + + m_transformScreenFrameFunctions.insert(screenIdentifier, + TransformScreenFrameFunction{tranformFunction, typed}); +} + +bool ControllerScriptEngineLegacy::bindSceneToScreen( + const LegacyControllerMapping::ScriptFileInfo& qmlFile, + const QString& screenIdentifier, + std::shared_ptr pScreen) { + // Like for Javascript, if the script is invalid, it should be watched so the user can fix it + // without having to restart Mixxx. So, add it to the watcher before + // evaluating it. + watchFilePath(qmlFile.file.absoluteFilePath()); + + auto pScene = loadQMLFile(qmlFile, pScreen); + if (!pScene) { + pScreen->stop(); + std::move(pScreen)->deleteLater(); + return false; + } + const QMetaObject* metaObject = pScene->metaObject(); + + extractTranformFunction(metaObject, screenIdentifier); + connect(pScreen.get(), + &ControllerRenderingEngine::frameRendered, + this, + &ControllerScriptEngineLegacy::handleScreenFrame); + m_renderingScreens.insert(screenIdentifier, pScreen); + m_rootItems.insert(screenIdentifier, pScene); return true; } +void ControllerScriptEngineLegacy::handleScreenFrame( + const LegacyControllerMapping::ScreenInfo& screeninfo, + const QImage& frame, + const QDateTime& timestamp) { + VERIFY_OR_DEBUG_ASSERT( + m_transformScreenFrameFunctions.contains(screeninfo.identifier) || + m_renderingScreens.contains(screeninfo.identifier)) { + qWarning() << "Unable to find transform function info for the given screen"; + return; + }; + VERIFY_OR_DEBUG_ASSERT(m_rootItems.contains(screeninfo.identifier)) { + qWarning() << "Unable to find a root item for the given screen"; + return; + }; + + if (CmdlineArgs::Instance().getControllerPreviewScreens()) { + QImage screenDebug(frame); + + if (screeninfo.endian != std::endian::native) { + switch (screeninfo.endian) { + case std::endian::big: + qFromBigEndian(frame.constBits(), + frame.sizeInBytes() / 2, + screenDebug.bits()); + break; + case std::endian::little: + qFromLittleEndian(frame.constBits(), + frame.sizeInBytes() / 2, + screenDebug.bits()); + break; + default: + break; + } + } + if (screeninfo.reversedColor) { + screenDebug.rgbSwap(); + } + + emit previewRenderedScreen(screeninfo, screenDebug); + } + + QByteArray input((const char*)frame.constBits(), frame.sizeInBytes()); + const TransformScreenFrameFunction& tranformMethod = + m_transformScreenFrameFunctions[screeninfo.identifier]; + + if (!tranformMethod.method.isValid() && screeninfo.rawData) { + m_renderingScreens[screeninfo.identifier]->requestSend(m_pController, input); + return; + } + + if (!tranformMethod.method.isValid()) { + qWarning() + << "Could not find a valid transform function but the screen " + "doesn't accept raw data. Aborting screen rendering."; + m_renderingScreens[screeninfo.identifier]->stop(); + return; + } + + QVariant returnedValue; + + VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { + qWarning() << "Controller JS engine has an unhandled error. Discarding."; + qDebug() << "Controller JS error is:" << m_pJSEngine->catchError().toString(); + } + // During the frame transformation, any QML errors are considered fatal + setErrorsAreFatal(true); + bool isSuccessful = tranformMethod.typed + ? tranformMethod.method.invoke( + m_rootItems.value(screeninfo.identifier).get(), + Qt::DirectConnection, + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, input), + Q_ARG(QDateTime, timestamp)) + : tranformMethod.method.invoke( + m_rootItems.value(screeninfo.identifier).get(), + Qt::DirectConnection, + Q_RETURN_ARG(QVariant, returnedValue), + Q_ARG(QVariant, input), + Q_ARG(QVariant, timestamp)); + setErrorsAreFatal(false); + + if (!isSuccessful) { + qWarning() << "Could not transform rendering buffer for screen" << screeninfo.identifier; + + // We manually stop the screen before we trigger the shutdown procedure + // as this last one may continue rendering process in order to perform + // screen splash off + shutdown(); + return; + } + if (!isSuccessful || !returnedValue.isValid()) { + qWarning() << "Could not transform rendering buffer. The transform " + "function didn't return the expected Array. Stopping " + "rendering on this screen"; + return; + } + + QByteArray transformedFrame; + + if (returnedValue.canView()) { + transformedFrame = returnedValue.view(); + } else if (returnedValue.canConvert()) { + transformedFrame = returnedValue.toByteArray(); + } else { + qWarning() << "Unable to interpret the returned data " << returnedValue; + return; + } + + if (CmdlineArgs::Instance().getControllerDebug()) { + qDebug() << "Transform screen data for screen " << screeninfo.identifier + << "(first 64 bytes)" + << QByteArray(transformedFrame.toHex(' '), 128); + m_pController->sendBytes(returnedValue.view()); + } + + m_renderingScreens[screeninfo.identifier]->requestSend( + m_pController, transformedFrame); +} +#endif + void ControllerScriptEngineLegacy::shutdown() { // There is no js engine if the mapping was not loaded from a file but by // creating a new, empty mapping LegacyMidiControllerMapping with the wizard if (m_pJSEngine) { callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); } + +#ifdef MIXXX_USE_QML + setCanPause(false); + // Wait till the splash off animation has finished rendering + uint maxSplashOffDuration = 0; + for (const auto& pScreen : qAsConst(m_renderingScreens)) { + if (!pScreen->isRunning()) { + continue; + } + maxSplashOffDuration = qMax(maxSplashOffDuration, pScreen->info().splash_off); + } + + auto splashOffDeadline = mixxx::Duration::fromMillis(maxSplashOffDuration) + + mixxx::Time::elapsed(); + while (splashOffDeadline > mixxx::Time::elapsed()) { + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, + (splashOffDeadline - mixxx::Time::elapsed()).toIntegerMillis()); + } + + m_rootItems.clear(); + for (const auto& pScreen : qAsConst(m_renderingScreens)) { + VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || + !pScreen->isRunning() || pScreen->stop()) { + qWarning() << "Unable to stop the screen"; + }; + } + m_renderingScreens.clear(); + m_transformScreenFrameFunctions.clear(); +#endif m_scriptWrappedFunctionCache.clear(); m_incomingDataFunctions.clear(); m_scriptFunctionPrefixes.clear(); @@ -226,10 +732,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil // If the script is invalid, it should be watched so the user can fix it // without having to restart Mixxx. So, add it to the watcher before // evaluating it. - if (!m_fileWatcher.addPath(scriptFile.absoluteFilePath())) { - qCWarning(m_logger) << "Failed to watch script file" << scriptFile.absoluteFilePath(); - }; - + watchFilePath(scriptFile.absoluteFilePath()); qCDebug(m_logger) << "Loading" << scriptFile.absoluteFilePath(); @@ -278,6 +781,91 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil return true; } +#ifdef MIXXX_USE_QML +std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( + const LegacyControllerMapping::ScriptFileInfo& qmlScript, + std::shared_ptr pScreen) { + VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || + qmlScript.type != + LegacyControllerMapping::ScriptFileInfo::Type::QML) { + return std::shared_ptr(nullptr); + } + + std::unique_ptr qmlComponent = + std::make_unique( + std::dynamic_pointer_cast(m_pJSEngine).get()); + + QFile scene = QFile(qmlScript.file.absoluteFilePath()); + if (!scene.exists()) { + qWarning() << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() + << "does not exist."; + return std::shared_ptr(nullptr); + } + + QDir dir(m_resourcePath + "/qml/"); + + scene.open(QIODevice::ReadOnly); + qmlComponent->setData(scene.readAll(), + // Obfuscate the scene filename to make it appear in the QML folder. + // This allows a smooth integration with QML components. + QUrl::fromLocalFile( + dir.absoluteFilePath(qmlScript.file.fileName()))); + scene.close(); + + while (qmlComponent->isLoading()) { + qDebug() << "Waiting for component " + << qmlScript.file.absoluteFilePath() + << " to be ready: " << qmlComponent->progress(); + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 500); + } + + if (qmlComponent->isError()) { + const QList errorList = qmlComponent->errors(); + for (const QQmlError& error : errorList) { + qWarning() << "Unable to load the QML scene:" << error.url() + << "at line" << error.line() << ", error: " << error; + showQMLExceptionDialog(error, true); + } + return std::shared_ptr(nullptr); + } + + VERIFY_OR_DEBUG_ASSERT(qmlComponent->isReady()) { + qWarning() << "QMLComponent isn't ready although synchronous load was requested."; + return std::shared_ptr(nullptr); + } + + QObject* pRootObject = qmlComponent->createWithInitialProperties( + QVariantMap{{"screenId", pScreen->info().identifier}}); + if (qmlComponent->isError()) { + const QList errorList = qmlComponent->errors(); + for (const QQmlError& error : errorList) { + qWarning() << error.url() << error.line() << error; + } + return std::shared_ptr(nullptr); + } + + std::shared_ptr rootItem = + std::shared_ptr(qobject_cast(pRootObject)); + if (!rootItem) { + qWarning("run: Not a QQuickItem"); + delete pRootObject; + return std::shared_ptr(nullptr); + } + + watchFilePath(qmlScript.file.absoluteFilePath()); + + // The root item is ready. Associate it with the window. + if (!m_bTesting) { + rootItem->setParentItem(pScreen->quickWindow()->contentItem()); + + rootItem->setWidth(pScreen->quickWindow()->width()); + rootItem->setHeight(pScreen->quickWindow()->height()); + } + + return rootItem; +} +#endif + QJSValue ControllerScriptEngineLegacy::wrapArrayBufferCallback(const QJSValue& callback) { return m_makeArrayBufferWrapperFunction.call(QJSValueList{callback}); } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 9bf379dc0cf4..4d0ce7151838 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -4,10 +4,18 @@ #include #include #include +#ifdef MIXXX_USE_QML +#include +#endif #include "controllers/legacycontrollermapping.h" #include "controllers/scripting/controllerscriptenginebase.h" +#ifdef MIXXX_USE_QML +class QQuickItem; +class ControllerRenderingEngine; +#endif + /// ControllerScriptEngineLegacy loads and executes controller scripts for the legacy /// JS/XML hybrid controller mapping system. class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { @@ -38,6 +46,26 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { void setSettings( const QList>& settings); +#ifdef MIXXX_USE_QML + void setLibraryDirectories(const QList& scripts); + void setInfoScrens(const QList& scripts); + void setResourcePath(const QString& resourcePath) { + m_resourcePath = resourcePath; + } + + private slots: + void handleScreenFrame( + const LegacyControllerMapping::ScreenInfo& screeninfo, + const QImage& frame, + const QDateTime& timestamp); + + signals: + /// Emitted when a screen has been rendered + // TODO (XXX) Move this signal in ControllerScriptEngineBase when ScreenInfo + // isn't tight to LegacyControllerMapping anymore + void previewRenderedScreen(const LegacyControllerMapping::ScreenInfo& screen, QImage frame); +#endif + private: struct Setting { QString name; @@ -45,16 +73,44 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { }; bool evaluateScriptFile(const QFileInfo& scriptFile); - void shutdown() override; +#ifdef MIXXX_USE_QML + bool bindSceneToScreen( + const LegacyControllerMapping::ScriptFileInfo& qmlFile, + const QString& screenIdentifier, + std::shared_ptr pScreen); + void extractTranformFunction(const QMetaObject* metaObject, const QString& screenIdentifier); + std::shared_ptr loadQMLFile( + const LegacyControllerMapping::ScriptFileInfo& qmlScript, + std::shared_ptr pScreen); + + static QByteArray kScreenTranformFunctionUntypedSignature; + static QByteArray kScreenTranformFunctionTypedSignature; + + struct TransformScreenFrameFunction { + QMetaMethod method; + bool typed; + }; +#endif + + void shutdown() override; QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool callFunctionOnObjects(const QList& scriptFunctionPrefixes, const QString&, const QJSValueList& args = {}, bool bFatalError = false); + void watchFilePath(const QString& path); QJSValue m_makeArrayBufferWrapperFunction; QList m_scriptFunctionPrefixes; +#ifdef MIXXX_USE_QML + QHash> m_renderingScreens; + QHash> m_rootItems; + QHash m_transformScreenFrameFunctions; + QList m_libraryDirectories; + QList m_infoScreens; + QString m_resourcePath{"."}; +#endif QList m_incomingDataFunctions; QHash m_scriptWrappedFunctionCache; QList m_scriptFiles; diff --git a/src/coreservices.cpp b/src/coreservices.cpp index 3a66ab5decc5..aaaebf362d89 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -28,6 +28,17 @@ #include "preferences/dialog/dlgprefmodplug.h" #endif #include "skin/skincontrols.h" +#ifdef MIXXX_USE_QML +#include "controllers/scripting/controllerscriptenginebase.h" +#include "qml/qmlconfigproxy.h" +#include "qml/qmlcontrolproxy.h" +#include "qml/qmldlgpreferencesproxy.h" +#include "qml/qmleffectslotproxy.h" +#include "qml/qmleffectsmanagerproxy.h" +#include "qml/qmllibraryproxy.h" +#include "qml/qmlplayermanagerproxy.h" +#include "qml/qmlplayerproxy.h" +#endif #include "soundio/soundmanager.h" #include "sources/soundsourceproxy.h" #include "util/clipboard.h" @@ -449,6 +460,28 @@ void CoreServices::initialize(QApplication* pApp) { } m_isInitialized = true; + +#ifdef MIXXX_USE_QML + initializeQMLSignletons(); +} + +void CoreServices::initializeQMLSignletons() { + // Any uncreateable non-singleton types registered here require + // arguments that we don't want to expose to QML directly. Instead, they + // can be retrieved by member properties or methods from the singleton + // types. + // + // The alternative would be to register their *arguments* in the QML + // system, which would improve nothing, or we had to expose them as + // singletons to that they can be accessed by components instantiated by + // QML, which would also be suboptimal. + mixxx::qml::QmlEffectsManagerProxy::registerEffectsManager(getEffectsManager()); + mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(getPlayerManager()); + mixxx::qml::QmlConfigProxy::registerUserSettings(getSettings()); + mixxx::qml::QmlLibraryProxy::registerLibrary(getLibrary()); + + ControllerScriptEngineBase::registerTrackCollectionManager(getTrackCollectionManager()); +#endif } void CoreServices::initializeKeyboard() { @@ -556,6 +589,16 @@ void CoreServices::finalize() { Timer t("CoreServices::~CoreServices"); t.start(); +#ifdef MIXXX_USE_QML + // Delete all the QML singletons in order to prevent controller leaks + mixxx::qml::QmlEffectsManagerProxy::registerEffectsManager(nullptr); + mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(nullptr); + mixxx::qml::QmlConfigProxy::registerUserSettings(nullptr); + mixxx::qml::QmlLibraryProxy::registerLibrary(nullptr); + + ControllerScriptEngineBase::registerTrackCollectionManager(nullptr); +#endif + // Stop all pending library operations qDebug() << t.elapsed(false).debugMillisWithUnit() << "stopping pending Library tasks"; m_pTrackCollectionManager->stopLibraryScan(); diff --git a/src/coreservices.h b/src/coreservices.h index b5d5325c4d31..0102d2000583 100644 --- a/src/coreservices.h +++ b/src/coreservices.h @@ -115,6 +115,9 @@ class CoreServices : public QObject { void initializeSettings(); void initializeScreensaverManager(); void initializeLogging(); +#ifdef MIXXX_USE_QML + void initializeQMLSignletons(); +#endif /// Tear down CoreServices that were previously initialized by `initialize()`. void finalize(); diff --git a/src/qml/qmlapplication.cpp b/src/qml/qmlapplication.cpp index 11ace8aa0554..dbae62d83dea 100644 --- a/src/qml/qmlapplication.cpp +++ b/src/qml/qmlapplication.cpp @@ -65,20 +65,6 @@ QmlApplication::QmlApplication( // Since DlgPreferences is only meant to be used in the main QML engine, it // follows a strict singleton pattern design QmlDlgPreferencesProxy::s_pInstance = new QmlDlgPreferencesProxy(pDlgPreferences, this); - - // Any uncreateable non-singleton types registered here require arguments - // that we don't want to expose to QML directly. Instead, they can be - // retrieved by member properties or methods from the singleton types. - // - // The alternative would be to register their *arguments* in the QML - // system, which would improve nothing, or we had to expose them as - // singletons to that they can be accessed by components instantiated by - // QML, which would also be suboptimal. - QmlEffectsManagerProxy::registerEffectsManager(pCoreServices->getEffectsManager()); - QmlPlayerManagerProxy::registerPlayerManager(pCoreServices->getPlayerManager()); - QmlConfigProxy::registerUserSettings(pCoreServices->getSettings()); - QmlLibraryProxy::registerLibrary(pCoreServices->getLibrary()); - loadQml(m_mainFilePath); pCoreServices->getControllerManager()->setUpDevices(); @@ -93,10 +79,6 @@ QmlApplication::QmlApplication( QmlApplication::~QmlApplication() { // Delete all the QML singletons in order to prevent leak detection in CoreService - QmlEffectsManagerProxy::registerEffectsManager(nullptr); - QmlPlayerManagerProxy::registerPlayerManager(nullptr); - QmlConfigProxy::registerUserSettings(nullptr); - QmlLibraryProxy::registerLibrary(nullptr); QmlDlgPreferencesProxy::s_pInstance->deleteLater(); } diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp new file mode 100644 index 000000000000..1d70d99074cd --- /dev/null +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -0,0 +1,936 @@ +#include +#include + +#include +#include + +#include "controllers/defs_controllers.h" +#include "controllers/legacycontrollermapping.h" +#include "controllers/legacycontrollermappingfilehandler.h" +#include "helpers/log_test.h" +#include "test/mixxxtest.h" +#include "util/time.h" + +using ::testing::_; + +class LegacyControllerMappingFileHandlerTest + : public LegacyControllerMappingFileHandler, + public MixxxTest { + public: + void SetUp() override { + mixxx::Time::setTestMode(true); + mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); + SETUP_LOG_CAPTURE(); + } + + void TearDown() override { + mixxx::Time::setTestMode(false); + } + std::shared_ptr load(const QDomElement&, + const QString&, + const QDir&) override { + throw std::runtime_error("not implemented"); + } + static QFileInfo findLibraryPath(std::shared_ptr, + const QString& dirname, + const QDir&) { + return QFileInfo(QDir("/dummy/path/").absoluteFilePath(dirname)); + } + static QFileInfo findScriptFile(std::shared_ptr, + const QString& filename, + const QDir&) { + return QFileInfo(QDir("/dummy/path/").absoluteFilePath(filename)); + } +}; + +class MockLegacyControllerMapping : public LegacyControllerMapping { + public: + MOCK_METHOD(void, + addScriptFile, + (const QString& name, + const QString& identifier, + const QFileInfo& file, + ScriptFileInfo::Type type, + bool builtin), + (override)); + MOCK_METHOD(void, + addScreenInfo, + (const QString& identifier, + const QSize& size, + uint targetFps, + uint splashoff, + QImage::Format pixelFormat, + std::endian endian, + bool reversedColorse, + bool rawData), + (override)); + MOCK_METHOD(void, addLibraryDirectory, (const QFileInfo& dirinfo, bool builtin), (override)); + + std::shared_ptr clone() const override { + throw std::runtime_error("not implemented"); + } + bool saveMapping(const QString&) const override { + throw std::runtime_error("not implemented"); + } + bool isMappable() const override { + throw std::runtime_error("not implemented"); + } +}; + +TEST_F(LegacyControllerMappingFileHandlerTest, canParseSimpleMapping) { + QDomDocument doc; + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, + addScriptFile(QString("DummyDeviceDefaultScreen.js"), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + false)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addLibraryDirectory(_, _)).Times(0); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { + QDomDocument doc; + doc.setContent(QByteArray(R"EOF( + + + + + + + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, + addScriptFile(QString("DummyDeviceDefaultScreen.qml"), + QString(""), + QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), + LegacyControllerMapping::ScriptFileInfo::Type::QML, + false)); + EXPECT_CALL(*mapping, + addScreenInfo(QString("main"), + QSize(480, 360), + 20, + 2000, + QImage::Format_RGBA8888, + std::endian::little, + false, + false)); + EXPECT_CALL(*mapping, addLibraryDirectory(QFileInfo("/dummy/path/foobar"), false)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { + QDomDocument doc; + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, 20, _, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Invalid target FPS. Target FPS must be between 1 and %0") + .arg(s_maxTargetFps)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + QString("Invalid target FPS. Target FPS must be between 1 and %0") + .arg(s_maxTargetFps)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + QString("Invalid target FPS. Target FPS must be between 1 and %0") + .arg(s_maxTargetFps)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + QString("Invalid target FPS. Target FPS must be between 1 and %0") + .arg(s_maxTargetFps)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { + QDomDocument doc; + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, QSize(10, 10), _, _, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Invalid screen size. Screen size must have a width and height above 1 pixel"); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Invalid screen size. Screen size must have a width and height above 1 pixel"); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Invalid screen size. Screen size must have a width and height above 1 pixel"); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Invalid screen size. Screen size must have a width and height above 1 pixel"); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) { + // pixelType + // endian + + // No pixel type default to RGB 8-bits depth + QDomDocument doc; + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, QImage::Format_RGB888, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, QImage::Format_RGB16, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Unsupported pixel format \"FOOBAR\""); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::little, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::little, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::big, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_LOG_MSG( + QtWarningMsg, + "Unknown endiant format \"enormous\""); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesDefinition) { + QStringList kFalseValue = {"false", "FALse", "no", "nope", "maybe"}; + QStringList kTrueValue = {"true", "trUe", "1", "yes"}; + QDomDocument doc; + std::shared_ptr mapping; + + // reversed + for (const QString& falseValue : std::as_const(kFalseValue)) { + doc.setContent( + QString(R"EOF( + + + + + + )EOF") + .arg(falseValue) + .toUtf8()); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, false, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + } + for (const QString& falseValue : std::as_const(kTrueValue)) { + doc.setContent( + QString(R"EOF( + + + + + + )EOF") + .arg(falseValue) + .toUtf8()); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, true, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + } + // raw + for (const QString& falseValue : std::as_const(kFalseValue)) { + doc.setContent( + QString(R"EOF( + + + + + + )EOF") + .arg(falseValue) + .toUtf8()); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, false)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + } + for (const QString& falseValue : std::as_const(kTrueValue)) { + doc.setContent( + QString(R"EOF( + + + + + + )EOF") + .arg(falseValue) + .toUtf8()); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, true)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + } +} + +TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDefinition) { + // splashoff + QDomDocument doc; + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 500, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, s_maxSplashOffDuration, _, _, _, _)); + EXPECT_LOG_MSG( + QtWarningMsg, + QString("Invalid splashoff duration. Splashoff duration must " + "be between 0 and %0. Clamping to %1") + .arg(s_maxSplashOffDuration) + .arg(s_maxSplashOffDuration)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + ASSERT_ALL_EXPECTED_MSG(); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); + + doc.setContent( + QByteArray(R"EOF( + + + + + + )EOF")); + + mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); +} + +TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { + QDomDocument doc; + + doc.setContent(QByteArray(R"EOF( + + + + + + + + + + + + + )EOF")); + + auto mapping = std::make_shared(); + // This file always gets added + EXPECT_CALL(*mapping, + addScriptFile(QString(REQUIRED_SCRIPT_FILE), + QString(""), + _, // gmock seems unable to assert QFileInfo + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + true)); + + EXPECT_CALL(*mapping, + addScriptFile(QString("DummyDeviceDefaultScreen.qml"), + QString(""), + QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), + LegacyControllerMapping::ScriptFileInfo::Type::QML, + false)); + EXPECT_CALL(*mapping, + addScriptFile(QString("LegacyScript.js"), + QString(""), + QFileInfo("/dummy/path/LegacyScript.js"), + LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + false)); + EXPECT_CALL(*mapping, + addScreenInfo(QString("main"), + QSize(480, 360), + 20, + 2000, + QImage::Format_RGBA8888, + std::endian::little, + false, + false)); + EXPECT_CALL(*mapping, addLibraryDirectory(QFileInfo("/dummy/path/foobar"), false)); + + addScriptFilesToMapping( + doc.documentElement(), + mapping, + QDir()); +} diff --git a/src/test/controller_mapping_validation_test.cpp b/src/test/controller_mapping_validation_test.cpp index 29be36d171c7..1ffbfad36285 100644 --- a/src/test/controller_mapping_validation_test.cpp +++ b/src/test/controller_mapping_validation_test.cpp @@ -1,9 +1,23 @@ #include "test/controller_mapping_validation_test.h" +#include +#include + #include #include "controllers/defs_controllers.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#ifdef MIXXX_USE_QML +#include "effects/effectsmanager.h" +#include "engine/channelhandle.h" +#include "engine/enginemixer.h" +#include "library/coverartcache.h" +#include "library/library.h" +#include "mixer/playerinfo.h" +#include "mixer/playermanager.h" +#include "qml/qmlplayermanagerproxy.h" +#include "soundio/soundmanager.h" +#endif #include "moc_controller_mapping_validation_test.cpp" FakeMidiControllerJSProxy::FakeMidiControllerJSProxy() @@ -107,10 +121,73 @@ bool FakeController::isMappable() const { return false; } +#ifdef MIXXX_USE_QML +void deleteTrack(Track* pTrack) { + // Delete track objects directly in unit tests with + // no main event loop + delete pTrack; +}; +#endif + void LegacyControllerMappingValidationTest::SetUp() { m_mappingPath = QDir::current(); m_mappingPath.cd("res/controllers"); m_pEnumerator.reset(new MappingInfoEnumerator(QList{m_mappingPath.absolutePath()})); +#ifdef MIXXX_USE_QML + // This setup mirrors coreservices -- it would be nice if we could use coreservices instead + // but it does a lot of local disk / settings setup. + auto pChannelHandleFactory = std::make_shared(); + m_pEffectsManager = std::make_shared(m_pConfig, pChannelHandleFactory); + m_pEngine = std::make_shared( + m_pConfig, + "[Master]", + m_pEffectsManager.get(), + pChannelHandleFactory, + true); + m_pSoundManager = std::make_shared(m_pConfig, m_pEngine.get()); + m_pControlIndicatorTimer = std::make_shared(nullptr); + m_pEngine->registerNonEngineChannelSoundIO(m_pSoundManager.get()); + m_pPlayerManager = std::make_shared(m_pConfig, + m_pSoundManager.get(), + m_pEffectsManager.get(), + m_pEngine.get()); + + m_pPlayerManager->addConfiguredDecks(); + m_pPlayerManager->addSampler(); + PlayerInfo::create(); + m_pEffectsManager->setup(); + + const auto dbConnection = mixxx::DbConnectionPooled(dbConnectionPooler()); + if (!MixxxDb::initDatabaseSchema(dbConnection)) { + exit(1); + } + m_pTrackCollectionManager = std::make_shared( + nullptr, + m_pConfig, + dbConnectionPooler(), + deleteTrack); + + m_pRecordingManager = std::make_shared(m_pConfig, m_pEngine.get()); + CoverArtCache::createInstance(); + m_pLibrary = std::make_shared( + nullptr, + m_pConfig, + dbConnectionPooler(), + m_pTrackCollectionManager.get(), + m_pPlayerManager.get(), + m_pRecordingManager.get()); + + m_pPlayerManager->bindToLibrary(m_pLibrary.get()); + mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(m_pPlayerManager); + ControllerScriptEngineBase::registerTrackCollectionManager(m_pTrackCollectionManager); +} + +void LegacyControllerMappingValidationTest::TearDown() { + PlayerInfo::destroy(); + CoverArtCache::destroy(); + mixxx::qml::QmlPlayerManagerProxy::registerPlayerManager(nullptr); + ControllerScriptEngineBase::registerTrackCollectionManager(nullptr); +#endif } bool LegacyControllerMappingValidationTest::testLoadMapping(const MappingInfo& mapping) { @@ -123,7 +200,7 @@ bool LegacyControllerMappingValidationTest::testLoadMapping(const MappingInfo& m FakeController controller; controller.setMapping(pMapping); - bool result = controller.applyMapping(); + bool result = controller.applyMapping("./res"); controller.stopEngine(); return result; } diff --git a/src/test/controller_mapping_validation_test.h b/src/test/controller_mapping_validation_test.h index da868da44cb1..b2596c51051a 100644 --- a/src/test/controller_mapping_validation_test.h +++ b/src/test/controller_mapping_validation_test.h @@ -2,11 +2,14 @@ #include +#include "control/controlindicatortimer.h" #include "controllers/controller.h" #include "controllers/controllermappinginfoenumerator.h" #include "controllers/hid/legacyhidcontrollermapping.h" #include "controllers/midi/legacymidicontrollermapping.h" -#include "test/mixxxtest.h" +#include "library/trackcollectionmanager.h" +#include "test/mixxxdbtest.h" +#include "test/soundsourceproviderregistration.h" class FakeMidiControllerJSProxy : public ControllerJSProxy { Q_OBJECT @@ -136,9 +139,41 @@ class FakeController : public Controller { std::shared_ptr m_pHidMapping; }; -class LegacyControllerMappingValidationTest : public MixxxTest { +class EngineMixer; +class EffectsManager; +class SoundManager; +class RecordingManager; +class Library; +class PlayerManager; + +// We can't inherit from LibraryTest because that creates a key_notation control object that is also +// created by the Library object itself. The duplicated CO creation causes a debug assert. +class LegacyControllerMappingValidationTest : public MixxxDbTest, SoundSourceProviderRegistration { + public: + LegacyControllerMappingValidationTest() + : MixxxDbTest(true) { + } + protected: void SetUp() override; +#ifdef MIXXX_USE_QML + void TearDown() override; + + TrackPointer getOrAddTrackByLocation( + const QString& trackLocation) const { + return m_pTrackCollectionManager->getOrAddTrack( + TrackRef::fromFilePath(trackLocation)); + } + + std::shared_ptr m_pEffectsManager; + std::shared_ptr m_pControlIndicatorTimer; + std::shared_ptr m_pEngine; + std::shared_ptr m_pSoundManager; + std::shared_ptr m_pPlayerManager; + std::shared_ptr m_pTrackCollectionManager; + std::shared_ptr m_pRecordingManager; + std::shared_ptr m_pLibrary; +#endif bool testLoadMapping(const MappingInfo& mapping); diff --git a/src/test/controllerrenderingengine_test.cpp b/src/test/controllerrenderingengine_test.cpp new file mode 100644 index 000000000000..0a0a3c1b52ab --- /dev/null +++ b/src/test/controllerrenderingengine_test.cpp @@ -0,0 +1,49 @@ +#include "controllers/rendering/controllerrenderingengine.h" + +#include +#include + +#include +#include + +#include "controllers/legacycontrollermappingfilehandler.h" +#include "helpers/log_test.h" +#include "test/mixxxtest.h" + +using ::testing::_; + +class ControllerRenderingEngineTest : public MixxxTest { + public: + void SetUp() override { + mixxx::Time::setTestMode(true); + mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); + SETUP_LOG_CAPTURE(); + } + + QList supportedPixelFormat() const { + return LegacyControllerMappingFileHandler::kSupportedPixelFormat.values(); + } +}; + +class MockRenderingEngine : public ControllerRenderingEngine { + public: + MockRenderingEngine(const LegacyControllerMapping::ScreenInfo& info) + : ControllerRenderingEngine(info, nullptr){}; +}; + +TEST_F(ControllerRenderingEngineTest, createValidRendererWithSupportedTypes) { + for (auto pixelFormat : supportedPixelFormat()) { + MockRenderingEngine screenTest(LegacyControllerMapping::ScreenInfo( + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 10, // splash_off + pixelFormat, // pixelFormat + std::endian::big, // endian + false, // reversedColor + false // rawData + )); + EXPECT_TRUE(screenTest.isValid()); + EXPECT_TRUE(screenTest.stop()); + } +} diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index df55b0724129..475e7b60c4c0 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -1,25 +1,40 @@ #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" +#include +#include + #include #include #include #include +#include #include #include "control/controlobject.h" #include "control/controlpotmeter.h" +#ifdef MIXXX_USE_QML +#include + +#include "controllers/rendering/controllerrenderingengine.h" +#endif #include "controllers/softtakeover.h" +#include "helpers/log_test.h" #include "preferences/usersettings.h" #include "test/mixxxtest.h" #include "util/color/colorpalette.h" #include "util/time.h" +using ::testing::_; + typedef std::unique_ptr ScopedTemporaryFile; const RuntimeLoggingCategory logger(QString("test").toLocal8Bit()); -class ControllerScriptEngineLegacyTest : public MixxxTest { +class ControllerScriptEngineLegacyTest : public ControllerScriptEngineLegacy, public MixxxTest { protected: + ControllerScriptEngineLegacyTest() + : ControllerScriptEngineLegacy(nullptr, logger) { + } static ScopedTemporaryFile makeTemporaryFile(const QString& contents) { QByteArray contentsBa = contents.toLocal8Bit(); ScopedTemporaryFile pFile = std::make_unique(); @@ -33,21 +48,19 @@ class ControllerScriptEngineLegacyTest : public MixxxTest { mixxx::Time::setTestMode(true); mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); QThread::currentThread()->setObjectName("Main"); - cEngine = new ControllerScriptEngineLegacy(nullptr, logger); - cEngine->initialize(); + initialize(); } void TearDown() override { - delete cEngine; mixxx::Time::setTestMode(false); } bool evaluateScriptFile(const QFileInfo& scriptFile) { - return cEngine->evaluateScriptFile(scriptFile); + return ControllerScriptEngineLegacy::evaluateScriptFile(scriptFile); } QJSValue evaluate(const QString& code) { - return cEngine->jsEngine()->evaluate(code); + return jsEngine()->evaluate(code); } bool evaluateAndAssert(const QString& code) { @@ -65,7 +78,31 @@ class ControllerScriptEngineLegacyTest : public MixxxTest { application()->processEvents(); } - ControllerScriptEngineLegacy* cEngine; +#ifdef MIXXX_USE_QML + QHash& transformScreenFrameFunctions() { + return m_transformScreenFrameFunctions; + } + + QHash>& renderingScreens() { + return m_renderingScreens; + } + + QHash>& rootItems() { + return m_rootItems; + } + + void testHandleScreen( + const LegacyControllerMapping::ScreenInfo& screeninfo, + const QImage& frame, + const QDateTime& timestamp) { + handleScreenFrame(screeninfo, frame, timestamp); + } + + TransformScreenFrameFunction newTransformScreenFrameFunction( + QMetaMethod method, bool typed) const { + return TransformScreenFrameFunction{method, typed}; + } +#endif }; TEST_F(ControllerScriptEngineLegacyTest, commonScriptHasNoErrors) { @@ -620,3 +657,85 @@ TEST_F(ControllerScriptEngineLegacyTest, connectionExecutesWithCorrectThisObject // The counter should have been incremented exactly once. EXPECT_DOUBLE_EQ(1.0, pass->get()); } + +#ifdef MIXXX_USE_QML +class MockScreenRender : public ControllerRenderingEngine { + public: + MockScreenRender(const LegacyControllerMapping::ScreenInfo& info) + : ControllerRenderingEngine(info, nullptr){}; + MOCK_METHOD(void, requestSend, (Controller * controller, const QByteArray& frame), (override)); +}; + +TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { + SETUP_LOG_CAPTURE(); + LegacyControllerMapping::ScreenInfo dummyScreen( + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 10, // splash_off + QImage::Format_RGB16, // pixelFormat + std::endian::big, // endian + false, // rawData + false // reversedColor + ); + QImage dummyFrame; + // Allocate screen on the heap as it need to outlive the this function, + // since the engine will take ownership of it + std::shared_ptr pDummyRender = + std::make_shared(dummyScreen); + EXPECT_CALL(*pDummyRender, requestSend(_, _)).Times(0); + EXPECT_LOG_MSG(QtWarningMsg, + "Could not find a valid transform function but the screen doesn't " + "accept raw data. Aborting screen rendering."); + + transformScreenFrameFunctions().insert( + dummyScreen.identifier, + newTransformScreenFrameFunction( + QMetaMethod(), + false)); + renderingScreens().insert(dummyScreen.identifier, pDummyRender); + rootItems().insert(dummyScreen.identifier, std::make_shared()); + + testHandleScreen( + dummyScreen, + dummyFrame, + QDateTime::currentDateTime()); + + ASSERT_ALL_EXPECTED_MSG(); +} + +TEST_F(ControllerScriptEngineLegacyTest, screenWillSentRawDataIfConfigured) { + SETUP_LOG_CAPTURE(); + LegacyControllerMapping::ScreenInfo dummyScreen( + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 10, // splash_off + QImage::Format_RGB16, // pixelFormat + std::endian::big, // endian + false, // reversedColor + true // rawData + ); + QImage dummyFrame; + // Allocate screen on the heap as it need to outlive the this function, + // since the engine will take ownership of it + std::shared_ptr pDummyRender = + std::make_shared(dummyScreen); + EXPECT_CALL(*pDummyRender, requestSend(_, QByteArray())); + + transformScreenFrameFunctions().insert( + dummyScreen.identifier, + newTransformScreenFrameFunction( + QMetaMethod(), + false)); + renderingScreens().insert(dummyScreen.identifier, pDummyRender); + rootItems().insert(dummyScreen.identifier, std::make_shared()); + + testHandleScreen( + dummyScreen, + dummyFrame, + QDateTime::currentDateTime()); + + ASSERT_ALL_EXPECTED_MSG(); +} +#endif diff --git a/src/test/helpers/log_test.cpp b/src/test/helpers/log_test.cpp new file mode 100644 index 000000000000..63acdbdfeb1d --- /dev/null +++ b/src/test/helpers/log_test.cpp @@ -0,0 +1,22 @@ +#include "test/helpers/log_test.h" + +QList> logMessagesExpected; + +void logCapture(QtMsgType msgType, const QMessageLogContext&, const QString& msg) { + for (int i = 0; i < logMessagesExpected.size(); i++) { + if (std::get(logMessagesExpected[i]) == msgType && + std::get(logMessagesExpected[i]) + .match(msg) + .hasMatch()) { + logMessagesExpected.removeAt(i); + return; + } + } + // Unexpected info or debug message aren't considered as a failure + if (msgType < QtMsgType::QtWarningMsg) + return; + QString errMsg("Got an unexpected log message: \n\t"); + QDebug strm(&errMsg); + strm << msgType << msg; + FAIL() << errMsg.toStdString(); +} diff --git a/src/test/helpers/log_test.h b/src/test/helpers/log_test.h new file mode 100644 index 000000000000..10f3cbb5936c --- /dev/null +++ b/src/test/helpers/log_test.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#define SETUP_LOG_CAPTURE() \ + qInstallMessageHandler(logCapture) + +#define ASSERT_ALL_EXPECTED_MSG() \ + if (!logMessagesExpected.isEmpty()) { \ + QString errMsg; \ + QDebug strm(&errMsg); \ + strm << logMessagesExpected.size() << "expected log messages didn't occur: \n"; \ + for (const auto& msg : std::as_const(logMessagesExpected)) \ + strm << "\t" << std::get(msg) << std::get(msg) << "\n"; \ + FAIL() << errMsg.toStdString(); \ + } else \ + logMessagesExpected.clear(); + +#define EXPECT_LOG_MSG(type, exp) \ + logMessagesExpected.push_back(std::make_tuple(type, QRegularExpression(exp))) + +extern QList> logMessagesExpected; +void logCapture(QtMsgType msgType, const QMessageLogContext&, const QString& msg); diff --git a/src/util/cmdlineargs.cpp b/src/util/cmdlineargs.cpp index 328e07022820..89ee6db84043 100644 --- a/src/util/cmdlineargs.cpp +++ b/src/util/cmdlineargs.cpp @@ -315,6 +315,12 @@ bool CmdlineArgs::parse(const QStringList& arguments, CmdlineArgs::ParseMode mod "you specify will be loaded into the next virtual deck.") : QString()); + const QCommandLineOption controllerPreviewScreens(QStringLiteral("controller-preview-screens"), + forUserFeedback ? QCoreApplication::translate("CmdlineArgs", + "Preview rendered controller screens in the Setting windows.") + : QString()); + parser.addOption(controllerPreviewScreens); + if (forUserFeedback) { // We know form the first path, that there will be likely an error message, check again. // This is not the case if the user uses a Qt internal option that is unknown @@ -383,6 +389,7 @@ bool CmdlineArgs::parse(const QStringList& arguments, CmdlineArgs::ParseMode mod m_useLegacyVuMeter = parser.isSet(enableLegacyVuMeter); m_useLegacySpinny = parser.isSet(enableLegacySpinny); m_controllerDebug = parser.isSet(controllerDebug) || parser.isSet(controllerDebugDeprecated); + m_controllerPreviewScreens = parser.isSet(controllerPreviewScreens); m_controllerAbortOnWarning = parser.isSet(controllerAbortOnWarning); m_developer = parser.isSet(developer); #ifdef MIXXX_USE_QML diff --git a/src/util/cmdlineargs.h b/src/util/cmdlineargs.h index c31b91ba6709..5032d2ccb18c 100644 --- a/src/util/cmdlineargs.h +++ b/src/util/cmdlineargs.h @@ -36,6 +36,9 @@ class CmdlineArgs final { bool getControllerDebug() const { return m_controllerDebug; } + bool getControllerPreviewScreens() const { + return m_controllerPreviewScreens; + } bool getControllerAbortOnWarning() const { return m_controllerAbortOnWarning; } @@ -87,6 +90,7 @@ class CmdlineArgs final { bool m_startInFullscreen; // Start in fullscreen mode bool m_startAutoDJ; bool m_controllerDebug; + bool m_controllerPreviewScreens; bool m_controllerAbortOnWarning; // Controller Engine will be stricter bool m_developer; // Developer Mode #ifdef MIXXX_USE_QML diff --git a/tools/README b/tools/README index 8aa1a4a36ea0..79ba807ac723 100644 --- a/tools/README +++ b/tools/README @@ -1,2 +1,22 @@ This directory is for developer helper scripts, like those used to automate the creation of controller layouts. + + +## Dummy HID Device (Linux only) + +If you ever need a dummy HID device to test mapping capabilities or introspect reports set by a mapping for example, you can build a dummy one using `dummy_hid_device.cpp `. Note that you will need `uhid` to use this daemon. + +Here is how to get the daemon setup. Make sure to do this **before** Mixxx is started + +```sh +# Enable UHID +sudo modprobe uhid +# This next command assumes you are running it from the Mixxx directory +# Optionally, you can pass: +# `-DVENDOR_ID=0x1234` to customize vendor ID +# `-DPRODUCT_ID=0x1234` to customize product ID +# `-DDEVICE_NAME="My Device"` to customize the device name +cd build && gcc ../tools/dummy_hid_device.cpp -lhidapi-hidraw -o dummy_hid_device && sudo ./dummy_hid_device +# Allow the created hidraw device to be accesssed by the user. You may also set the write udev rules. Finally, you can also run Mixxx as root, but that's not recommended. +sudo chown "$USER" "$(ls -1t /dev/hidraw* | head -n 1)" +``` diff --git a/tools/dummy_hid_device.cpp b/tools/dummy_hid_device.cpp new file mode 100644 index 000000000000..42277f459841 --- /dev/null +++ b/tools/dummy_hid_device.cpp @@ -0,0 +1,253 @@ +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef VENDOR_ID +#define VENDOR_ID 0xDEAD +#endif +#ifndef PRODUCT_ID +#define PRODUCT_ID 0xBEAF +#endif +#ifndef DEVICE_NAME +#define DEVICE_NAME "test-uhid-device" +#endif + +static volatile uint32_t done; + +static unsigned char rdesc[] = { + 0x06, 0x02, 0xFF, /* Usage Page (FF02h), */ + 0x09, + 0x00, /* Usage (00h), */ + 0xA1, + 0x01, /* Collection (Application), */ + 0x09, + 0x01, /* Usage (01h), */ + 0xA1, + 0x02, /* Collection (Logical), */ + 0x85, + 0x01, /* Report ID (1), */ + 0x09, + 0x02, /* Usage (02h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x01, /* Logical Maximum (1), */ + 0x75, + 0x01, /* Report Size (1), */ + 0x95, + 0x88, /* Report Count (136), */ + 0x81, + 0x02, /* Input (Variable), */ + 0x09, + 0x07, /* Usage (07h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x01, /* Logical Maximum (1), */ + 0x75, + 0x01, /* Report Size (1), */ + 0x95, + 0x10, /* Report Count (16), */ + 0x81, + 0x02, /* Input (Variable), */ + 0x09, + 0x03, /* Usage (03h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x0F, /* Logical Maximum (15), */ + 0x75, + 0x04, /* Report Size (4), */ + 0x95, + 0x06, /* Report Count (6), */ + 0x81, + 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +void sighndlr(int signal) { + done = 1; + printf("\n"); +} + +static int uhid_write(int fd, const struct uhid_event* ev) { + ssize_t ret; + + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %m\n"); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", ret, sizeof(ev)); + return -EFAULT; + } else { + return 0; + } +} + +static int create(int fd) { + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char*)ev.u.create.name, DEVICE_NAME); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = VENDOR_ID; + ev.u.create.product = PRODUCT_ID; + ev.u.create.version = 0; + ev.u.create.country = 0; + + return uhid_write(fd, &ev); +} + +static void destroy(int fd) { + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +/* This parses raw output reports sent by the kernel to the device. A normal + * uhid program shouldn't do this but instead just forward the raw report. + * However, for ducomentational purposes, we try to detect LED events here and + * print debug messages for it. */ +static void handle_output(struct uhid_event* ev) { + if (ev->u.output.rtype != UHID_OUTPUT_REPORT) + return; + + fprintf(stderr, + "Output received of type %d and size %d: %s\n", + ev->u.output.rtype, + ev->u.output.size, + ev->u.output.data); + for (int i = 0; i < ev->u.output.size; i++) { + printf("%02X ", ev->u.output.data[i]); + } + printf("\n"); + return; +} +static void handle_report(struct uhid_event* ev, int fd) { + fprintf(stderr, "RType is %d\n", ev->u.get_report.rtype); + if (ev->u.get_report.rtype != UHID_START) + return; + + fprintf(stderr, "Get report for %d\n", ev->u.get_report.rnum); + + struct uhid_event rep; + + memset(&rep, 0, sizeof(rep)); + rep.type = UHID_GET_REPORT_REPLY; + + rep.u.get_report_reply.id = ev->u.get_report.id; + rep.u.get_report_reply.err = 0; + rep.u.get_report_reply.size = 255; + memset(rep.u.get_report_reply.data, 0, UHID_DATA_MAX); + + uhid_write(fd, &rep); + return; +} + +static int event(int fd) { + struct uhid_event ev; + ssize_t ret; + + memset(&ev, 0, sizeof(ev)); + ret = read(fd, &ev, sizeof(ev)); + if (ret == 0) { + fprintf(stderr, "Read HUP on uhid-cdev\n"); + return -EFAULT; + } else if (ret < 0) { + fprintf(stderr, "Cannot read uhid-cdev: %m\n"); + return -errno; + } else if (ret != sizeof(ev)) { + fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", ret, sizeof(ev)); + return -EFAULT; + } + + switch (ev.type) { + case UHID_START: + fprintf(stderr, "UHID_START from uhid-dev\n"); + break; + case UHID_STOP: + fprintf(stderr, "UHID_STOP from uhid-dev\n"); + break; + case UHID_OPEN: + fprintf(stderr, "UHID_OPEN from uhid-dev\n"); + break; + case UHID_CLOSE: + fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); + case UHID_OUTPUT: + fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); + handle_output(&ev); + break; + case UHID_OUTPUT_EV: + fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n"); + break; + case UHID_GET_REPORT: + fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + handle_report(&ev, fd); + break; + default: + fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); + } + + return 0; +} + +int main(int argc, char** argv) { + signal(SIGINT, sighndlr); + + // UHID + int fd = open("/dev/uhid", O_RDWR); + if (fd < 0) { + perror("open"); + return -1; + } + + fprintf(stderr, "Create uhid device\n"); + if (create(fd)) { + close(fd); + return EXIT_FAILURE; + } + + struct pollfd pfds; + pfds.fd = fd; + pfds.events = POLLIN; + + while (!done) { + int ret = poll(&pfds, 1, 10); + if (ret < 0) { + fprintf(stderr, "Cannot poll for fds: %m\n"); + break; + } + if (pfds.revents & POLLHUP) { + fprintf(stderr, "Received HUP on uhid-cdev\n"); + break; + } + if (pfds.revents & POLLIN) { + ret = event(fd); + if (ret) + break; + } + } + + destroy(fd); + + return 0; +} From ba4d030b47bfa4b9e8c2ab1913bfc7029fca0df2 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 6 Apr 2024 21:32:46 +0100 Subject: [PATCH 02/26] Adding some more inline doc and addressing clang-tidy warnings --- src/controllers/controllerscreenpreview.cpp | 21 ++- src/controllers/controllerscreenpreview.h | 15 ++- src/controllers/dlgprefcontroller.cpp | 11 +- src/controllers/dlgprefcontrollerdlg.ui | 109 --------------- src/controllers/legacycontrollermapping.h | 18 ++- .../legacycontrollermappingfilehandler.cpp | 44 +++--- .../rendering/controllerrenderingengine.cpp | 79 ++++++----- .../scripting/controllerscriptenginebase.cpp | 12 +- .../legacy/controllerscriptenginelegacy.cpp | 126 ++++++++++-------- .../legacy/controllerscriptenginelegacy.h | 3 - 10 files changed, 183 insertions(+), 255 deletions(-) diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp index b9b1cd475bd5..a1ab7da6a33f 100644 --- a/src/controllers/controllerscreenpreview.cpp +++ b/src/controllers/controllerscreenpreview.cpp @@ -13,14 +13,13 @@ ControllerScreenPreview::ControllerScreenPreview( m_pFrame(make_parented(this)), m_pStat(make_parented("- FPS", this)), m_frameDurationHistoryIdx(0), - m_lastFrameTimespamp(mixxx::Time::elapsed()) { - size_t frameDurationHistoryLenght = sizeof(m_frameDurationHistory) / sizeof(uint); - memset(m_frameDurationHistory, 0, frameDurationHistoryLenght); + m_lastFrameTimestamp(mixxx::Time::elapsed()) { + std::fill(m_frameDurationHistory.begin(), m_frameDurationHistory.end(), 0); m_pFrame->setFixedSize(screen.size); setMaximumWidth(screen.size.width()); m_pStat->setAlignment(Qt::AlignRight); auto pLayout = make_parented(this); - auto pBottomLayout = new QHBoxLayout(); + auto* pBottomLayout = new QHBoxLayout(); pLayout->addWidget(m_pFrame); pBottomLayout->addWidget(make_parented( QString("\"%0\"") @@ -38,17 +37,17 @@ void ControllerScreenPreview::updateFrame( if (m_screenInfo.identifier != screen.identifier) { return; } - size_t frameDurationHistoryLenght = sizeof(m_frameDurationHistory) / sizeof(uint); + size_t frameDurationHistoryLength = sizeof(m_frameDurationHistory) / sizeof(uint); auto currentTimestamp = mixxx::Time::elapsed(); m_frameDurationHistory[m_frameDurationHistoryIdx++] = - (currentTimestamp - m_lastFrameTimespamp).toIntegerMillis(); - m_frameDurationHistoryIdx %= frameDurationHistoryLenght; + (currentTimestamp - m_lastFrameTimestamp).toIntegerMillis(); + m_frameDurationHistoryIdx %= frameDurationHistoryLength; double durationSinceLastFrame = 0.0; - for (uint8_t i = 0; i < frameDurationHistoryLenght; i++) { - durationSinceLastFrame += (double)m_frameDurationHistory[i]; + for (uint i = 0; i < frameDurationHistoryLength; i++) { + durationSinceLastFrame += static_cast(m_frameDurationHistory[i]); } - durationSinceLastFrame /= (double)frameDurationHistoryLenght; + durationSinceLastFrame /= (double)frameDurationHistoryLength; if (durationSinceLastFrame > 0.0) { m_pStat->setText(QString("FPS: %0/%1") @@ -56,5 +55,5 @@ void ControllerScreenPreview::updateFrame( .arg(m_screenInfo.target_fps)); } m_pFrame->setPixmap(QPixmap::fromImage(frame)); - m_lastFrameTimespamp = currentTimestamp; + m_lastFrameTimestamp = currentTimestamp; } diff --git a/src/controllers/controllerscreenpreview.h b/src/controllers/controllerscreenpreview.h index eaa16840240e..c11ae94ad860 100644 --- a/src/controllers/controllerscreenpreview.h +++ b/src/controllers/controllerscreenpreview.h @@ -2,14 +2,21 @@ #include #include +#include #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "util/duration.h" #include "util/parented_ptr.h" -#define CONTROLLER_SCREEN_PREVIEW_FRAME_HISTORY_SIZE 5 +/// Number of sample frame timestamp sample to calculate FPS label. +constexpr int kFrameHistorySize = 5; -/// Widget to preview controller screen +/// Widget to preview controller screen, used in preference window. This is +/// useful to help when developing new screen layout, without inducing any wear +/// and tear on a hardware device, or allow testing when not owning a device +/// using onboard screens. This can also be used to provide debugging +/// information as user can easily take a screenshot of what they see on the +/// device. class ControllerScreenPreview : public QWidget { Q_OBJECT public: @@ -24,7 +31,7 @@ class ControllerScreenPreview : public QWidget { parented_ptr m_pFrame; parented_ptr m_pStat; uint8_t m_frameDurationHistoryIdx; - uint m_frameDurationHistory[CONTROLLER_SCREEN_PREVIEW_FRAME_HISTORY_SIZE]; + std::array m_frameDurationHistory; - mixxx::Duration m_lastFrameTimespamp; + mixxx::Duration m_lastFrameTimestamp; }; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 387f7bb50902..b056d412aad1 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -434,7 +434,7 @@ QString DlgPrefController::mappingFileLinks( qmlLibrary.dirinfo.absoluteFilePath()); if (!qmlLibrary.dirinfo.exists()) { scriptFileLink += - QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); + tr(" (missing)"); } else if (qmlLibrary.dirinfo.absoluteFilePath().startsWith( systemMappingPath)) { scriptFileLink += builtinFileSuffix; @@ -873,13 +873,18 @@ void DlgPrefController::initTableView(QTableView* pTable) { #ifdef MIXXX_USE_QML void DlgPrefController::slotShowPreviewScreens( std::shared_ptr scriptEngine) { - qDeleteAll(m_ui.groupBoxScreens->findChildren("", Qt::FindDirectChildrenOnly)); + QLayoutItem* pItem; + while ((pItem = m_ui.groupBoxScreens->layout()->takeAt(0)) != nullptr) { + delete pItem->widget(); + delete pItem; + } if (!m_pMapping) { return; } - m_ui.groupBoxScreens->setVisible(scriptEngine != nullptr); + m_ui.groupBoxScreens->setVisible( + scriptEngine != nullptr && !m_pMapping->getInfoScreens().empty()); if (!scriptEngine) { return; } diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index e7e80bdc73c0..9e83f1b5cab9 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -324,66 +324,6 @@ - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - true - - - - 0 - 0 - - - - - - - (device category goes here) - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Load Mapping: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - comboBoxMapping - - - @@ -406,32 +346,6 @@ - - - - Click to start the Controller Learning wizard. - - - - - - Learning Wizard (MIDI Only) - - - false - - - false - - - - - - - Enabled - - - @@ -540,29 +454,6 @@ - - - - true - - - - 0 - 0 - - - - - 14 - 75 - true - - - - Controller Name - - - diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 1ea5e844379c..62f8c61f88ef 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -42,7 +42,13 @@ class LegacyControllerMapping { m_settingsLayout(other.m_settingsLayout.get() != nullptr ? other.m_settingsLayout->clone() : nullptr), +#ifdef MIXXX_USE_QML + m_scripts(other.m_scripts), + m_modules(other.m_modules), + m_screens(other.m_screens) { +#else m_scripts(other.m_scripts) { +#endif } virtual ~LegacyControllerMapping() = default; @@ -231,12 +237,12 @@ class LegacyControllerMapping { /// @param rawData whether or not the screen is allowed to reserve bare data, not transformed virtual void addScreenInfo(const QString& identifier, const QSize& size, - uint targetFps = 30, - uint splashoff = 50, - QImage::Format pixelFormat = QImage::Format_RGB32, - std::endian endian = std::endian::little, - bool reversedColor = false, - bool rawData = false) { + uint targetFps, + uint splashoff, + QImage::Format pixelFormat, + std::endian endian, + bool reversedColor, + bool rawData) { m_screens.append(ScreenInfo(identifier, size, targetFps, diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 5fc958638896..fd7353a7e8cc 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -4,6 +4,7 @@ #include "controllers/defs_controllers.h" #include "controllers/midi/legacymidicontrollermappingfilehandler.h" +#include "util/logger.h" #include "util/xml.h" #ifdef __HID__ @@ -23,6 +24,7 @@ QMap LegacyControllerMappingFileHandler::kEndianFormat = { }; #endif namespace { +const mixxx::Logger kLogger("LegacyControllerMappingFileHandler"); #ifdef MIXXX_USE_QML @@ -47,6 +49,14 @@ QFileInfo findLibraryPath( } return dir; } + +/// @brief Parse a string that contain a boolean value in human representation +/// @param value the string containing the boolean setting +/// @return true for string value "yes", "true" and "1", false otherwise +bool parseHumanBoolean(const QString& value) { + return value == QStringLiteral("yes") || value == QStringLiteral("true") || + value == QStringLiteral("1"); +} #endif /// Find script file in the mapping or system path. @@ -284,7 +294,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( file, LegacyControllerMapping::ScriptFileInfo::Type::QML); #else - qWarning() << "Unsupported render scene. Mixxx isn't built with QML support"; + kLogger.warning() << "Unsupported render scene. Mixxx isn't built with QML support"; return; #endif } else { @@ -308,31 +318,33 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( uint targetFps = screen.attribute("targetFps", "30").toUInt(); QString pixelFormatName = screen.attribute("pixelType", "RBG"); QString endianName = screen.attribute("endian", "little"); - QString reversedColor = screen.attribute("reversed", "false").toLower(); - QString rawData = screen.attribute("raw", "false").toLower(); + QString reversedColor = screen.attribute("reversed", "false").toLower().trimmed(); + QString rawData = screen.attribute("raw", "false").toLower().trimmed(); uint splashOff = screen.attribute("splashoff", "0").toUInt(); if (!targetFps || targetFps > s_maxTargetFps) { - qWarning() << "Invalid target FPS. Target FPS must be between 1 and" << s_maxTargetFps; + kLogger.warning() + << "Invalid target FPS. Target FPS must be between 1 and" + << s_maxTargetFps; return; } if (splashOff > s_maxSplashOffDuration) { - qWarning() << QString( + kLogger.warning() << QString( "Invalid splashoff duration. Splashoff duration must be " "between 0 and %1. Clamping to %2") - .arg(s_maxSplashOffDuration) - .arg(s_maxSplashOffDuration); + .arg(s_maxSplashOffDuration) + .arg(s_maxSplashOffDuration); splashOff = s_maxSplashOffDuration; } if (!kSupportedPixelFormat.contains(pixelFormatName)) { - qWarning() << "Unsupported pixel format" << pixelFormatName; + kLogger.warning() << "Unsupported pixel format" << pixelFormatName; return; } if (!kEndianFormat.contains(endianName)) { - qWarning() << "Unknown endiant format" << endianName; + kLogger.warning() << "Unknown endian format" << endianName; return; } @@ -343,20 +355,20 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( uint height = screen.attribute("height", "0").toUInt(); if (!width || !height) { - qWarning() << "Invalid screen size. Screen size must have a width " - "and height above 1 pixel"; + kLogger.warning() << "Invalid screen size. Screen size must have a width " + "and height above 1 pixel"; return; } - qDebug() << "Adding screen " << identifier; + kLogger.debug() << "Adding screen " << identifier; mapping->addScreenInfo(identifier, QSize(width, height), targetFps, splashOff, pixelFormat, endian, - reversedColor == "yes" || reversedColor == "true" || reversedColor == "1", - rawData == "yes" || rawData == "true" || rawData == "1"); + parseHumanBoolean(reversedColor), + parseHumanBoolean(rawData)); screen = screen.nextSiblingElement("screen"); } // Build a list of QML files to load @@ -367,7 +379,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( while (!qmlLibrary.isNull()) { QString libFilename = qmlLibrary.attribute("path", ""); QFileInfo path = findLibraryPath(mapping, libFilename, systemMappingsPath); - qDebug() << "Adding QML directory " << libFilename; + kLogger.debug() << "Adding QML directory " << libFilename; mapping->addLibraryDirectory(path); qmlLibrary = qmlLibrary.nextSiblingElement("library"); } @@ -454,7 +466,7 @@ QDomDocument LegacyControllerMappingFileHandler::buildRootWithScripts( if (script.builtin) { continue; } - qDebug() << "writing script block for" << filename; + kLogger.debug() << "writing script block for" << filename; QString functionPrefix = script.identifier; QDomElement scriptFile = doc.createElement("file"); diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 089ac342f602..ae7041d424bb 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -17,8 +17,13 @@ #include "moc_controllerrenderingengine.cpp" #include "qml/qmlwaveformoverview.h" #include "util/cmdlineargs.h" +#include "util/logger.h" #include "util/time.h" +namespace { +const mixxx::Logger kLogger("ControllerRenderingEngine"); +} // anonymous namespace + QMutex ControllerRenderingEngine::s_glMutex = QMutex(); ControllerRenderingEngine::ControllerRenderingEngine( @@ -50,8 +55,9 @@ ControllerRenderingEngine::ControllerRenderingEngine( DEBUG_ASSERT(!"Unsupported format"); } - if (!m_isValid) + if (!m_isValid) { return; + } prepare(); } @@ -79,15 +85,15 @@ void ControllerRenderingEngine::prepare() { ControllerRenderingEngine::~ControllerRenderingEngine() { VERIFY_OR_DEBUG_ASSERT(!m_fbo) { - qWarning() << "The ControllerEngine is being deleted but hasn't been " - "cleaned up. Brace for impact"; + kLogger.warning() << "The ControllerEngine is being deleted but hasn't been " + "cleaned up. Brace for impact"; }; } void ControllerRenderingEngine::start() { VERIFY_OR_DEBUG_ASSERT(!thread()->isFinished() && !thread()->isInterruptionRequested()) { - qWarning() << "Render thread has or ir about to terminate. Cannot " - "start this render anymore."; + kLogger.warning() << "Render thread has or ir about to terminate. Cannot " + "start this render anymore."; return; } QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); @@ -99,8 +105,8 @@ bool ControllerRenderingEngine::isRunning() const { void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngine) { m_isValid = false; VERIFY_OR_DEBUG_ASSERT(QThread::currentThread() != thread()) { - qWarning() << "Unable to setup OpenGL rendering context from the same " - "thread as the render object"; + kLogger.warning() << "Unable to setup OpenGL rendering context from the same " + "thread as the render object"; return; } emit setupRequested(qmlEngine); @@ -129,7 +135,7 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { m_context = std::make_unique(); m_context->setFormat(format); VERIFY_OR_DEBUG_ASSERT(m_context->create()) { - qWarning() << "Unable to initialize controller screen rendering. Giving up"; + kLogger.warning() << "Unable to initialize controller screen rendering. Giving up"; m_waitCondition.wakeAll(); return; } @@ -150,8 +156,8 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { // This invocation will block the current thread! Qt::BlockingQueuedConnection) && m_offscreenSurface->isValid()) { - qWarning() << "Unable to create the OffscreenSurface for controller " - "screen rendering. Giving up"; + kLogger.warning() << "Unable to create the OffscreenSurface for controller " + "screen rendering. Giving up"; m_offscreenSurface.reset(); m_waitCondition.wakeAll(); return; @@ -160,8 +166,9 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { m_renderControl = std::make_unique(this); m_quickWindow = std::make_unique(m_renderControl.get()); - if (!qmlEngine->incubationController()) + if (!qmlEngine->incubationController()) { qmlEngine->setIncubationController(m_quickWindow->incubationController()); + } m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height()); @@ -201,16 +208,18 @@ void ControllerRenderingEngine::finish() { } void ControllerRenderingEngine::renderFrame() { - if (!m_isValid) + if (!m_isValid) { + DEBUG_ASSERT(!"Trying to render frame on an invalid engine"); return; + } VERIFY_OR_DEBUG_ASSERT(m_offscreenSurface->isValid()) { - qWarning() << "OffscreenSurface isn't valid anymore."; + kLogger.warning() << "OffscreenSurface isn't valid anymore."; finish(); return; }; VERIFY_OR_DEBUG_ASSERT(m_context->isValid()) { - qWarning() << "GLContext isn't valid anymore."; + kLogger.warning() << "GLContext isn't valid anymore."; finish(); return; }; @@ -218,7 +227,7 @@ void ControllerRenderingEngine::renderFrame() { auto lock = lockMutex(&s_glMutex); VERIFY_OR_DEBUG_ASSERT(m_context->makeCurrent(m_offscreenSurface.get())) { - qWarning() << "Couldn't make the GLContext current to the OffscreenSurface."; + kLogger.warning() << "Couldn't make the GLContext current to the OffscreenSurface."; lock.unlock(); finish(); return; @@ -226,7 +235,7 @@ void ControllerRenderingEngine::renderFrame() { if (!m_fbo) { VERIFY_OR_DEBUG_ASSERT(QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) { - qWarning() << "OpenGL doesn't support FBO"; + kLogger.warning() << "OpenGL doesn't support FBO"; lock.unlock(); finish(); return; @@ -239,14 +248,14 @@ void ControllerRenderingEngine::renderFrame() { glError = m_context->functions()->glGetError(); VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - qWarning() << "GLError: " << glError; + kLogger.warning() << "GLError: " << glError; lock.unlock(); finish(); return; }; VERIFY_OR_DEBUG_ASSERT(m_fbo->isValid()) { - qWarning() << "Failed to initialize FBO"; + kLogger.warning() << "Failed to initialize FBO"; lock.unlock(); finish(); return; @@ -255,7 +264,7 @@ void ControllerRenderingEngine::renderFrame() { m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(m_context.get())); VERIFY_OR_DEBUG_ASSERT(m_renderControl->initialize()) { - qWarning() << "Failed to initialize redirected Qt Quick rendering"; + kLogger.warning() << "Failed to initialize redirected Qt Quick rendering"; lock.unlock(); finish(); return; @@ -278,8 +287,7 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->polishItems(); VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { - qWarning() << "Couldn't sync the render control."; - // m_waitCondition.wakeAll(); + kLogger.warning() << "Couldn't sync the render control."; lock.unlock(); finish(); if (m_pControllerEngine) { @@ -295,13 +303,13 @@ void ControllerRenderingEngine::renderFrame() { QImage fboImage(m_screenInfo.size, m_screenInfo.pixelFormat); VERIFY_OR_DEBUG_ASSERT(m_fbo->bind()) { - qWarning() << "Couldn't bind the FBO."; + kLogger.warning() << "Couldn't bind the FBO."; } GLenum glError; m_context->functions()->glFlush(); glError = m_context->functions()->glGetError(); VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - qWarning() << "GLError: " << glError; + kLogger.warning() << "GLError: " << glError; lock.unlock(); finish(); return; @@ -311,7 +319,7 @@ void ControllerRenderingEngine::renderFrame() { } glError = m_context->functions()->glGetError(); VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - qWarning() << "GLError: " << glError; + kLogger.warning() << "GLError: " << glError; lock.unlock(); finish(); return; @@ -321,8 +329,9 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->render(); m_renderControl->endFrame(); - while (m_context->functions()->glGetError()) - ; + // Flush any remaining GL errors + while (m_context->functions()->glGetError()) { + } m_context->functions()->glReadPixels(0, 0, m_screenInfo.size.width(), @@ -332,16 +341,16 @@ void ControllerRenderingEngine::renderFrame() { fboImage.bits()); glError = m_context->functions()->glGetError(); VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - qWarning() << "GLError: " << glError; + kLogger.warning() << "GLError: " << glError; lock.unlock(); finish(); return; } VERIFY_OR_DEBUG_ASSERT(!fboImage.isNull()) { - qWarning() << "Screen frame is null!"; + kLogger.warning() << "Screen frame is null!"; } VERIFY_OR_DEBUG_ASSERT(m_fbo->release()) { - qDebug() << "Couldn't release the FBO."; + kLogger.debug() << "Couldn't release the FBO."; } fboImage.mirror(false, true); @@ -364,9 +373,9 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f if (CmdlineArgs::Instance() .getControllerDebug()) { auto endOfRender = mixxx::Time::elapsed(); - qDebug() << "Fame took " - << (endOfRender - m_nextFrameStart).formatMillisWithUnit() - << " and frame has" << frame.size() << "bytes"; + kLogger.debug() << "Fame took " + << (endOfRender - m_nextFrameStart).formatMillisWithUnit() + << " and frame has" << frame.size() << "bytes"; } m_nextFrameStart += mixxx::Duration::fromSeconds(1.0 / (double)m_screenInfo.target_fps); @@ -377,9 +386,9 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f if (msecToWaitBeforeFrame > 0) { if (CmdlineArgs::Instance() .getControllerDebug()) { - qDebug() << "Waiting for " - << durationToWaitBeforeFrame.formatMillisWithUnit() - << " before rendering next frame"; + kLogger.debug() << "Waiting for " + << durationToWaitBeforeFrame.formatMillisWithUnit() + << " before rendering next frame"; } QTimer::singleShot(msecToWaitBeforeFrame, Qt::PreciseTimer, diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 9a6d3ca7c916..899078c23b7c 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -68,7 +68,7 @@ bool ControllerScriptEngineBase::initialize() { pImageProvider); } else { DEBUG_ASSERT(!"TrackCollectionManager is missing"); - qWarning() << "TrackCollectionManager hasn't been registered yet"; + qCWarning(m_logger) << "TrackCollectionManager hasn't been registered yet"; } connect(pQmlEngine.get(), &QQmlEngine::warnings, @@ -202,22 +202,18 @@ bool ControllerScriptEngineBase::pause() { if (m_canPause && !m_isPaused) { emit pauseRequested(); } - // qDebug() << "Pause requested by" << QThread::currentThread(); while (m_canPause && !m_isPaused) { if (!m_isPausedCondition.wait(&m_pauseMutex, 1000)) { - qWarning() << "Pause request timed out!"; + qCWarning(m_logger) << "Pause request timed out!"; return false; } } - // qDebug() << "Pause granted to" << QThread::currentThread(); return !m_canPause || m_isPaused; } void ControllerScriptEngineBase::resume() { const auto lock = lockMutex(&m_pauseMutex); - // qDebug() << "Resume triggered by" << QThread::currentThread(); - m_isPaused = false; m_isPausedCondition.wakeAll(); } @@ -226,16 +222,14 @@ void ControllerScriptEngineBase::doPause() { m_isPaused = true; m_isPausedCondition.wakeAll(); - // qDebug() << "Paused of" << QThread::currentThread(); while (m_canPause && m_isPaused) { VERIFY_OR_DEBUG_ASSERT(m_isPausedCondition.wait(&m_pauseMutex, 1000)) { - qWarning() << "Main GUI pause timed out!"; + qCWarning(m_logger) << "Main GUI pause timed out!"; m_isPaused = false; }; } m_isPausedCondition.wakeAll(); - // qDebug() << "Resume of" << QThread::currentThread(); } void ControllerScriptEngineBase::showQMLExceptionDialog( diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 4673cf38b0d0..06de604a71c5 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -21,14 +21,17 @@ #ifdef MIXXX_USE_QML #include "util/assert.h" #include "util/cmdlineargs.h" +#endif -QByteArray - ControllerScriptEngineLegacy::kScreenTranformFunctionUntypedSignature = - QMetaObject::normalizedSignature( - "transformFrame(QVariant,QVariant)"); -QByteArray ControllerScriptEngineLegacy::kScreenTranformFunctionTypedSignature = +namespace { +#ifdef MIXXX_USE_QML +QByteArray kScreenTransformFunctionUntypedSignature = + QMetaObject::normalizedSignature( + "transformFrame(QVariant,QVariant)"); +QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); #endif +} // anonymous namespace ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( Controller* controller, const RuntimeLoggingCategory& logger) @@ -37,7 +40,7 @@ ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( &QFileSystemWatcher::fileChanged, this, [this](const QString& changedFile) { - qDebug() << "File" << changedFile << "has been changed."; + qCDebug(m_logger) << "File" << changedFile << "has been changed."; // This is to prevent double-reload when a file is updated twice // in a row as part of the normal saving process. See note in // QFileSystemWatcher::fileChanged documentation. @@ -59,7 +62,7 @@ ControllerScriptEngineLegacy::~ControllerScriptEngineLegacy() { void ControllerScriptEngineLegacy::watchFilePath(const QString& path) { if (m_fileWatcher.files().contains(path) || m_fileWatcher.directories().contains(path)) { - qDebug() << "File" << path << "is already being watch for controller auto-reload"; + qCDebug(m_logger) << "File" << path << "is already being watch for controller auto-reload"; return; } @@ -67,7 +70,7 @@ void ControllerScriptEngineLegacy::watchFilePath(const QString& path) { qCWarning(m_logger) << "Failed to watch script file" << path; } else { - qDebug() << "Watching file" << path << "for controller auto-reload"; + qCDebug(m_logger) << "Watching file" << path << "for controller auto-reload"; } } @@ -128,8 +131,9 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( << function << "on QML Scene " << i.key(); VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { - qWarning() << "Controller JS engine has an unhandled error. Discarding."; - qDebug() << "Controller JS error is:" << m_pJSEngine->catchError().toString(); + qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; + qCDebug(m_logger) << "Controller JS error is:" + << m_pJSEngine->catchError().toString(); } switch (args.size()) { @@ -164,8 +168,8 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( Q_ARG(QVariant, args[3].toVariant())); break; default: - qDebug() << "Trying to call a controller lifecycle method with " - "more than 5 args. Ignoring extra args"; + qCDebug(m_logger) << "Trying to call a controller lifecycle method with " + "more than 5 args. Ignoring extra args"; [[fallthrough]]; case 5: success &= method.invoke(i.value().get(), @@ -280,18 +284,18 @@ bool ControllerScriptEngineLegacy::initialize() { if (m_bQmlMode) { for (const LegacyControllerMapping::ScreenInfo& screen : std::as_const(m_infoScreens)) { VERIFY_OR_DEBUG_ASSERT(!availableScreens.contains(screen.identifier)) { - qWarning() << "A controller screen already contains the " - "identifier " - << screen.identifier; + qCWarning(m_logger) << "A controller screen already contains the " + "identifier " + << screen.identifier; return false; } availableScreens.insert(screen.identifier, std::make_shared(screen, this)); if (!availableScreens.value(screen.identifier)->isValid()) { - qWarning() << QString( + qCWarning(m_logger) << QString( "Unable to start the screen render for %1.") - .arg(screen.identifier); + .arg(screen.identifier); return false; } @@ -310,16 +314,16 @@ bool ControllerScriptEngineLegacy::initialize() { std::dynamic_pointer_cast(m_pJSEngine)); if (!availableScreens.value(screen.identifier)->isValid()) { - qWarning() << QString( + qCWarning(m_logger) << QString( "Unable to setup the screen render for %1.") - .arg(screen.identifier); + .arg(screen.identifier); availableScreens.value(screen.identifier)->stop(); return false; } } } else if (!m_infoScreens.isEmpty()) { - qWarning() << "Controller mapping has screen definitions but no QML " - "files to render on it. Ignoring."; + qCWarning(m_logger) << "Controller mapping has screen definitions but no QML " + "files to render on it. Ignoring."; } #endif @@ -361,11 +365,11 @@ bool ControllerScriptEngineLegacy::initialize() { watchFilePath(path); auto pQmlEngine = std::dynamic_pointer_cast(m_pJSEngine); pQmlEngine->addImportPath(path); - qWarning() << pQmlEngine->importPathList(); + qCWarning(m_logger) << pQmlEngine->importPathList(); } } else if (!m_libraryDirectories.isEmpty()) { - qWarning() << "Controller mapping has QML library definitions but no " - "QML files to use it. Ignoring."; + qCWarning(m_logger) << "Controller mapping has QML library definitions but no " + "QML files to use it. Ignoring."; } // If we encounter a failure while loading a scene, we will need to properly @@ -396,7 +400,7 @@ bool ControllerScriptEngineLegacy::initialize() { } } else { if (!availableScreens.contains(script.identifier)) { - qCritical() << "Not screen" << script.identifier << "found!"; + qCCritical(m_logger) << "Not screen" << script.identifier << "found!"; sceneBindingHasFailure = true; break; @@ -412,7 +416,7 @@ bool ControllerScriptEngineLegacy::initialize() { if (!availableScreens.isEmpty()) { if (!sceneBindingHasFailure) { - qWarning() + qCWarning(m_logger) << "Found screen with no QML scene able to run on it. Ignoring" << availableScreens.size() << "screens"; } @@ -475,33 +479,36 @@ bool ControllerScriptEngineLegacy::initialize() { void ControllerScriptEngineLegacy::extractTranformFunction( const QMetaObject* metaObject, const QString& screenIdentifier) { VERIFY_OR_DEBUG_ASSERT(metaObject) { - qWarning() << "Invalid meta object for screen" << screenIdentifier - << "It may be that an unhandled issue occurred when imnporting the scene."; + qCWarning(m_logger) + << "Invalid meta object for screen" << screenIdentifier + << "It may be that an unhandled issue occurred when imnporting " + "the scene."; return; } QMetaMethod tranformFunction; bool typed = false; - int methodIdx = metaObject->indexOfMethod(kScreenTranformFunctionUntypedSignature); + int methodIdx = metaObject->indexOfMethod(kScreenTransformFunctionUntypedSignature); if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { - qDebug() << "QML Scene for screen" << screenIdentifier - << "has no valid untyped transformFrame method."; - methodIdx = metaObject->indexOfMethod(kScreenTranformFunctionTypedSignature); + qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier + << "has no valid untyped transformFrame method."; + methodIdx = metaObject->indexOfMethod(kScreenTransformFunctionTypedSignature); typed = true; } tranformFunction = metaObject->method(methodIdx); if (!tranformFunction.isValid()) { - qDebug() << "QML Scene for screen" << screenIdentifier - << "has no valid typed transformFrame method. The frame data will be sent " - "untransformed"; + qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier + << "has no valid typed transformFrame method. The " + "frame data will be sent " + "untransformed"; QStringList methods; for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) { methods << QString::fromLatin1(metaObject->method(i).methodSignature()); } - qDebug() << "Found methods are: " << methods.join(", "); + qCDebug(m_logger) << "Found methods are: " << methods.join(", "); } m_transformScreenFrameFunctions.insert(screenIdentifier, @@ -543,11 +550,11 @@ void ControllerScriptEngineLegacy::handleScreenFrame( VERIFY_OR_DEBUG_ASSERT( m_transformScreenFrameFunctions.contains(screeninfo.identifier) || m_renderingScreens.contains(screeninfo.identifier)) { - qWarning() << "Unable to find transform function info for the given screen"; + qCWarning(m_logger) << "Unable to find transform function info for the given screen"; return; }; VERIFY_OR_DEBUG_ASSERT(m_rootItems.contains(screeninfo.identifier)) { - qWarning() << "Unable to find a root item for the given screen"; + qCWarning(m_logger) << "Unable to find a root item for the given screen"; return; }; @@ -587,7 +594,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( } if (!tranformMethod.method.isValid()) { - qWarning() + qCWarning(m_logger) << "Could not find a valid transform function but the screen " "doesn't accept raw data. Aborting screen rendering."; m_renderingScreens[screeninfo.identifier]->stop(); @@ -597,8 +604,8 @@ void ControllerScriptEngineLegacy::handleScreenFrame( QVariant returnedValue; VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { - qWarning() << "Controller JS engine has an unhandled error. Discarding."; - qDebug() << "Controller JS error is:" << m_pJSEngine->catchError().toString(); + qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; + qCDebug(m_logger) << "Controller JS error is:" << m_pJSEngine->catchError().toString(); } // During the frame transformation, any QML errors are considered fatal setErrorsAreFatal(true); @@ -618,7 +625,8 @@ void ControllerScriptEngineLegacy::handleScreenFrame( setErrorsAreFatal(false); if (!isSuccessful) { - qWarning() << "Could not transform rendering buffer for screen" << screeninfo.identifier; + qCWarning(m_logger) << "Could not transform rendering buffer for screen" + << screeninfo.identifier; // We manually stop the screen before we trigger the shutdown procedure // as this last one may continue rendering process in order to perform @@ -627,9 +635,9 @@ void ControllerScriptEngineLegacy::handleScreenFrame( return; } if (!isSuccessful || !returnedValue.isValid()) { - qWarning() << "Could not transform rendering buffer. The transform " - "function didn't return the expected Array. Stopping " - "rendering on this screen"; + qCWarning(m_logger) << "Could not transform rendering buffer. The transform " + "function didn't return the expected Array. Stopping " + "rendering on this screen"; return; } @@ -640,14 +648,14 @@ void ControllerScriptEngineLegacy::handleScreenFrame( } else if (returnedValue.canConvert()) { transformedFrame = returnedValue.toByteArray(); } else { - qWarning() << "Unable to interpret the returned data " << returnedValue; + qCWarning(m_logger) << "Unable to interpret the returned data " << returnedValue; return; } if (CmdlineArgs::Instance().getControllerDebug()) { - qDebug() << "Transform screen data for screen " << screeninfo.identifier - << "(first 64 bytes)" - << QByteArray(transformedFrame.toHex(' '), 128); + qCDebug(m_logger) << "Transform screen data for screen " << screeninfo.identifier + << "(first 64 bytes)" + << QByteArray(transformedFrame.toHex(' '), 128); m_pController->sendBytes(returnedValue.view()); } @@ -685,7 +693,7 @@ void ControllerScriptEngineLegacy::shutdown() { for (const auto& pScreen : qAsConst(m_renderingScreens)) { VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || !pScreen->isRunning() || pScreen->stop()) { - qWarning() << "Unable to stop the screen"; + qCWarning(m_logger) << "Unable to stop the screen"; }; } m_renderingScreens.clear(); @@ -797,8 +805,8 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( QFile scene = QFile(qmlScript.file.absoluteFilePath()); if (!scene.exists()) { - qWarning() << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() - << "does not exist."; + qCWarning(m_logger) << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() + << "does not exist."; return std::shared_ptr(nullptr); } @@ -813,24 +821,24 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( scene.close(); while (qmlComponent->isLoading()) { - qDebug() << "Waiting for component " - << qmlScript.file.absoluteFilePath() - << " to be ready: " << qmlComponent->progress(); + qCDebug(m_logger) << "Waiting for component " + << qmlScript.file.absoluteFilePath() + << " to be ready: " << qmlComponent->progress(); QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 500); } if (qmlComponent->isError()) { const QList errorList = qmlComponent->errors(); for (const QQmlError& error : errorList) { - qWarning() << "Unable to load the QML scene:" << error.url() - << "at line" << error.line() << ", error: " << error; + qCWarning(m_logger) << "Unable to load the QML scene:" << error.url() + << "at line" << error.line() << ", error: " << error; showQMLExceptionDialog(error, true); } return std::shared_ptr(nullptr); } VERIFY_OR_DEBUG_ASSERT(qmlComponent->isReady()) { - qWarning() << "QMLComponent isn't ready although synchronous load was requested."; + qCWarning(m_logger) << "QMLComponent isn't ready although synchronous load was requested."; return std::shared_ptr(nullptr); } @@ -839,7 +847,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( if (qmlComponent->isError()) { const QList errorList = qmlComponent->errors(); for (const QQmlError& error : errorList) { - qWarning() << error.url() << error.line() << error; + qCWarning(m_logger) << error.url() << error.line() << error; } return std::shared_ptr(nullptr); } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 4d0ce7151838..3c34dd8aee11 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -84,9 +84,6 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { const LegacyControllerMapping::ScriptFileInfo& qmlScript, std::shared_ptr pScreen); - static QByteArray kScreenTranformFunctionUntypedSignature; - static QByteArray kScreenTranformFunctionTypedSignature; - struct TransformScreenFrameFunction { QMetaMethod method; bool typed; From f1487a068e5a491461b004cd68722cc8eeb58fbf Mon Sep 17 00:00:00 2001 From: Antoine C Date: Tue, 9 Apr 2024 18:02:26 +0100 Subject: [PATCH 03/26] Fix typo in test --- src/test/controller_mapping_file_handler_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index 1d70d99074cd..457fb451f118 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -620,7 +620,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, - "Unknown endiant format \"enormous\""); + "Unknown endian format \"enormous\""); addScriptFilesToMapping( doc.documentElement(), From 6d5fb99b2fceea330242e65f0a3de7264ca1729c Mon Sep 17 00:00:00 2001 From: Antoine C Date: Fri, 12 Apr 2024 21:43:08 +0100 Subject: [PATCH 04/26] Removing unused QML Scene3D import --- res/controllers/DummyDeviceDefaultScreen.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/res/controllers/DummyDeviceDefaultScreen.qml b/res/controllers/DummyDeviceDefaultScreen.qml index 82e44949cd8d..c216f2259d6f 100755 --- a/res/controllers/DummyDeviceDefaultScreen.qml +++ b/res/controllers/DummyDeviceDefaultScreen.qml @@ -1,6 +1,5 @@ import QtQuick 2.15 import QtQuick.Window 2.3 -import QtQuick.Scene3D 2.14 import QtQuick.Controls 2.15 import QtQuick.Shapes 1.11 From a2044051216caf664de2379254adf95ce8228f0c Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 5 May 2024 00:16:11 +0100 Subject: [PATCH 05/26] Fix typo and rename confusing symbols --- src/controllers/controller.cpp | 4 ++-- src/controllers/controllerscreenpreview.cpp | 4 ++-- src/controllers/dlgprefcontroller.cpp | 2 +- src/controllers/legacycontrollermapping.h | 8 ++++---- .../legacycontrollermappingfilehandler.cpp | 10 ++++++---- src/controllers/legacycontrollermappingfilehandler.h | 2 +- .../rendering/controllerrenderingengine.cpp | 2 +- .../legacy/controllerscriptenginelegacy.cpp | 12 ++++++------ .../scripting/legacy/controllerscriptenginelegacy.h | 6 +++--- src/coreservices.cpp | 4 ++-- src/coreservices.h | 2 +- src/test/controller_mapping_file_handler_test.cpp | 12 ++++++------ 12 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index f7ce14d9d7bc..03e7e0f7a796 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -85,8 +85,8 @@ bool Controller::applyMapping(const QString& resourcePath) { m_pScriptEngineLegacy->setSettings(pMapping->getSettings()); #ifdef MIXXX_USE_QML - m_pScriptEngineLegacy->setLibraryDirectories(pMapping->getLibraryDirectories()); - m_pScriptEngineLegacy->setInfoScrens(pMapping->getInfoScreens()); + m_pScriptEngineLegacy->setModulePaths(pMapping->getModules()); + m_pScriptEngineLegacy->setInfoScreens(pMapping->getInfoScreens()); m_pScriptEngineLegacy->setResourcePath(resourcePath); #else Q_UNUSED(resourcePath); diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp index a1ab7da6a33f..928682337b40 100644 --- a/src/controllers/controllerscreenpreview.cpp +++ b/src/controllers/controllerscreenpreview.cpp @@ -47,11 +47,11 @@ void ControllerScreenPreview::updateFrame( for (uint i = 0; i < frameDurationHistoryLength; i++) { durationSinceLastFrame += static_cast(m_frameDurationHistory[i]); } - durationSinceLastFrame /= (double)frameDurationHistoryLength; + durationSinceLastFrame /= static_cast(frameDurationHistoryLength); if (durationSinceLastFrame > 0.0) { m_pStat->setText(QString("FPS: %0/%1") - .arg((int)(1000.0 / durationSinceLastFrame)) + .arg(static_cast(1000.0 / durationSinceLastFrame)) .arg(m_screenInfo.target_fps)); } m_pFrame->setPixmap(QPixmap::fromImage(frame)); diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index b056d412aad1..37aa006eb46c 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -427,7 +427,7 @@ QString DlgPrefController::mappingFileLinks( } #ifdef MIXXX_USE_QML - for (const auto& qmlLibrary : pMapping->getLibraryDirectories()) { + for (const auto& qmlLibrary : pMapping->getModules()) { QString scriptFileLink = coloredLinkString( m_pLinkColor, qmlLibrary.dirinfo.fileName(), diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 62f8c61f88ef..36edc14b0885 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -212,17 +212,17 @@ class LegacyControllerMapping { #ifdef MIXXX_USE_QML /// Adds a custom QML module file to the list of controller modules for this mapping. - /// @param dirinfo A FileInfo of the directory or QML module + /// @param dirInfo A FileInfo of the directory or QML module /// @param builtin If this is true, the script won't be written to the XML - virtual void addLibraryDirectory(const QFileInfo& dirinfo, + virtual void addModule(const QFileInfo& dirInfo, bool builtin = false) { m_modules.append(QMLModuleInfo( - dirinfo, + dirInfo, builtin)); setDirty(true); } - const QList& getLibraryDirectories() const { + const QList& getModules() const { return m_modules; } diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index fd7353a7e8cc..b7dfe5dfd93f 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -52,9 +52,9 @@ QFileInfo findLibraryPath( /// @brief Parse a string that contain a boolean value in human representation /// @param value the string containing the boolean setting -/// @return true for string value "yes", "true" and "1", false otherwise +/// @return true for string value "true" and "1", false otherwise bool parseHumanBoolean(const QString& value) { - return value == QStringLiteral("yes") || value == QStringLiteral("true") || + return value == QStringLiteral("true") || value == QStringLiteral("1"); } #endif @@ -294,7 +294,9 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( file, LegacyControllerMapping::ScriptFileInfo::Type::QML); #else - kLogger.warning() << "Unsupported render scene. Mixxx isn't built with QML support"; + kLogger.warning() + << "Unsupported render scene for file" << file.filePath() + << ". Mixxx isn't built with QML support"; return; #endif } else { @@ -380,7 +382,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( QString libFilename = qmlLibrary.attribute("path", ""); QFileInfo path = findLibraryPath(mapping, libFilename, systemMappingsPath); kLogger.debug() << "Adding QML directory " << libFilename; - mapping->addLibraryDirectory(path); + mapping->addModule(path); qmlLibrary = qmlLibrary.nextSiblingElement("library"); } #endif diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index a3a5c8ffb77c..10ac52780254 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -73,7 +73,7 @@ class LegacyControllerMappingFileHandler { bool writeDocument(const QDomDocument& root, const QString& fileName) const; #ifdef MIXXX_USE_QML - // Maximum target frame per request for a a screen controller + // Maximum target frame per request for a screen controller static constexpr int s_maxTargetFps = 240; // Maximum time allowed for a screen to run a splash off animation static constexpr int s_maxSplashOffDuration = 3000; diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index ae7041d424bb..fdce41cc09e1 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -373,7 +373,7 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f if (CmdlineArgs::Instance() .getControllerDebug()) { auto endOfRender = mixxx::Time::elapsed(); - kLogger.debug() << "Fame took " + kLogger.debug() << "Frame took " << (endOfRender - m_nextFrameStart).formatMillisWithUnit() << " and frame has" << frame.size() << "bytes"; } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 06de604a71c5..f818431063bf 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -221,16 +221,16 @@ QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( } #ifdef MIXXX_USE_QML -void ControllerScriptEngineLegacy::setLibraryDirectories( - const QList& directories) { +void ControllerScriptEngineLegacy::setModulePaths( + const QList& modules) { const QStringList paths = m_fileWatcher.files(); if (!paths.isEmpty()) { m_fileWatcher.removePaths(paths); } - m_libraryDirectories = directories; + m_modules = modules; } -void ControllerScriptEngineLegacy::setInfoScrens( +void ControllerScriptEngineLegacy::setInfoScreens( const QList& screens) { m_rootItems.clear(); m_renderingScreens.clear(); @@ -353,7 +353,7 @@ bool ControllerScriptEngineLegacy::initialize() { #ifdef MIXXX_USE_QML if (m_bQmlMode) { for (const LegacyControllerMapping::QMLModuleInfo& module : - std::as_const(m_libraryDirectories)) { + std::as_const(m_modules)) { auto path = module.dirinfo.absoluteFilePath(); QDirIterator it(path, QStringList() << "*.qml", @@ -367,7 +367,7 @@ bool ControllerScriptEngineLegacy::initialize() { pQmlEngine->addImportPath(path); qCWarning(m_logger) << pQmlEngine->importPathList(); } - } else if (!m_libraryDirectories.isEmpty()) { + } else if (!m_modules.isEmpty()) { qCWarning(m_logger) << "Controller mapping has QML library definitions but no " "QML files to use it. Ignoring."; } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 475f58801f6a..c4ff9a67c4e3 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -51,8 +51,8 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { const QList>& settings); #ifdef MIXXX_USE_QML - void setLibraryDirectories(const QList& scripts); - void setInfoScrens(const QList& scripts); + void setModulePaths(const QList& scripts); + void setInfoScreens(const QList& scripts); void setResourcePath(const QString& resourcePath) { m_resourcePath = resourcePath; } @@ -108,7 +108,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QHash> m_renderingScreens; QHash> m_rootItems; QHash m_transformScreenFrameFunctions; - QList m_libraryDirectories; + QList m_modules; QList m_infoScreens; QString m_resourcePath{"."}; #endif diff --git a/src/coreservices.cpp b/src/coreservices.cpp index aaaebf362d89..8ff74d909ced 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -462,10 +462,10 @@ void CoreServices::initialize(QApplication* pApp) { m_isInitialized = true; #ifdef MIXXX_USE_QML - initializeQMLSignletons(); + initializeQMLSingletons(); } -void CoreServices::initializeQMLSignletons() { +void CoreServices::initializeQMLSingletons() { // Any uncreateable non-singleton types registered here require // arguments that we don't want to expose to QML directly. Instead, they // can be retrieved by member properties or methods from the singleton diff --git a/src/coreservices.h b/src/coreservices.h index 0102d2000583..d0c1a2499378 100644 --- a/src/coreservices.h +++ b/src/coreservices.h @@ -116,7 +116,7 @@ class CoreServices : public QObject { void initializeScreensaverManager(); void initializeLogging(); #ifdef MIXXX_USE_QML - void initializeQMLSignletons(); + void initializeQMLSingletons(); #endif /// Tear down CoreServices that were previously initialized by `initialize()`. diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index 457fb451f118..b4cf117235c6 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -64,7 +64,7 @@ class MockLegacyControllerMapping : public LegacyControllerMapping { bool reversedColorse, bool rawData), (override)); - MOCK_METHOD(void, addLibraryDirectory, (const QFileInfo& dirinfo, bool builtin), (override)); + MOCK_METHOD(void, addModule, (const QFileInfo& dirinfo, bool builtin), (override)); std::shared_ptr clone() const override { throw std::runtime_error("not implemented"); @@ -104,7 +104,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseSimpleMapping) { LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, false)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); - EXPECT_CALL(*mapping, addLibraryDirectory(_, _)).Times(0); + EXPECT_CALL(*mapping, addModule(_, _)).Times(0); addScriptFilesToMapping( doc.documentElement(), @@ -152,7 +152,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { std::endian::little, false, false)); - EXPECT_CALL(*mapping, addLibraryDirectory(QFileInfo("/dummy/path/foobar"), false)); + EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); addScriptFilesToMapping( doc.documentElement(), @@ -630,8 +630,8 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) } TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesDefinition) { - QStringList kFalseValue = {"false", "FALse", "no", "nope", "maybe"}; - QStringList kTrueValue = {"true", "trUe", "1", "yes"}; + QStringList kFalseValue = {"false", "FALse", "no", "yes", "nope", "maybe"}; + QStringList kTrueValue = {"true", "trUe", "1"}; QDomDocument doc; std::shared_ptr mapping; @@ -927,7 +927,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { std::endian::little, false, false)); - EXPECT_CALL(*mapping, addLibraryDirectory(QFileInfo("/dummy/path/foobar"), false)); + EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); addScriptFilesToMapping( doc.documentElement(), From 676cd906dad5788c5faff7a3500d2f277d258296 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 8 May 2024 14:47:16 +0100 Subject: [PATCH 06/26] Use std for duration and address couple of PR nits --- src/controllers/controller.h | 4 +- src/controllers/controllerscreenpreview.cpp | 55 ++-- src/controllers/controllerscreenpreview.h | 8 +- src/controllers/dlgprefcontroller.cpp | 10 +- src/controllers/dlgprefcontroller.h | 2 +- src/controllers/legacycontrollermapping.h | 26 +- .../legacycontrollermappingfilehandler.cpp | 25 +- .../legacycontrollermappingfilehandler.h | 2 + src/controllers/midi/midicontroller.cpp | 4 +- .../rendering/controllerrenderingengine.cpp | 97 ++++--- .../rendering/controllerrenderingengine.h | 3 +- .../scripting/controllerscriptenginebase.cpp | 6 +- .../legacy/controllerscriptenginelegacy.cpp | 16 +- tools/README | 4 +- tools/dummy_hid_device.cpp | 253 ------------------ 15 files changed, 150 insertions(+), 365 deletions(-) delete mode 100644 tools/dummy_hid_device.cpp diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 80e90ddca138..14e9d5d40a2f 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -139,7 +139,7 @@ class Controller : public QObject { const RuntimeLoggingCategory m_logInput; const RuntimeLoggingCategory m_logOutput; - public slots: + public: // This must be reimplemented by sub-classes desiring to send raw bytes to a // controller. virtual void sendBytes(const QByteArray& data) = 0; @@ -159,6 +159,8 @@ class Controller : public QObject { } private: + /// Controllers have multiple ownership over an engine. + /// A ControllerScriptEngine must not own a controller. std::shared_ptr m_pScriptEngineLegacy; // Verbose and unique description of device type, defaults to empty diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp index 928682337b40..3903a275c1ef 100644 --- a/src/controllers/controllerscreenpreview.cpp +++ b/src/controllers/controllerscreenpreview.cpp @@ -6,15 +6,20 @@ #include "moc_controllerscreenpreview.cpp" #include "util/time.h" +namespace { +/// Number of sample frame timestamp sample to perform a smooth average FPS label. +constexpr double kFrameSmoothAverageFactor = 5; +} // namespace + +using Clock = std::chrono::steady_clock; +using TimePoint = std::chrono::time_point; + ControllerScreenPreview::ControllerScreenPreview( QWidget* parent, const LegacyControllerMapping::ScreenInfo& screen) : QWidget(parent), m_screenInfo(screen), m_pFrame(make_parented(this)), - m_pStat(make_parented("- FPS", this)), - m_frameDurationHistoryIdx(0), - m_lastFrameTimestamp(mixxx::Time::elapsed()) { - std::fill(m_frameDurationHistory.begin(), m_frameDurationHistory.end(), 0); + m_pStat(make_parented(tr("FPS: n/a"), this)) { m_pFrame->setFixedSize(screen.size); setMaximumWidth(screen.size.width()); m_pStat->setAlignment(Qt::AlignRight); @@ -22,14 +27,14 @@ ControllerScreenPreview::ControllerScreenPreview( auto* pBottomLayout = new QHBoxLayout(); pLayout->addWidget(m_pFrame); pBottomLayout->addWidget(make_parented( - QString("\"%0\"") + QStringLiteral("\"%0\"") .arg(m_screenInfo.identifier.isEmpty() - ? QStringLiteral("Unnamed") + ? tr("Unnamed") : m_screenInfo.identifier), this)); pBottomLayout->addWidget(m_pStat); - pLayout->addItem(pBottomLayout); - pLayout->addItem(new QSpacerItem( + pLayout->addLayout(pBottomLayout); + pLayout->addSpacerItem(new QSpacerItem( 1, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); } void ControllerScreenPreview::updateFrame( @@ -37,23 +42,29 @@ void ControllerScreenPreview::updateFrame( if (m_screenInfo.identifier != screen.identifier) { return; } - size_t frameDurationHistoryLength = sizeof(m_frameDurationHistory) / sizeof(uint); - auto currentTimestamp = mixxx::Time::elapsed(); - m_frameDurationHistory[m_frameDurationHistoryIdx++] = - (currentTimestamp - m_lastFrameTimestamp).toIntegerMillis(); - m_frameDurationHistoryIdx %= frameDurationHistoryLength; + m_pFrame->setPixmap(QPixmap::fromImage(frame)); - double durationSinceLastFrame = 0.0; - for (uint i = 0; i < frameDurationHistoryLength; i++) { - durationSinceLastFrame += static_cast(m_frameDurationHistory[i]); + auto currentTimestamp = Clock::now(); + if (m_lastFrameTimestamp == TimePoint()) { + m_lastFrameTimestamp = currentTimestamp; + return; } - durationSinceLastFrame /= static_cast(frameDurationHistoryLength); - if (durationSinceLastFrame > 0.0) { - m_pStat->setText(QString("FPS: %0/%1") - .arg(static_cast(1000.0 / durationSinceLastFrame)) - .arg(m_screenInfo.target_fps)); + if (m_averageFrameDuration == 0) { + m_averageFrameDuration = + std::chrono::duration_cast( + currentTimestamp - m_lastFrameTimestamp) + .count(); + } else { + m_averageFrameDuration = std::lerp(m_averageFrameDuration, + std::chrono::duration_cast( + currentTimestamp - m_lastFrameTimestamp) + .count(), + 1.0 / kFrameSmoothAverageFactor); } - m_pFrame->setPixmap(QPixmap::fromImage(frame)); m_lastFrameTimestamp = currentTimestamp; + m_pStat->setText(tr("FPS: %0/%1") + .arg(QString::number(static_cast( + 1000 / m_averageFrameDuration)), + QString::number(m_screenInfo.target_fps))); } diff --git a/src/controllers/controllerscreenpreview.h b/src/controllers/controllerscreenpreview.h index c11ae94ad860..f4906c150f56 100644 --- a/src/controllers/controllerscreenpreview.h +++ b/src/controllers/controllerscreenpreview.h @@ -3,13 +3,12 @@ #include #include #include +#include #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "util/duration.h" #include "util/parented_ptr.h" -/// Number of sample frame timestamp sample to calculate FPS label. -constexpr int kFrameHistorySize = 5; /// Widget to preview controller screen, used in preference window. This is /// useful to help when developing new screen layout, without inducing any wear @@ -30,8 +29,7 @@ class ControllerScreenPreview : public QWidget { parented_ptr m_pFrame; parented_ptr m_pStat; - uint8_t m_frameDurationHistoryIdx; - std::array m_frameDurationHistory; - mixxx::Duration m_lastFrameTimestamp; + double m_averageFrameDuration; + std::chrono::time_point m_lastFrameTimestamp; }; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 37aa006eb46c..ced296e81596 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -120,12 +120,11 @@ DlgPrefController::DlgPrefController( connect(m_pController, &Controller::engineStarted, this, - QOverload>::of( - &DlgPrefController::slotShowPreviewScreens)); + &DlgPrefController::slotShowPreviewScreens); connect(m_pController, &Controller::engineStopped, this, - QOverload<>::of(&DlgPrefController::slotShowPreviewScreens)); + &DlgPrefController::slotClearPreviewScreens); } #endif @@ -874,6 +873,9 @@ void DlgPrefController::initTableView(QTableView* pTable) { void DlgPrefController::slotShowPreviewScreens( std::shared_ptr scriptEngine) { QLayoutItem* pItem; + VERIFY_OR_DEBUG_ASSERT(m_ui.groupBoxScreens->layout()) { + return; + } while ((pItem = m_ui.groupBoxScreens->layout()->takeAt(0)) != nullptr) { delete pItem->widget(); delete pItem; @@ -891,7 +893,7 @@ void DlgPrefController::slotShowPreviewScreens( auto screens = m_pMapping->getInfoScreens(); - for (const LegacyControllerMapping::ScreenInfo& screen : qAsConst(screens)) { + for (const LegacyControllerMapping::ScreenInfo& screen : std::as_const(screens)) { ControllerScreenPreview* pPreviewScreen = new ControllerScreenPreview(m_ui.groupBoxScreens, screen); m_ui.groupBoxScreens->layout()->addWidget(pPreviewScreen); diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 9f93b960729f..72938fbafbb4 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -68,7 +68,7 @@ class DlgPrefController : public DlgPreferencePage { // Onboard screen controller void slotShowPreviewScreens(std::shared_ptr scriptEngine); // Wrapper used on shutdown - void slotShowPreviewScreens() { + void slotClearPreviewScreens() { slotShowPreviewScreens(nullptr); } #endif diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 36edc14b0885..5bcd414790da 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -99,27 +99,10 @@ class LegacyControllerMapping { }; struct ScreenInfo { - ScreenInfo(const QString& aIdentifier, - const QSize& aSize, - uint aTargetFps, - uint aSplashOff, - QImage::Format aPixelFormat, - std::endian anEndian, - bool isReversedColor, - bool isRawData) - : identifier(aIdentifier), - size(aSize), - target_fps(aTargetFps), - splash_off(aSplashOff), - pixelFormat(aPixelFormat), - endian(anEndian), - reversedColor(isReversedColor), - rawData(isRawData) { - } - QString identifier; QSize size; uint target_fps; + uint msaa; uint splash_off; QImage::Format pixelFormat; std::endian endian; @@ -230,6 +213,7 @@ class LegacyControllerMapping { /// @param identifier The screen identifier /// @param size the size of the screen /// @param targetFps the maximum FPS to render + /// @param msaa the MSAA value to use for render /// @param splashoff the rendering grace time given when the screen is requested to shutdown /// @param pixelFormat the pixel encoding format /// @param endian the pixel endian format @@ -238,19 +222,21 @@ class LegacyControllerMapping { virtual void addScreenInfo(const QString& identifier, const QSize& size, uint targetFps, + uint msaa, uint splashoff, QImage::Format pixelFormat, std::endian endian, bool reversedColor, bool rawData) { - m_screens.append(ScreenInfo(identifier, + m_screens.append(ScreenInfo{identifier, size, targetFps, + msaa, splashoff, pixelFormat, endian, reversedColor, - rawData)); + rawData}); setDirty(true); } diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index b7dfe5dfd93f..27f34db5a2b5 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -1,7 +1,4 @@ #include "controllers/legacycontrollermappingfilehandler.h" - -#include - #include "controllers/defs_controllers.h" #include "controllers/midi/legacymidicontrollermappingfilehandler.h" #include "util/logger.h" @@ -52,10 +49,9 @@ QFileInfo findLibraryPath( /// @brief Parse a string that contain a boolean value in human representation /// @param value the string containing the boolean setting -/// @return true for string value "true" and "1", false otherwise +/// @return true for string value "true", false otherwise bool parseHumanBoolean(const QString& value) { - return value == QStringLiteral("true") || - value == QStringLiteral("1"); + return value == QStringLiteral("true"); } #endif @@ -318,10 +314,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( while (!screen.isNull()) { QString identifier = screen.attribute("identifier", ""); uint targetFps = screen.attribute("targetFps", "30").toUInt(); + uint msaa = screen.attribute("msaa", "1").toUInt(); QString pixelFormatName = screen.attribute("pixelType", "RBG"); QString endianName = screen.attribute("endian", "little"); - QString reversedColor = screen.attribute("reversed", "false").toLower().trimmed(); - QString rawData = screen.attribute("raw", "false").toLower().trimmed(); + bool reversedColor = parseHumanBoolean( + screen.attribute("reversed", "false").toLower().trimmed()); + bool rawData = parseHumanBoolean(screen.attribute("raw", "false").toLower().trimmed()); uint splashOff = screen.attribute("splashoff", "0").toUInt(); if (!targetFps || targetFps > s_maxTargetFps) { @@ -330,6 +328,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( << s_maxTargetFps; return; } + if (!msaa || msaa > s_maxMsaa) { + kLogger.warning() + << "Invalid MSAA value. MSAA value must be between 1 and" + << s_maxMsaa; + return; + } if (splashOff > s_maxSplashOffDuration) { kLogger.warning() << QString( @@ -366,11 +370,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( mapping->addScreenInfo(identifier, QSize(width, height), targetFps, + msaa, splashOff, pixelFormat, endian, - parseHumanBoolean(reversedColor), - parseHumanBoolean(rawData)); + reversedColor, + rawData); screen = screen.nextSiblingElement("screen"); } // Build a list of QML files to load diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index 10ac52780254..e1339910105e 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -75,6 +75,8 @@ class LegacyControllerMappingFileHandler { #ifdef MIXXX_USE_QML // Maximum target frame per request for a screen controller static constexpr int s_maxTargetFps = 240; + // Maximum MSAA value that can be used + static constexpr int s_maxMsaa = 16; // Maximum time allowed for a screen to run a splash off animation static constexpr int s_maxSplashOffDuration = 3000; #endif diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index d5a928a593ff..3130f6594fed 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -302,7 +302,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, if (mapping.options.testFlag(MidiOption::Script)) { auto pEngine = getScriptEngine(); - if (!pEngine) { + if (pEngine == nullptr) { return; } @@ -596,7 +596,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, // Custom script handler if (mapping.options.testFlag(MidiOption::Script)) { auto pEngine = getScriptEngine(); - if (!pEngine) { + if (pEngine == nullptr) { return; } pEngine->handleIncomingData(data); diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index fdce41cc09e1..fdb9370fe9da 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -19,11 +19,15 @@ #include "util/cmdlineargs.h" #include "util/logger.h" #include "util/time.h" +#include "util/timer.h" namespace { const mixxx::Logger kLogger("ControllerRenderingEngine"); } // anonymous namespace +using Clock = std::chrono::steady_clock; +using TimePoint = std::chrono::time_point; + QMutex ControllerRenderingEngine::s_glMutex = QMutex(); ControllerRenderingEngine::ControllerRenderingEngine( @@ -66,7 +70,12 @@ void ControllerRenderingEngine::prepare() { m_pThread = std::make_unique(); m_pThread->setObjectName("ControllerScreenRenderer"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + DEBUG_ASSERT(moveToThread(m_pThread.get())); +#else moveToThread(m_pThread.get()); +#endif + connect(this, &ControllerRenderingEngine::setupRequested, this, @@ -85,15 +94,15 @@ void ControllerRenderingEngine::prepare() { ControllerRenderingEngine::~ControllerRenderingEngine() { VERIFY_OR_DEBUG_ASSERT(!m_fbo) { - kLogger.warning() << "The ControllerEngine is being deleted but hasn't been " - "cleaned up. Brace for impact"; + kLogger.critical() << "The ControllerEngine is being deleted but hasn't been " + "cleaned up. Brace for impact"; }; } void ControllerRenderingEngine::start() { VERIFY_OR_DEBUG_ASSERT(!thread()->isFinished() && !thread()->isInterruptionRequested()) { - kLogger.warning() << "Render thread has or ir about to terminate. Cannot " - "start this render anymore."; + kLogger.critical() << "Render thread has or is about to terminate. Cannot " + "start this render anymore."; return; } QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); @@ -104,6 +113,10 @@ bool ControllerRenderingEngine::isRunning() const { void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngine) { m_isValid = false; + VERIFY_OR_DEBUG_ASSERT(qmlEngine) { + kLogger.critical() << "No QML engine was passed!"; + return; + } VERIFY_OR_DEBUG_ASSERT(QThread::currentThread() != thread()) { kLogger.warning() << "Unable to setup OpenGL rendering context from the same " "thread as the render object"; @@ -126,7 +139,7 @@ void ControllerRenderingEngine::requestSend(Controller* controller, const QByteA void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { QSurfaceFormat format; - format.setSamples(16); + format.setSamples(m_screenInfo.msaa); format.setDepthBufferSize(16); format.setStencilBufferSize(8); @@ -142,8 +155,7 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { connect(m_context.get(), &QOpenGLContext::aboutToBeDestroyed, this, - &ControllerRenderingEngine::finish, - Qt::BlockingQueuedConnection); + &ControllerRenderingEngine::finish); m_offscreenSurface = std::make_unique(); m_offscreenSurface->setFormat(m_context->format()); @@ -208,6 +220,7 @@ void ControllerRenderingEngine::finish() { } void ControllerRenderingEngine::renderFrame() { + ScopedTimer t(u"ControllerRenderingEngine::renderFrame"); if (!m_isValid) { DEBUG_ASSERT(!"Trying to render frame on an invalid engine"); return; @@ -234,6 +247,7 @@ void ControllerRenderingEngine::renderFrame() { }; if (!m_fbo) { + ScopedTimer t(u"ControllerRenderingEngine::renderFrame::initFBO"); VERIFY_OR_DEBUG_ASSERT(QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) { kLogger.warning() << "OpenGL doesn't support FBO"; lock.unlock(); @@ -276,7 +290,7 @@ void ControllerRenderingEngine::renderFrame() { m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height()); } - m_nextFrameStart = mixxx::Time::elapsed(); + m_nextFrameStart = Clock::now(); m_renderControl->beginFrame(); @@ -286,16 +300,19 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->polishItems(); - VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { - kLogger.warning() << "Couldn't sync the render control."; - lock.unlock(); - finish(); - if (m_pControllerEngine) { - m_pControllerEngine->resume(); - } + { + ScopedTimer t(u"ControllerRenderingEngine::renderFrame::sync"); + VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { + kLogger.warning() << "Couldn't sync the render control."; + lock.unlock(); + finish(); + if (m_pControllerEngine) { + m_pControllerEngine->resume(); + } - return; - }; + return; + }; + } if (m_pControllerEngine) { m_pControllerEngine->resume(); @@ -332,13 +349,16 @@ void ControllerRenderingEngine::renderFrame() { // Flush any remaining GL errors while (m_context->functions()->glGetError()) { } - m_context->functions()->glReadPixels(0, - 0, - m_screenInfo.size.width(), - m_screenInfo.size.height(), - m_GLDataFormat, - m_GLDataType, - fboImage.bits()); + { + ScopedTimer t(u"ControllerRenderingEngine::renderFrame::glReadPixels"); + m_context->functions()->glReadPixels(0, + 0, + m_screenInfo.size.width(), + m_screenInfo.size.height(), + m_GLDataFormat, + m_GLDataType, + fboImage.bits()); + } glError = m_context->functions()->glGetError(); VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { kLogger.warning() << "GLError: " << glError; @@ -366,31 +386,36 @@ bool ControllerRenderingEngine::stop() { } void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { + ScopedTimer t(u"ControllerRenderingEngine::send"); if (!frame.isEmpty()) { controller->sendBytes(frame); } if (CmdlineArgs::Instance() .getControllerDebug()) { - auto endOfRender = mixxx::Time::elapsed(); - kLogger.debug() << "Frame took " - << (endOfRender - m_nextFrameStart).formatMillisWithUnit() - << " and frame has" << frame.size() << "bytes"; + auto endOfRender = Clock::now(); + kLogger.debug() + << "Frame took " + << std::chrono::duration_cast( + endOfRender - m_nextFrameStart) + .count() + << "milliseconds and frame has" << frame.size() << "bytes"; } - m_nextFrameStart += mixxx::Duration::fromSeconds(1.0 / (double)m_screenInfo.target_fps); + m_nextFrameStart += std::chrono::milliseconds(1000 / m_screenInfo.target_fps); - auto durationToWaitBeforeFrame = (m_nextFrameStart - mixxx::Time::elapsed()); - auto msecToWaitBeforeFrame = durationToWaitBeforeFrame.toIntegerMillis(); + auto durationToWaitBeforeFrame = + std::chrono::duration_cast( + m_nextFrameStart - Clock::now()); - if (msecToWaitBeforeFrame > 0) { + if (durationToWaitBeforeFrame > std::chrono::milliseconds(0)) { if (CmdlineArgs::Instance() .getControllerDebug()) { kLogger.debug() << "Waiting for " - << durationToWaitBeforeFrame.formatMillisWithUnit() - << " before rendering next frame"; + << durationToWaitBeforeFrame.count() + << "milliseconds before rendering next frame"; } - QTimer::singleShot(msecToWaitBeforeFrame, + QTimer::singleShot(durationToWaitBeforeFrame, Qt::PreciseTimer, this, &ControllerRenderingEngine::renderFrame); @@ -400,6 +425,8 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f } bool ControllerRenderingEngine::event(QEvent* event) { + // In case there is a request for update (e.g using QWindow::requestUpdate), + // we emit the signal to request rendering using the engine if (event->type() == QEvent::UpdateRequest) { renderFrame(); return true; diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index f07c0bf7fb0d..396936d95a1f 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "controllers/legacycontrollermapping.h" #include "controllers/scripting/controllerscriptenginebase.h" @@ -72,7 +73,7 @@ class ControllerRenderingEngine : public QObject { private: virtual void prepare(); - mixxx::Duration m_nextFrameStart; + std::chrono::time_point m_nextFrameStart; LegacyControllerMapping::ScreenInfo m_screenInfo; diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index c24fa4782545..e4d866e78d55 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -245,10 +245,8 @@ void ControllerScriptEngineBase::showQMLExceptionDialog( if (filename.isEmpty()) { filename = QStringLiteral(""); } - QString errorText = QString("Uncaught exception: %1:%2: %3") - .arg(filename) - .arg(error.line()) - .arg(error.description()); + QString errorText = QStringLiteral("Uncaught exception: %1:%2: %3") + .arg(filename, QString::number(error.line()), error.description()); qCWarning(m_logger) << "ControllerScriptHandlerBase:" << errorText; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f818431063bf..a6bc699143fd 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -30,6 +30,11 @@ QByteArray kScreenTransformFunctionUntypedSignature = "transformFrame(QVariant,QVariant)"); QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); + +template +void delete_later(T* screen) { + screen->deleteLater(); +}; #endif } // anonymous namespace @@ -422,8 +427,9 @@ bool ControllerScriptEngineLegacy::initialize() { } while (!availableScreens.isEmpty()) { - auto orphanScreen = availableScreens.take(availableScreens.firstKey()); - std::move(orphanScreen)->deleteLater(); + std::shared_ptr orphanScreen( + nullptr, &delete_later); + availableScreens.take(availableScreens.firstKey()).swap(orphanScreen); } } if (sceneBindingHasFailure) { @@ -457,7 +463,7 @@ bool ControllerScriptEngineLegacy::initialize() { #ifdef MIXXX_USE_QML setCanPause(true); - for (const auto& pScreen : qAsConst(m_renderingScreens)) { + for (const auto& pScreen : std::as_const(m_renderingScreens)) { pScreen->start(); } #endif @@ -675,7 +681,7 @@ void ControllerScriptEngineLegacy::shutdown() { setCanPause(false); // Wait till the splash off animation has finished rendering uint maxSplashOffDuration = 0; - for (const auto& pScreen : qAsConst(m_renderingScreens)) { + for (const auto& pScreen : std::as_const(m_renderingScreens)) { if (!pScreen->isRunning()) { continue; } @@ -690,7 +696,7 @@ void ControllerScriptEngineLegacy::shutdown() { } m_rootItems.clear(); - for (const auto& pScreen : qAsConst(m_renderingScreens)) { + for (const auto& pScreen : std::as_const(m_renderingScreens)) { VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || !pScreen->isRunning() || pScreen->stop()) { qCWarning(m_logger) << "Unable to stop the screen"; diff --git a/tools/README b/tools/README index 79ba807ac723..875579939de5 100644 --- a/tools/README +++ b/tools/README @@ -4,7 +4,7 @@ the creation of controller layouts. ## Dummy HID Device (Linux only) -If you ever need a dummy HID device to test mapping capabilities or introspect reports set by a mapping for example, you can build a dummy one using `dummy_hid_device.cpp `. Note that you will need `uhid` to use this daemon. +If you ever need a dummy HID device to test mapping capabilities or introspect reports set by a mapping for example, you can build a dummy one using `dummy_hid_device.c`. Note that you will need `uhid` to use this daemon. Here is how to get the daemon setup. Make sure to do this **before** Mixxx is started @@ -16,7 +16,7 @@ sudo modprobe uhid # `-DVENDOR_ID=0x1234` to customize vendor ID # `-DPRODUCT_ID=0x1234` to customize product ID # `-DDEVICE_NAME="My Device"` to customize the device name -cd build && gcc ../tools/dummy_hid_device.cpp -lhidapi-hidraw -o dummy_hid_device && sudo ./dummy_hid_device +cd build && gcc ../tools/dummy_hid_device.c -lhidapi-hidraw -o dummy_hid_device && sudo ./dummy_hid_device # Allow the created hidraw device to be accesssed by the user. You may also set the write udev rules. Finally, you can also run Mixxx as root, but that's not recommended. sudo chown "$USER" "$(ls -1t /dev/hidraw* | head -n 1)" ``` diff --git a/tools/dummy_hid_device.cpp b/tools/dummy_hid_device.cpp deleted file mode 100644 index 42277f459841..000000000000 --- a/tools/dummy_hid_device.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#define _DEFAULT_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifndef VENDOR_ID -#define VENDOR_ID 0xDEAD -#endif -#ifndef PRODUCT_ID -#define PRODUCT_ID 0xBEAF -#endif -#ifndef DEVICE_NAME -#define DEVICE_NAME "test-uhid-device" -#endif - -static volatile uint32_t done; - -static unsigned char rdesc[] = { - 0x06, 0x02, 0xFF, /* Usage Page (FF02h), */ - 0x09, - 0x00, /* Usage (00h), */ - 0xA1, - 0x01, /* Collection (Application), */ - 0x09, - 0x01, /* Usage (01h), */ - 0xA1, - 0x02, /* Collection (Logical), */ - 0x85, - 0x01, /* Report ID (1), */ - 0x09, - 0x02, /* Usage (02h), */ - 0x15, - 0x00, /* Logical Minimum (0), */ - 0x25, - 0x01, /* Logical Maximum (1), */ - 0x75, - 0x01, /* Report Size (1), */ - 0x95, - 0x88, /* Report Count (136), */ - 0x81, - 0x02, /* Input (Variable), */ - 0x09, - 0x07, /* Usage (07h), */ - 0x15, - 0x00, /* Logical Minimum (0), */ - 0x25, - 0x01, /* Logical Maximum (1), */ - 0x75, - 0x01, /* Report Size (1), */ - 0x95, - 0x10, /* Report Count (16), */ - 0x81, - 0x02, /* Input (Variable), */ - 0x09, - 0x03, /* Usage (03h), */ - 0x15, - 0x00, /* Logical Minimum (0), */ - 0x25, - 0x0F, /* Logical Maximum (15), */ - 0x75, - 0x04, /* Report Size (4), */ - 0x95, - 0x06, /* Report Count (6), */ - 0x81, - 0x02, /* Input (Variable), */ - 0xC0, /* End Collection, */ - 0xC0 /* End Collection */ -}; - -void sighndlr(int signal) { - done = 1; - printf("\n"); -} - -static int uhid_write(int fd, const struct uhid_event* ev) { - ssize_t ret; - - ret = write(fd, ev, sizeof(*ev)); - if (ret < 0) { - fprintf(stderr, "Cannot write to uhid: %m\n"); - return -errno; - } else if (ret != sizeof(*ev)) { - fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", ret, sizeof(ev)); - return -EFAULT; - } else { - return 0; - } -} - -static int create(int fd) { - struct uhid_event ev; - - memset(&ev, 0, sizeof(ev)); - ev.type = UHID_CREATE; - strcpy((char*)ev.u.create.name, DEVICE_NAME); - ev.u.create.rd_data = rdesc; - ev.u.create.rd_size = sizeof(rdesc); - ev.u.create.bus = BUS_USB; - ev.u.create.vendor = VENDOR_ID; - ev.u.create.product = PRODUCT_ID; - ev.u.create.version = 0; - ev.u.create.country = 0; - - return uhid_write(fd, &ev); -} - -static void destroy(int fd) { - struct uhid_event ev; - - memset(&ev, 0, sizeof(ev)); - ev.type = UHID_DESTROY; - - uhid_write(fd, &ev); -} - -/* This parses raw output reports sent by the kernel to the device. A normal - * uhid program shouldn't do this but instead just forward the raw report. - * However, for ducomentational purposes, we try to detect LED events here and - * print debug messages for it. */ -static void handle_output(struct uhid_event* ev) { - if (ev->u.output.rtype != UHID_OUTPUT_REPORT) - return; - - fprintf(stderr, - "Output received of type %d and size %d: %s\n", - ev->u.output.rtype, - ev->u.output.size, - ev->u.output.data); - for (int i = 0; i < ev->u.output.size; i++) { - printf("%02X ", ev->u.output.data[i]); - } - printf("\n"); - return; -} -static void handle_report(struct uhid_event* ev, int fd) { - fprintf(stderr, "RType is %d\n", ev->u.get_report.rtype); - if (ev->u.get_report.rtype != UHID_START) - return; - - fprintf(stderr, "Get report for %d\n", ev->u.get_report.rnum); - - struct uhid_event rep; - - memset(&rep, 0, sizeof(rep)); - rep.type = UHID_GET_REPORT_REPLY; - - rep.u.get_report_reply.id = ev->u.get_report.id; - rep.u.get_report_reply.err = 0; - rep.u.get_report_reply.size = 255; - memset(rep.u.get_report_reply.data, 0, UHID_DATA_MAX); - - uhid_write(fd, &rep); - return; -} - -static int event(int fd) { - struct uhid_event ev; - ssize_t ret; - - memset(&ev, 0, sizeof(ev)); - ret = read(fd, &ev, sizeof(ev)); - if (ret == 0) { - fprintf(stderr, "Read HUP on uhid-cdev\n"); - return -EFAULT; - } else if (ret < 0) { - fprintf(stderr, "Cannot read uhid-cdev: %m\n"); - return -errno; - } else if (ret != sizeof(ev)) { - fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", ret, sizeof(ev)); - return -EFAULT; - } - - switch (ev.type) { - case UHID_START: - fprintf(stderr, "UHID_START from uhid-dev\n"); - break; - case UHID_STOP: - fprintf(stderr, "UHID_STOP from uhid-dev\n"); - break; - case UHID_OPEN: - fprintf(stderr, "UHID_OPEN from uhid-dev\n"); - break; - case UHID_CLOSE: - fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); - case UHID_OUTPUT: - fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); - handle_output(&ev); - break; - case UHID_OUTPUT_EV: - fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n"); - break; - case UHID_GET_REPORT: - fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); - handle_report(&ev, fd); - break; - default: - fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); - } - - return 0; -} - -int main(int argc, char** argv) { - signal(SIGINT, sighndlr); - - // UHID - int fd = open("/dev/uhid", O_RDWR); - if (fd < 0) { - perror("open"); - return -1; - } - - fprintf(stderr, "Create uhid device\n"); - if (create(fd)) { - close(fd); - return EXIT_FAILURE; - } - - struct pollfd pfds; - pfds.fd = fd; - pfds.events = POLLIN; - - while (!done) { - int ret = poll(&pfds, 1, 10); - if (ret < 0) { - fprintf(stderr, "Cannot poll for fds: %m\n"); - break; - } - if (pfds.revents & POLLHUP) { - fprintf(stderr, "Received HUP on uhid-cdev\n"); - break; - } - if (pfds.revents & POLLIN) { - ret = event(fd); - if (ret) - break; - } - } - - destroy(fd); - - return 0; -} From 61ec210159f9bfc47edef3b0727dbd9af58b9048 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 8 May 2024 15:59:46 +0100 Subject: [PATCH 07/26] Fixing assert, comment, typo const and wait for thread --- src/controllers/controllerscreenpreview.h | 1 - .../legacycontrollermappingfilehandler.cpp | 1 + .../rendering/controllerrenderingengine.cpp | 2 +- .../rendering/controllerrenderingengine.h | 3 + .../legacy/controllerscriptenginelegacy.cpp | 18 +- .../legacy/controllerscriptenginelegacy.h | 2 +- tools/dummy_hid_device.c | 251 ++++++++++++++++++ 7 files changed, 267 insertions(+), 11 deletions(-) create mode 100644 tools/dummy_hid_device.c diff --git a/src/controllers/controllerscreenpreview.h b/src/controllers/controllerscreenpreview.h index f4906c150f56..e26ece27ad23 100644 --- a/src/controllers/controllerscreenpreview.h +++ b/src/controllers/controllerscreenpreview.h @@ -9,7 +9,6 @@ #include "util/duration.h" #include "util/parented_ptr.h" - /// Widget to preview controller screen, used in preference window. This is /// useful to help when developing new screen layout, without inducing any wear /// and tear on a hardware device, or allow testing when not owning a device diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 27f34db5a2b5..9376304751f3 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -1,4 +1,5 @@ #include "controllers/legacycontrollermappingfilehandler.h" + #include "controllers/defs_controllers.h" #include "controllers/midi/legacymidicontrollermappingfilehandler.h" #include "util/logger.h" diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index fdb9370fe9da..22ba239c95ef 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -189,10 +189,10 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { } void ControllerRenderingEngine::finish() { - m_isValid = false; disconnect(this); const auto lock = lockMutex(&s_glMutex); + m_isValid = false; if (m_context && m_context->isValid()) { disconnect(m_context.get(), diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 396936d95a1f..5fd9d6ad91f7 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -68,6 +68,9 @@ class ControllerRenderingEngine : public QObject { const QDateTime& timestamp); void setupRequested(std::shared_ptr engine); void stopRequested(); + /// @brief Request the screen thread to send a frame to the device + /// @param controller the controller to send the frame to + /// @param frame the frame data, ready to be sent void sendRequested(Controller* controller, const QByteArray& frame); private: diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index a6bc699143fd..489214145250 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -25,16 +25,16 @@ namespace { #ifdef MIXXX_USE_QML -QByteArray kScreenTransformFunctionUntypedSignature = +const QByteArray kScreenTransformFunctionUntypedSignature = QMetaObject::normalizedSignature( "transformFrame(QVariant,QVariant)"); -QByteArray kScreenTransformFunctionTypedSignature = +const QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); template void delete_later(T* screen) { screen->deleteLater(); -}; +} #endif } // anonymous namespace @@ -427,9 +427,11 @@ bool ControllerScriptEngineLegacy::initialize() { } while (!availableScreens.isEmpty()) { - std::shared_ptr orphanScreen( - nullptr, &delete_later); - availableScreens.take(availableScreens.firstKey()).swap(orphanScreen); + auto pScreen = availableScreens.take(availableScreens.firstKey()); + VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || + !pScreen->isRunning() || pScreen->stop()) { + qCWarning(m_logger) << "Unable to stop the screen"; + }; } } if (sceneBindingHasFailure) { @@ -482,7 +484,7 @@ bool ControllerScriptEngineLegacy::initialize() { } #ifdef MIXXX_USE_QML -void ControllerScriptEngineLegacy::extractTranformFunction( +void ControllerScriptEngineLegacy::extractTransformFunction( const QMetaObject* metaObject, const QString& screenIdentifier) { VERIFY_OR_DEBUG_ASSERT(metaObject) { qCWarning(m_logger) @@ -538,7 +540,7 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( } const QMetaObject* metaObject = pScene->metaObject(); - extractTranformFunction(metaObject, screenIdentifier); + extractTransformFunction(metaObject, screenIdentifier); connect(pScreen.get(), &ControllerRenderingEngine::frameRendered, diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index c4ff9a67c4e3..3b5c391057dd 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -82,7 +82,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { const LegacyControllerMapping::ScriptFileInfo& qmlFile, const QString& screenIdentifier, std::shared_ptr pScreen); - void extractTranformFunction(const QMetaObject* metaObject, const QString& screenIdentifier); + void extractTransformFunction(const QMetaObject* metaObject, const QString& screenIdentifier); std::shared_ptr loadQMLFile( const LegacyControllerMapping::ScriptFileInfo& qmlScript, diff --git a/tools/dummy_hid_device.c b/tools/dummy_hid_device.c new file mode 100644 index 000000000000..ec3c392c5094 --- /dev/null +++ b/tools/dummy_hid_device.c @@ -0,0 +1,251 @@ +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef VENDOR_ID +#define VENDOR_ID 0xDEAD +#endif +#ifndef PRODUCT_ID +#define PRODUCT_ID 0xBEAF +#endif +#ifndef DEVICE_NAME +#define DEVICE_NAME "test-uhid-device" +#endif + +static volatile u_int32_t done; + +static unsigned char rdesc[] = { + 0x06, 0x02, 0xFF, /* Usage Page (FF02h), */ + 0x09, + 0x00, /* Usage (00h), */ + 0xA1, + 0x01, /* Collection (Application), */ + 0x09, + 0x01, /* Usage (01h), */ + 0xA1, + 0x02, /* Collection (Logical), */ + 0x85, + 0x01, /* Report ID (1), */ + 0x09, + 0x02, /* Usage (02h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x01, /* Logical Maximum (1), */ + 0x75, + 0x01, /* Report Size (1), */ + 0x95, + 0x88, /* Report Count (136), */ + 0x81, + 0x02, /* Input (Variable), */ + 0x09, + 0x07, /* Usage (07h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x01, /* Logical Maximum (1), */ + 0x75, + 0x01, /* Report Size (1), */ + 0x95, + 0x10, /* Report Count (16), */ + 0x81, + 0x02, /* Input (Variable), */ + 0x09, + 0x03, /* Usage (03h), */ + 0x15, + 0x00, /* Logical Minimum (0), */ + 0x25, + 0x0F, /* Logical Maximum (15), */ + 0x75, + 0x04, /* Report Size (4), */ + 0x95, + 0x06, /* Report Count (6), */ + 0x81, + 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +void sighndlr(int signal) { + done = 1; + printf("\n"); +} + +static int uhid_write(int fd, const struct uhid_event* ev) { + ssize_t ret; + + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %s\n", strerror(errno)); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", ret, sizeof(ev)); + return -1; + } else { + return 0; + } +} + +static int create(int fd) { + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char*)ev.u.create.name, DEVICE_NAME); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = VENDOR_ID; + ev.u.create.product = PRODUCT_ID; + ev.u.create.version = 0; + ev.u.create.country = 0; + + return uhid_write(fd, &ev); +} + +static void destroy(int fd) { + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +/* This parses raw output reports sent by the kernel to the device. A normal + * uhid program shouldn't do this but instead just forward the raw report. + * However, for ducomentational purposes, we try to detect LED events here and + * print debug messages for it. */ +static void handle_output(struct uhid_event* ev) { + if (ev->u.output.rtype != UHID_OUTPUT_REPORT) + return; + + fprintf(stderr, + "Output received of type %d and size %d: %s\n", + ev->u.output.rtype, + ev->u.output.size, + ev->u.output.data); + for (int i = 0; i < ev->u.output.size; i++) { + printf("%02X ", ev->u.output.data[i]); + } + printf("\n"); + return; +} +static void handle_report(struct uhid_event* ev, int fd) { + fprintf(stderr, "RType is %d\n", ev->u.get_report.rtype); + if (ev->u.get_report.rtype != UHID_START) + return; + + fprintf(stderr, "Get report for %d\n", ev->u.get_report.rnum); + + struct uhid_event rep; + + memset(&rep, 0, sizeof(rep)); + rep.type = UHID_GET_REPORT_REPLY; + + rep.u.get_report_reply.id = ev->u.get_report.id; + rep.u.get_report_reply.err = 0; + rep.u.get_report_reply.size = 255; + memset(rep.u.get_report_reply.data, 0, UHID_DATA_MAX); + + uhid_write(fd, &rep); + return; +} + +static int event(int fd) { + struct uhid_event ev; + ssize_t ret; + + memset(&ev, 0, sizeof(ev)); + ret = read(fd, &ev, sizeof(ev)); + if (ret == 0) { + fprintf(stderr, "Read HUP on uhid-cdev\n"); + return -1; + } else if (ret < 0) { + fprintf(stderr, "Cannot read uhid-cdev\n"); + return -errno; + } else if (ret != sizeof(ev)) { + fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", ret, sizeof(ev)); + return -1; + } + + switch (ev.type) { + case UHID_START: + fprintf(stderr, "UHID_START from uhid-dev\n"); + break; + case UHID_STOP: + fprintf(stderr, "UHID_STOP from uhid-dev\n"); + break; + case UHID_OPEN: + fprintf(stderr, "UHID_OPEN from uhid-dev\n"); + break; + case UHID_CLOSE: + fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); + case UHID_OUTPUT: + fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); + handle_output(&ev); + break; + case UHID_OUTPUT_EV: + fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n"); + break; + case UHID_GET_REPORT: + fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + handle_report(&ev, fd); + break; + default: + fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); + } + + return 0; +} + +int main(int argc, char** argv) { + signal(SIGINT, sighndlr); + + // UHID + int fd = open("/dev/uhid", O_RDWR); + if (fd < 0) { + perror("open"); + return -1; + } + + fprintf(stderr, "Create uhid device\n"); + if (create(fd)) { + close(fd); + return EXIT_FAILURE; + } + + struct pollfd pfds; + pfds.fd = fd; + pfds.events = POLLIN; + + while (!done) { + int ret = poll(&pfds, 1, 10); + if (ret < 0) { + fprintf(stderr, "Cannot poll for fds: %m\n"); + break; + } + if (pfds.revents & POLLHUP) { + fprintf(stderr, "Received HUP on uhid-cdev\n"); + break; + } + if (pfds.revents & POLLIN) { + ret = event(fd); + if (ret) + break; + } + } + + destroy(fd); + + return 0; +} From 6bb223555a4ea66a4bed71f8453ba96f04c9f865 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 8 May 2024 19:26:19 +0100 Subject: [PATCH 08/26] Fix test --- .../controller_mapping_file_handler_test.cpp | 57 ++++++++++--------- src/test/controllerrenderingengine_test.cpp | 5 +- .../controllerscriptenginelegacy_test.cpp | 10 ++-- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index b4cf117235c6..858f55c4ef02 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -58,6 +58,7 @@ class MockLegacyControllerMapping : public LegacyControllerMapping { (const QString& identifier, const QSize& size, uint targetFps, + uint msaa, uint splashoff, QImage::Format pixelFormat, std::endian endian, @@ -103,7 +104,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseSimpleMapping) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, false)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_CALL(*mapping, addModule(_, _)).Times(0); addScriptFilesToMapping( @@ -147,6 +148,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { addScreenInfo(QString("main"), QSize(480, 360), 20, + 1, 2000, QImage::Format_RGBA8888, std::endian::little, @@ -179,7 +181,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, 20, _, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, 20, _, _, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -204,7 +206,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG(QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") .arg(s_maxTargetFps)); @@ -233,7 +235,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -263,7 +265,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -293,7 +295,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -325,7 +327,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, QSize(10, 10), _, _, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, QSize(10, 10), _, _, _, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -350,7 +352,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -378,7 +380,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -406,7 +408,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -434,7 +436,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -469,7 +471,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, QImage::Format_RGB888, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB888, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -493,7 +495,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, QImage::Format_RGB16, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB16, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -517,7 +519,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Unsupported pixel format \"FOOBAR\""); @@ -545,7 +547,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::little, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::little, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -569,7 +571,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::little, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::little, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -593,7 +595,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, std::endian::big, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::big, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -617,7 +619,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Unknown endian format \"enormous\""); @@ -656,7 +658,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, false, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, false, _)); addScriptFilesToMapping( doc.documentElement(), @@ -683,7 +685,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, true, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, true, _)); addScriptFilesToMapping( doc.documentElement(), @@ -711,7 +713,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, false)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, false)); addScriptFilesToMapping( doc.documentElement(), @@ -738,7 +740,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, true)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, true)); addScriptFilesToMapping( doc.documentElement(), @@ -768,7 +770,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -792,7 +794,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 500, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 500, _, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -816,7 +818,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, s_maxSplashOffDuration, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, s_maxSplashOffDuration, _, _, _, _)); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid splashoff duration. Splashoff duration must " @@ -847,7 +849,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -871,7 +873,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 0, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -922,6 +924,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { addScreenInfo(QString("main"), QSize(480, 360), 20, + 1, 2000, QImage::Format_RGBA8888, std::endian::little, diff --git a/src/test/controllerrenderingengine_test.cpp b/src/test/controllerrenderingengine_test.cpp index 0a0a3c1b52ab..49b5d2e16e77 100644 --- a/src/test/controllerrenderingengine_test.cpp +++ b/src/test/controllerrenderingengine_test.cpp @@ -33,16 +33,17 @@ class MockRenderingEngine : public ControllerRenderingEngine { TEST_F(ControllerRenderingEngineTest, createValidRendererWithSupportedTypes) { for (auto pixelFormat : supportedPixelFormat()) { - MockRenderingEngine screenTest(LegacyControllerMapping::ScreenInfo( + MockRenderingEngine screenTest(LegacyControllerMapping::ScreenInfo{ "", // identifier QSize(0, 0), // size 10, // target_fps + 1, // msaa 10, // splash_off pixelFormat, // pixelFormat std::endian::big, // endian false, // reversedColor false // rawData - )); + }); EXPECT_TRUE(screenTest.isValid()); EXPECT_TRUE(screenTest.stop()); } diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index 475e7b60c4c0..b21cb72bc4a5 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -668,16 +668,17 @@ class MockScreenRender : public ControllerRenderingEngine { TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { SETUP_LOG_CAPTURE(); - LegacyControllerMapping::ScreenInfo dummyScreen( + LegacyControllerMapping::ScreenInfo dummyScreen{ "", // identifier QSize(0, 0), // size 10, // target_fps + 1, // msaa 10, // splash_off QImage::Format_RGB16, // pixelFormat std::endian::big, // endian false, // rawData false // reversedColor - ); + }; QImage dummyFrame; // Allocate screen on the heap as it need to outlive the this function, // since the engine will take ownership of it @@ -706,16 +707,17 @@ TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { TEST_F(ControllerScriptEngineLegacyTest, screenWillSentRawDataIfConfigured) { SETUP_LOG_CAPTURE(); - LegacyControllerMapping::ScreenInfo dummyScreen( + LegacyControllerMapping::ScreenInfo dummyScreen{ "", // identifier QSize(0, 0), // size 10, // target_fps + 1, // msaa 10, // splash_off QImage::Format_RGB16, // pixelFormat std::endian::big, // endian false, // reversedColor true // rawData - ); + }; QImage dummyFrame; // Allocate screen on the heap as it need to outlive the this function, // since the engine will take ownership of it From e864684785207f4cebe30f5cfe8d629736131743 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 8 May 2024 23:39:08 +0100 Subject: [PATCH 09/26] Remove unnecessary mutex and removing code duplication --- src/controllers/controllerscreenpreview.cpp | 11 +- src/controllers/controllerscreenpreview.h | 3 +- src/controllers/dlgprefcontroller.cpp | 2 +- src/controllers/legacycontrollermapping.h | 42 +++-- .../legacycontrollermappingfilehandler.cpp | 178 +++++++++++------- .../legacycontrollermappingfilehandler.h | 23 +-- .../rendering/controllerrenderingengine.cpp | 118 ++++-------- .../rendering/controllerrenderingengine.h | 15 +- .../scripting/controllerscriptenginebase.cpp | 19 +- .../scripting/controllerscriptenginebase.h | 1 + .../legacy/controllerscriptenginelegacy.cpp | 55 +++--- .../controller_mapping_file_handler_test.cpp | 167 +++++++++++----- src/test/controllerrenderingengine_test.cpp | 18 +- .../controllerscriptenginelegacy_test.cpp | 45 ++--- 14 files changed, 391 insertions(+), 306 deletions(-) diff --git a/src/controllers/controllerscreenpreview.cpp b/src/controllers/controllerscreenpreview.cpp index 3903a275c1ef..30b655317bb1 100644 --- a/src/controllers/controllerscreenpreview.cpp +++ b/src/controllers/controllerscreenpreview.cpp @@ -8,11 +8,10 @@ namespace { /// Number of sample frame timestamp sample to perform a smooth average FPS label. -constexpr double kFrameSmoothAverageFactor = 5; +constexpr double kFrameSmoothAverageFactor = 20; } // namespace using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; ControllerScreenPreview::ControllerScreenPreview( QWidget* parent, const LegacyControllerMapping::ScreenInfo& screen) @@ -45,19 +44,19 @@ void ControllerScreenPreview::updateFrame( m_pFrame->setPixmap(QPixmap::fromImage(frame)); auto currentTimestamp = Clock::now(); - if (m_lastFrameTimestamp == TimePoint()) { + if (m_lastFrameTimestamp == Clock::time_point()) { m_lastFrameTimestamp = currentTimestamp; return; } if (m_averageFrameDuration == 0) { m_averageFrameDuration = - std::chrono::duration_cast( + std::chrono::duration_cast( currentTimestamp - m_lastFrameTimestamp) .count(); } else { m_averageFrameDuration = std::lerp(m_averageFrameDuration, - std::chrono::duration_cast( + std::chrono::duration_cast( currentTimestamp - m_lastFrameTimestamp) .count(), 1.0 / kFrameSmoothAverageFactor); @@ -65,6 +64,6 @@ void ControllerScreenPreview::updateFrame( m_lastFrameTimestamp = currentTimestamp; m_pStat->setText(tr("FPS: %0/%1") .arg(QString::number(static_cast( - 1000 / m_averageFrameDuration)), + 1000000 / m_averageFrameDuration)), QString::number(m_screenInfo.target_fps))); } diff --git a/src/controllers/controllerscreenpreview.h b/src/controllers/controllerscreenpreview.h index e26ece27ad23..6c4ef24784a0 100644 --- a/src/controllers/controllerscreenpreview.h +++ b/src/controllers/controllerscreenpreview.h @@ -30,5 +30,6 @@ class ControllerScreenPreview : public QWidget { parented_ptr m_pStat; double m_averageFrameDuration; - std::chrono::time_point m_lastFrameTimestamp; + using Clock = std::chrono::steady_clock; + Clock::time_point m_lastFrameTimestamp; }; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index ced296e81596..21419dce8548 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -433,7 +433,7 @@ QString DlgPrefController::mappingFileLinks( qmlLibrary.dirinfo.absoluteFilePath()); if (!qmlLibrary.dirinfo.exists()) { scriptFileLink += - tr(" (missing)"); + QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); } else if (qmlLibrary.dirinfo.absoluteFilePath().startsWith( systemMappingPath)) { scriptFileLink += builtinFileSuffix; diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 5bcd414790da..fd918a9236b1 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "controllers/legacycontrollersettings.h" @@ -55,10 +56,10 @@ class LegacyControllerMapping { virtual std::shared_ptr clone() const = 0; struct ScriptFileInfo { - enum Type { - JAVASCRIPT, + enum class Type { + Javascript, #ifdef MIXXX_USE_QML - QML, + Qml, #endif }; @@ -99,15 +100,26 @@ class LegacyControllerMapping { }; struct ScreenInfo { - QString identifier; - QSize size; - uint target_fps; - uint msaa; - uint splash_off; - QImage::Format pixelFormat; - std::endian endian; - bool reversedColor; - bool rawData; + // Defining a custom enum here as std::endian contains `native` which is + // confusing and will have unpredictable behaviour depending of the + // platform. + enum class ColorEndian { + Big = __ORDER_BIG_ENDIAN__, + Little = __ORDER_LITTLE_ENDIAN__, + }; + + QString identifier; // The screen identifier + QSize size; // the size of the screen + uint target_fps; // the maximum FPS to render + uint msaa; // the MSAA value to use for render + std::chrono::milliseconds + splash_off; // the rendering grace time given when the screen is + // requested to shutdown + QImage::Format pixelFormat; // the pixel encoding format + ColorEndian endian; // the pixel endian format + bool reversedColor; // whether or not the RGB is swapped BGR + bool rawData; // whether or not the screen is allowed to receive bare + // data, not transformed }; #endif @@ -121,7 +133,7 @@ class LegacyControllerMapping { virtual void addScriptFile(const QString& name, const QString& identifier, const QFileInfo& file, - ScriptFileInfo::Type type = ScriptFileInfo::Type::JAVASCRIPT, + ScriptFileInfo::Type type = ScriptFileInfo::Type::Javascript, bool builtin = false) { ScriptFileInfo info; info.name = name; @@ -223,9 +235,9 @@ class LegacyControllerMapping { const QSize& size, uint targetFps, uint msaa, - uint splashoff, + std::chrono::milliseconds splashoff, QImage::Format pixelFormat, - std::endian endian, + LegacyControllerMapping::ScreenInfo::ColorEndian endian, bool reversedColor, bool rawData) { m_screens.append(ScreenInfo{identifier, diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 9376304751f3..321b806b8daa 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -16,9 +16,12 @@ QMap LegacyControllerMappingFileHandler::kSupportedPixe {"RGB565", QImage::Format_RGB16}, }; -QMap LegacyControllerMappingFileHandler::kEndianFormat = { - {"big", std::endian::big}, - {"little", std::endian::little}, +QMap + LegacyControllerMappingFileHandler::kEndianFormat = { + {"big", LegacyControllerMapping::ScreenInfo::ColorEndian::Big}, + {"little", + LegacyControllerMapping::ScreenInfo::ColorEndian:: + Little}, }; #endif namespace { @@ -51,7 +54,10 @@ QFileInfo findLibraryPath( /// @brief Parse a string that contain a boolean value in human representation /// @param value the string containing the boolean setting /// @return true for string value "true", false otherwise -bool parseHumanBoolean(const QString& value) { +bool parseHumanBoolean(const QString& value, bool* ok) { + if (ok) { + *ok = value == QStringLiteral("true") || value == QStringLiteral("false"); + } return value == QStringLiteral("true"); } #endif @@ -77,6 +83,101 @@ QFileInfo findScriptFile(std::shared_ptr mapping, return file; } +#ifdef MIXXX_USE_QML +#define LOG_OF_NOT_OK(FIELD, TYPE) \ + if (!ok) { \ + kLogger.warning().nospace() \ + << "Unable to parse the field \"" << FIELD << "\" as " << TYPE \ + << " in the screen definition."; \ + } +/// @brief Parse the screen info from the XML definition and add it to the mapping object +/// @param screen the screen XML definition +/// @param mapping the mapping being parsed +/// @return true if the screen definition could be parse, false otherwise +bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapping* mapping) { + bool ok; + QString identifier = screen.attribute("identifier", ""); + uint targetFps = screen.attribute("targetFps", "30").toUInt(&ok); + LOG_OF_NOT_OK("targetFps", "an unsigned integer"); + uint msaa = screen.attribute("msaa", "1").toUInt(&ok); + LOG_OF_NOT_OK("msaa", "an unsigned integer"); + QString pixelFormatName = screen.attribute("pixelType", "RBG"); + QString endianName = screen.attribute("endian", "little"); + bool reversedColor = parseHumanBoolean( + screen.attribute("reversed", "false").toLower().trimmed(), &ok); + LOG_OF_NOT_OK("reversed", "a boolean"); + bool rawData = parseHumanBoolean(screen.attribute("raw", "false").toLower().trimmed(), &ok); + LOG_OF_NOT_OK("raw", "a boolean"); + uint splashOff = screen.attribute("splashoff", "0").toUInt(&ok); + LOG_OF_NOT_OK("splashoff", "an unsigned integer"); + + if (!targetFps || targetFps > LegacyControllerMappingFileHandler::s_maxTargetFps) { + kLogger.warning() + << "Invalid target FPS. Target FPS must be between 1 and" + << LegacyControllerMappingFileHandler::s_maxTargetFps; + return false; + } + if (!msaa || msaa > LegacyControllerMappingFileHandler::s_maxMsaa) { + kLogger.warning() + << "Invalid MSAA value. MSAA value must be between 1 and" + << LegacyControllerMappingFileHandler::s_maxMsaa; + return false; + } + + if (splashOff > LegacyControllerMappingFileHandler::s_maxSplashOffDuration) { + kLogger.warning() + << QString("Invalid splashoff duration. Splashoff duration " + "must be " + "between 0 and %1. Clamping to %2") + .arg(QString::number( + LegacyControllerMappingFileHandler:: + s_maxSplashOffDuration), + QString::number( + LegacyControllerMappingFileHandler:: + s_maxSplashOffDuration)); + splashOff = LegacyControllerMappingFileHandler::s_maxSplashOffDuration; + } + + if (!LegacyControllerMappingFileHandler::kSupportedPixelFormat.contains(pixelFormatName)) { + kLogger.warning() << "Unsupported pixel format" << pixelFormatName; + return false; + } + + if (!LegacyControllerMappingFileHandler::kEndianFormat.contains(endianName)) { + kLogger.warning() << "Unknown endian format" << endianName; + return false; + } + + QImage::Format pixelFormat = + LegacyControllerMappingFileHandler::kSupportedPixelFormat.value( + pixelFormatName); + auto endian = LegacyControllerMappingFileHandler::kEndianFormat.value(endianName); + + uint width = screen.attribute("width", "0").toUInt(&ok); + LOG_OF_NOT_OK("width", "an unsigned integer"); + uint height = screen.attribute("height", "0").toUInt(&ok); + LOG_OF_NOT_OK("height", "an unsigned integer"); + + if (!width || !height) { + kLogger.warning() << "Invalid screen size. Screen size must have a width " + "and height above 1 pixel"; + return false; + } + + kLogger.debug() << "Adding screen" << identifier; + mapping->addScreenInfo(identifier, + QSize(width, height), + targetFps, + msaa, + std::chrono::milliseconds(splashOff), + pixelFormat, + endian, + reversedColor, + rawData); + return true; +} +#endif + } // namespace // static @@ -276,7 +377,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( mapping->addScriptFile(REQUIRED_SCRIPT_FILE, "", findScriptFile(mapping, REQUIRED_SCRIPT_FILE, systemMappingsPath), - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true); // Look for additional ones @@ -289,7 +390,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( mapping->addScriptFile(filename, identifier, file, - LegacyControllerMapping::ScriptFileInfo::Type::QML); + LegacyControllerMapping::ScriptFileInfo::Type::Qml); #else kLogger.warning() << "Unsupported render scene for file" << file.filePath() @@ -301,7 +402,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( mapping->addScriptFile(filename, functionPrefix, file, - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT); + LegacyControllerMapping::ScriptFileInfo::Type::Javascript); } scriptFile = scriptFile.nextSiblingElement("file"); } @@ -313,70 +414,9 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( // Look for additional ones while (!screen.isNull()) { - QString identifier = screen.attribute("identifier", ""); - uint targetFps = screen.attribute("targetFps", "30").toUInt(); - uint msaa = screen.attribute("msaa", "1").toUInt(); - QString pixelFormatName = screen.attribute("pixelType", "RBG"); - QString endianName = screen.attribute("endian", "little"); - bool reversedColor = parseHumanBoolean( - screen.attribute("reversed", "false").toLower().trimmed()); - bool rawData = parseHumanBoolean(screen.attribute("raw", "false").toLower().trimmed()); - uint splashOff = screen.attribute("splashoff", "0").toUInt(); - - if (!targetFps || targetFps > s_maxTargetFps) { - kLogger.warning() - << "Invalid target FPS. Target FPS must be between 1 and" - << s_maxTargetFps; + if (!parseAndAddScreenDefinition(screen, mapping.get())) { return; } - if (!msaa || msaa > s_maxMsaa) { - kLogger.warning() - << "Invalid MSAA value. MSAA value must be between 1 and" - << s_maxMsaa; - return; - } - - if (splashOff > s_maxSplashOffDuration) { - kLogger.warning() << QString( - "Invalid splashoff duration. Splashoff duration must be " - "between 0 and %1. Clamping to %2") - .arg(s_maxSplashOffDuration) - .arg(s_maxSplashOffDuration); - splashOff = s_maxSplashOffDuration; - } - - if (!kSupportedPixelFormat.contains(pixelFormatName)) { - kLogger.warning() << "Unsupported pixel format" << pixelFormatName; - return; - } - - if (!kEndianFormat.contains(endianName)) { - kLogger.warning() << "Unknown endian format" << endianName; - return; - } - - QImage::Format pixelFormat = kSupportedPixelFormat.value(pixelFormatName); - std::endian endian = kEndianFormat.value(endianName); - - uint width = screen.attribute("width", "0").toUInt(); - uint height = screen.attribute("height", "0").toUInt(); - - if (!width || !height) { - kLogger.warning() << "Invalid screen size. Screen size must have a width " - "and height above 1 pixel"; - return; - } - - kLogger.debug() << "Adding screen " << identifier; - mapping->addScreenInfo(identifier, - QSize(width, height), - targetFps, - msaa, - splashOff, - pixelFormat, - endian, - reversedColor, - rawData); screen = screen.nextSiblingElement("screen"); } // Build a list of QML files to load diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index e1339910105e..c6a490c4ad5f 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -7,11 +7,14 @@ #include #include #include + +#include "controllers/legacycontrollermapping.h" +#else +class LegacyControllerMapping; #endif class QFileInfo; class QDir; -class LegacyControllerMapping; class LegacyControllerSettingsLayoutContainer; /// The LegacyControllerMappingFileHandler is used for serializing/deserializing the @@ -72,15 +75,6 @@ class LegacyControllerMappingFileHandler { bool writeDocument(const QDomDocument& root, const QString& fileName) const; -#ifdef MIXXX_USE_QML - // Maximum target frame per request for a screen controller - static constexpr int s_maxTargetFps = 240; - // Maximum MSAA value that can be used - static constexpr int s_maxMsaa = 16; - // Maximum time allowed for a screen to run a splash off animation - static constexpr int s_maxSplashOffDuration = 3000; -#endif - private: /// @brief Recursively parse setting definition and layout information /// within a setting node @@ -99,8 +93,15 @@ class LegacyControllerMappingFileHandler { const QDir& systemMappingPath) = 0; #ifdef MIXXX_USE_QML + public: static QMap kSupportedPixelFormat; - static QMap kEndianFormat; + static QMap kEndianFormat; + // Maximum target frame per request for a screen controller + static const int s_maxTargetFps = 240; + // Maximum MSAA value that can be used + static const int s_maxMsaa = 16; + // Maximum time allowed for a screen to run a splash off animation + static const int s_maxSplashOffDuration = 3000; friend class ControllerRenderingEngineTest; #endif diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 22ba239c95ef..64c07eabce9c 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -21,14 +21,19 @@ #include "util/time.h" #include "util/timer.h" +// Used in the renderFrame method to properly abort the rendering and terminate the engine +#define VERIFY_OR_TERMINATE(cond, msg) \ + VERIFY_OR_DEBUG_ASSERT(cond) { \ + kLogger.warning() << msg; \ + finish(); \ + return; \ + } + namespace { const mixxx::Logger kLogger("ControllerRenderingEngine"); } // anonymous namespace using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; - -QMutex ControllerRenderingEngine::s_glMutex = QMutex(); ControllerRenderingEngine::ControllerRenderingEngine( const LegacyControllerMapping::ScreenInfo& info, @@ -71,17 +76,19 @@ void ControllerRenderingEngine::prepare() { m_pThread->setObjectName("ControllerScreenRenderer"); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) - DEBUG_ASSERT(moveToThread(m_pThread.get())); + [[maybe_unused]] bool successful = moveToThread(m_pThread.get()); + DEBUG_ASSERT(successful); #else moveToThread(m_pThread.get()); #endif + // these at first sight weird-looking connections facilitate thread-safe communication. connect(this, - &ControllerRenderingEngine::setupRequested, + &ControllerRenderingEngine::engineSetupRequested, this, &ControllerRenderingEngine::setup); connect(this, - &ControllerRenderingEngine::sendRequested, + &ControllerRenderingEngine::sendFrameDataRequested, this, &ControllerRenderingEngine::send); connect(this, @@ -111,7 +118,7 @@ bool ControllerRenderingEngine::isRunning() const { return m_pThread && m_pThread->isRunning(); } -void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngine) { +void ControllerRenderingEngine::requestEngineSetup(std::shared_ptr qmlEngine) { m_isValid = false; VERIFY_OR_DEBUG_ASSERT(qmlEngine) { kLogger.critical() << "No QML engine was passed!"; @@ -122,7 +129,7 @@ void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngi "thread as the render object"; return; } - emit setupRequested(qmlEngine); + emit engineSetupRequested(qmlEngine); const auto lock = lockMutex(&m_mutex); if (!m_quickWindow) { @@ -133,18 +140,18 @@ void ControllerRenderingEngine::requestSetup(std::shared_ptr qmlEngi } } -void ControllerRenderingEngine::requestSend(Controller* controller, const QByteArray& frame) { - emit sendRequested(controller, frame); +void ControllerRenderingEngine::requestSendingFrameData( + Controller* controller, const QByteArray& frame) { + emit sendFrameDataRequested(controller, frame); } void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { + DEBUG_ASSERT(QThread::currentThread() == thread()); QSurfaceFormat format; format.setSamples(m_screenInfo.msaa); format.setDepthBufferSize(16); format.setStencilBufferSize(8); - const auto lock = lockMutex(&s_glMutex); - m_context = std::make_unique(); m_context->setFormat(format); VERIFY_OR_DEBUG_ASSERT(m_context->create()) { @@ -190,8 +197,6 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { void ControllerRenderingEngine::finish() { disconnect(this); - - const auto lock = lockMutex(&s_glMutex); m_isValid = false; if (m_context && m_context->isValid()) { @@ -226,34 +231,16 @@ void ControllerRenderingEngine::renderFrame() { return; } - VERIFY_OR_DEBUG_ASSERT(m_offscreenSurface->isValid()) { - kLogger.warning() << "OffscreenSurface isn't valid anymore."; - finish(); - return; - }; - VERIFY_OR_DEBUG_ASSERT(m_context->isValid()) { - kLogger.warning() << "GLContext isn't valid anymore."; - finish(); - return; - }; - - auto lock = lockMutex(&s_glMutex); - - VERIFY_OR_DEBUG_ASSERT(m_context->makeCurrent(m_offscreenSurface.get())) { - kLogger.warning() << "Couldn't make the GLContext current to the OffscreenSurface."; - lock.unlock(); - finish(); - return; - }; + VERIFY_OR_TERMINATE(m_offscreenSurface->isValid(), "OffscreenSurface isn't valid anymore."); + VERIFY_OR_TERMINATE(m_context->isValid(), "GLContext isn't valid anymore."); + VERIFY_OR_TERMINATE(m_context->makeCurrent(m_offscreenSurface.get()), + "Couldn't make the GLContext current to the OffscreenSurface."); if (!m_fbo) { ScopedTimer t(u"ControllerRenderingEngine::renderFrame::initFBO"); - VERIFY_OR_DEBUG_ASSERT(QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) { - kLogger.warning() << "OpenGL doesn't support FBO"; - lock.unlock(); - finish(); - return; - }; + VERIFY_OR_TERMINATE( + QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(), + "OpenGL doesn't support FBO"); m_fbo = std::make_unique( m_screenInfo.size, QOpenGLFramebufferObject::CombinedDepthStencil); @@ -261,28 +248,13 @@ void ControllerRenderingEngine::renderFrame() { GLenum glError; glError = m_context->functions()->glGetError(); - VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - kLogger.warning() << "GLError: " << glError; - lock.unlock(); - finish(); - return; - }; - - VERIFY_OR_DEBUG_ASSERT(m_fbo->isValid()) { - kLogger.warning() << "Failed to initialize FBO"; - lock.unlock(); - finish(); - return; - }; + VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError); + VERIFY_OR_TERMINATE(m_fbo->isValid(), "Failed to initialize FBO"); m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(m_context.get())); - VERIFY_OR_DEBUG_ASSERT(m_renderControl->initialize()) { - kLogger.warning() << "Failed to initialize redirected Qt Quick rendering"; - lock.unlock(); - finish(); - return; - }; + VERIFY_OR_TERMINATE(m_renderControl->initialize(), + "Failed to initialize redirected Qt Quick rendering"); m_quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(m_fbo->texture(), m_screenInfo.size)); @@ -304,7 +276,6 @@ void ControllerRenderingEngine::renderFrame() { ScopedTimer t(u"ControllerRenderingEngine::renderFrame::sync"); VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { kLogger.warning() << "Couldn't sync the render control."; - lock.unlock(); finish(); if (m_pControllerEngine) { m_pControllerEngine->resume(); @@ -325,29 +296,20 @@ void ControllerRenderingEngine::renderFrame() { GLenum glError; m_context->functions()->glFlush(); glError = m_context->functions()->glGetError(); - VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - kLogger.warning() << "GLError: " << glError; - lock.unlock(); - finish(); - return; - } - if (m_screenInfo.endian != std::endian::native) { + VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError); + if (static_cast(m_screenInfo.endian) != std::endian::native) { m_context->functions()->glPixelStorei(GL_PACK_SWAP_BYTES, GL_TRUE); } glError = m_context->functions()->glGetError(); - VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - kLogger.warning() << "GLError: " << glError; - lock.unlock(); - finish(); - return; - } + VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError); QDateTime timestamp = QDateTime::currentDateTime(); m_renderControl->render(); m_renderControl->endFrame(); // Flush any remaining GL errors - while (m_context->functions()->glGetError()) { + while ((glError = m_context->functions()->glGetError()) != GL_NO_ERROR) { + kLogger.debug() << "Retrieved a previously unhandled GL error: " << glError; } { ScopedTimer t(u"ControllerRenderingEngine::renderFrame::glReadPixels"); @@ -360,12 +322,7 @@ void ControllerRenderingEngine::renderFrame() { fboImage.bits()); } glError = m_context->functions()->glGetError(); - VERIFY_OR_DEBUG_ASSERT(glError == GL_NO_ERROR) { - kLogger.warning() << "GLError: " << glError; - lock.unlock(); - finish(); - return; - } + VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError); VERIFY_OR_DEBUG_ASSERT(!fboImage.isNull()) { kLogger.warning() << "Screen frame is null!"; } @@ -386,6 +343,7 @@ bool ControllerRenderingEngine::stop() { } void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { + DEBUG_ASSERT(QThread::currentThread() == thread()); ScopedTimer t(u"ControllerRenderingEngine::send"); if (!frame.isEmpty()) { controller->sendBytes(frame); @@ -402,7 +360,7 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f << "milliseconds and frame has" << frame.size() << "bytes"; } - m_nextFrameStart += std::chrono::milliseconds(1000 / m_screenInfo.target_fps); + m_nextFrameStart += std::chrono::microseconds(1000000 / m_screenInfo.target_fps); auto durationToWaitBeforeFrame = std::chrono::duration_cast( diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 5fd9d6ad91f7..d5bafc4b80fc 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -32,7 +32,7 @@ class ControllerRenderingEngine : public QObject { bool event(QEvent* event) override; - const QSize& size() const { + QSize screenSize() const { return m_screenInfo.size; } @@ -42,6 +42,7 @@ class ControllerRenderingEngine : public QObject { bool isRunning() const; + // pointer lives as long as the `ControllerRenderingEngine` instance it is retrieved from. QQuickWindow* quickWindow() const { return m_quickWindow.get(); } @@ -51,8 +52,8 @@ class ControllerRenderingEngine : public QObject { } public slots: - virtual void requestSend(Controller* controller, const QByteArray& frame); - void requestSetup(std::shared_ptr qmlEngine); + virtual void requestSendingFrameData(Controller* controller, const QByteArray& frame); + void requestEngineSetup(std::shared_ptr qmlEngine); void start(); virtual bool stop(); @@ -66,12 +67,12 @@ class ControllerRenderingEngine : public QObject { void frameRendered(const LegacyControllerMapping::ScreenInfo& screeninfo, QImage frame, const QDateTime& timestamp); - void setupRequested(std::shared_ptr engine); + void engineSetupRequested(std::shared_ptr engine); void stopRequested(); /// @brief Request the screen thread to send a frame to the device /// @param controller the controller to send the frame to /// @param frame the frame data, ready to be sent - void sendRequested(Controller* controller, const QByteArray& frame); + void sendFrameDataRequested(Controller* controller, const QByteArray& frame); private: virtual void prepare(); @@ -99,8 +100,4 @@ class ControllerRenderingEngine : public QObject { QMutex m_mutex; ControllerScriptEngineBase* m_pControllerEngine; - - // This static mutex is used to ensure exclusive access to OpenGL operation - // from each of the ControllerRenderingEngine instances - static QMutex s_glMutex; }; diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index e4d866e78d55..f186f974e1b6 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -194,11 +194,13 @@ void ControllerScriptEngineBase::setCanPause(bool canPause) { &ControllerScriptEngineBase::doPause); m_isPaused = false; - m_isPausedCondition.wakeAll(); + m_pauseCount = 0; + m_isPausedCondition.wakeOne(); } } bool ControllerScriptEngineBase::pause() { const auto lock = lockMutex(&m_pauseMutex); + m_pauseCount++; if (m_canPause && !m_isPaused) { emit pauseRequested(); @@ -207,6 +209,7 @@ bool ControllerScriptEngineBase::pause() { while (m_canPause && !m_isPaused) { if (!m_isPausedCondition.wait(&m_pauseMutex, 1000)) { qCWarning(m_logger) << "Pause request timed out!"; + m_pauseCount--; return false; } } @@ -214,20 +217,22 @@ bool ControllerScriptEngineBase::pause() { } void ControllerScriptEngineBase::resume() { const auto lock = lockMutex(&m_pauseMutex); - - m_isPaused = false; - m_isPausedCondition.wakeAll(); + if (m_pauseCount > 0) { + m_pauseCount--; + } + m_isPaused = m_pauseCount > 0; + m_isPausedCondition.wakeOne(); } void ControllerScriptEngineBase::doPause() { const auto lock = lockMutex(&m_pauseMutex); - - m_isPaused = true; - m_isPausedCondition.wakeAll(); + m_isPaused = m_pauseCount > 0; + m_isPausedCondition.wakeOne(); while (m_canPause && m_isPaused) { VERIFY_OR_DEBUG_ASSERT(m_isPausedCondition.wait(&m_pauseMutex, 1000)) { qCWarning(m_logger) << "Main GUI pause timed out!"; m_isPaused = false; + m_pauseCount = 0; }; } m_isPausedCondition.wakeAll(); diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 964e2e040861..d2dd9abcc4f3 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -102,6 +102,7 @@ class ControllerScriptEngineBase : public QObject { /// QMLApplication, which isn't the case here) QWaitCondition m_isPausedCondition; QMutex m_pauseMutex; + int m_pauseCount{0}; bool m_isPaused{false}; bool m_canPause{false}; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 489214145250..a23acd5dc2dd 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -21,6 +21,8 @@ #ifdef MIXXX_USE_QML #include "util/assert.h" #include "util/cmdlineargs.h" + +using Clock = std::chrono::steady_clock; #endif namespace { @@ -254,7 +256,7 @@ void ControllerScriptEngineLegacy::setScriptFiles( #ifdef MIXXX_USE_QML for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { - if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::QML) { + if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::Qml) { setQMLMode(true); return; } @@ -315,7 +317,7 @@ bool ControllerScriptEngineLegacy::initialize() { ->setObjectName( QString("CtrlScreen_%1").arg(screen.identifier)); availableScreens.value(screen.identifier) - ->requestSetup( + ->requestEngineSetup( std::dynamic_pointer_cast(m_pJSEngine)); if (!availableScreens.value(screen.identifier)->isValid()) { @@ -383,7 +385,7 @@ bool ControllerScriptEngineLegacy::initialize() { #endif for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { #ifdef MIXXX_USE_QML - if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT) { + if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::Javascript) { #endif if (!evaluateScriptFile(script.file)) { shutdown(); @@ -569,21 +571,19 @@ void ControllerScriptEngineLegacy::handleScreenFrame( if (CmdlineArgs::Instance().getControllerPreviewScreens()) { QImage screenDebug(frame); - if (screeninfo.endian != std::endian::native) { - switch (screeninfo.endian) { - case std::endian::big: - qFromBigEndian(frame.constBits(), - frame.sizeInBytes() / 2, - screenDebug.bits()); - break; - case std::endian::little: - qFromLittleEndian(frame.constBits(), - frame.sizeInBytes() / 2, - screenDebug.bits()); - break; - default: - break; - } + switch (screeninfo.endian) { + case LegacyControllerMapping::ScreenInfo::ColorEndian::Big: + qFromBigEndian(frame.constBits(), + frame.sizeInBytes() / 2, + screenDebug.bits()); + break; + case LegacyControllerMapping::ScreenInfo::ColorEndian::Little: + qFromLittleEndian(frame.constBits(), + frame.sizeInBytes() / 2, + screenDebug.bits()); + break; + default: + break; } if (screeninfo.reversedColor) { screenDebug.rgbSwap(); @@ -597,7 +597,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( m_transformScreenFrameFunctions[screeninfo.identifier]; if (!tranformMethod.method.isValid() && screeninfo.rawData) { - m_renderingScreens[screeninfo.identifier]->requestSend(m_pController, input); + m_renderingScreens[screeninfo.identifier]->requestSendingFrameData(m_pController, input); return; } @@ -667,7 +667,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( m_pController->sendBytes(returnedValue.view()); } - m_renderingScreens[screeninfo.identifier]->requestSend( + m_renderingScreens[screeninfo.identifier]->requestSendingFrameData( m_pController, transformedFrame); } #endif @@ -682,19 +682,20 @@ void ControllerScriptEngineLegacy::shutdown() { #ifdef MIXXX_USE_QML setCanPause(false); // Wait till the splash off animation has finished rendering - uint maxSplashOffDuration = 0; + std::chrono::milliseconds maxSplashOffDuration{}; for (const auto& pScreen : std::as_const(m_renderingScreens)) { if (!pScreen->isRunning()) { continue; } - maxSplashOffDuration = qMax(maxSplashOffDuration, pScreen->info().splash_off); + maxSplashOffDuration = std::max(maxSplashOffDuration, pScreen->info().splash_off); } - auto splashOffDeadline = mixxx::Duration::fromMillis(maxSplashOffDuration) + - mixxx::Time::elapsed(); - while (splashOffDeadline > mixxx::Time::elapsed()) { + auto splashOffDeadline = Clock::now() + maxSplashOffDuration; + while (splashOffDeadline > Clock::now()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, - (splashOffDeadline - mixxx::Time::elapsed()).toIntegerMillis()); + std::chrono::duration_cast( + splashOffDeadline - Clock::now()) + .count()); } m_rootItems.clear(); @@ -803,7 +804,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( std::shared_ptr pScreen) { VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || qmlScript.type != - LegacyControllerMapping::ScriptFileInfo::Type::QML) { + LegacyControllerMapping::ScriptFileInfo::Type::Qml) { return std::shared_ptr(nullptr); } diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index 858f55c4ef02..290307e799fe 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -59,10 +59,10 @@ class MockLegacyControllerMapping : public LegacyControllerMapping { const QSize& size, uint targetFps, uint msaa, - uint splashoff, + std::chrono::milliseconds splashoff, QImage::Format pixelFormat, - std::endian endian, - bool reversedColorse, + LegacyControllerMapping::ScreenInfo::ColorEndian endian, + bool reversedColors, bool rawData), (override)); MOCK_METHOD(void, addModule, (const QFileInfo& dirinfo, bool builtin), (override)); @@ -95,14 +95,14 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseSimpleMapping) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScriptFile(QString("DummyDeviceDefaultScreen.js"), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, false)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_CALL(*mapping, addModule(_, _)).Times(0); @@ -135,23 +135,23 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScriptFile(QString("DummyDeviceDefaultScreen.qml"), QString(""), QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), - LegacyControllerMapping::ScriptFileInfo::Type::QML, + LegacyControllerMapping::ScriptFileInfo::Type::Qml, false)); EXPECT_CALL(*mapping, addScreenInfo(QString("main"), QSize(480, 360), 20, 1, - 2000, + std::chrono::milliseconds(2000), QImage::Format_RGBA8888, - std::endian::little, + LegacyControllerMapping::ScreenInfo::ColorEndian::Little, false, false)); EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); @@ -179,7 +179,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, 20, _, _, _, _, _, _)); @@ -204,7 +204,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG(QtWarningMsg, @@ -232,8 +232,11 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"targetFps\" as an unsigned " + "integer in the screen definition.")); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -262,7 +265,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); @@ -292,8 +295,11 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"targetFps\" as an unsigned " + "integer in the screen definition.")); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -325,7 +331,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, QSize(10, 10), _, _, _, _, _, _, _)); @@ -350,7 +356,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -378,8 +384,11 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"height\" as an unsigned " + "integer in the screen definition.")); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, @@ -406,8 +415,11 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"height\" as an unsigned " + "integer in the screen definition.")); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, @@ -434,7 +446,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -469,7 +481,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB888, _, _, _)); @@ -493,7 +505,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB16, _, _, _)); @@ -517,7 +529,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -545,9 +557,18 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::little, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(_, + _, + _, + _, + _, + _, + LegacyControllerMapping::ScreenInfo::ColorEndian::Little, + _, + _)); addScriptFilesToMapping( doc.documentElement(), @@ -569,9 +590,18 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::little, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(_, + _, + _, + _, + _, + _, + LegacyControllerMapping::ScreenInfo::ColorEndian::Little, + _, + _)); addScriptFilesToMapping( doc.documentElement(), @@ -593,9 +623,18 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, std::endian::big, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(_, + _, + _, + _, + _, + _, + LegacyControllerMapping::ScreenInfo::ColorEndian::Big, + _, + _)); addScriptFilesToMapping( doc.documentElement(), @@ -617,7 +656,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG( @@ -632,12 +671,14 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) } TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesDefinition) { - QStringList kFalseValue = {"false", "FALse", "no", "yes", "nope", "maybe"}; - QStringList kTrueValue = {"true", "trUe", "1"}; + bool kExpectedWarning[] = {false, false, true, true, true, true, true}; + QStringList kFalseValue = {"false", "FALse", "no", "yes", "1", "nope", "maybe"}; + QStringList kTrueValue = {"true", "trUe", "TRUE "}; QDomDocument doc; std::shared_ptr mapping; // reversed + bool* expectedWarning = &kExpectedWarning[0]; for (const QString& falseValue : std::as_const(kFalseValue)) { doc.setContent( QString(R"EOF( @@ -656,8 +697,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); + if (expectedWarning++) { + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"reversed\" as a " + "boolean in the screen definition.")); + } EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, false, _)); addScriptFilesToMapping( @@ -683,7 +729,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, true, _)); @@ -693,6 +739,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD QDir()); } // raw + expectedWarning = &kExpectedWarning[0]; for (const QString& falseValue : std::as_const(kFalseValue)) { doc.setContent( QString(R"EOF( @@ -711,9 +758,14 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, false)); + if (expectedWarning++) { + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"raw\" as a boolean in " + "the screen definition.")); + } addScriptFilesToMapping( doc.documentElement(), @@ -738,7 +790,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, true)); @@ -768,9 +820,9 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -792,9 +844,9 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, 500, _, _, _, _, _)); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(500), _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -816,9 +868,18 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, s_maxSplashOffDuration, _, _, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(_, + _, + _, + _, + std::chrono::milliseconds(s_maxSplashOffDuration), + _, + _, + _, + _)); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid splashoff duration. Splashoff duration must " @@ -847,9 +908,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"splashoff\" as an unsigned " + "integer in the screen definition.")); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -871,9 +935,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, 0, _, _, _, _)); + EXPECT_LOG_MSG(QtWarningMsg, + QString("Unable to parse the field \"splashoff\" as an unsigned " + "integer in the screen definition.")); + EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); addScriptFilesToMapping( doc.documentElement(), @@ -905,29 +972,29 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { addScriptFile(QString(REQUIRED_SCRIPT_FILE), QString(""), _, // gmock seems unable to assert QFileInfo - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, true)); EXPECT_CALL(*mapping, addScriptFile(QString("DummyDeviceDefaultScreen.qml"), QString(""), QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), - LegacyControllerMapping::ScriptFileInfo::Type::QML, + LegacyControllerMapping::ScriptFileInfo::Type::Qml, false)); EXPECT_CALL(*mapping, addScriptFile(QString("LegacyScript.js"), QString(""), QFileInfo("/dummy/path/LegacyScript.js"), - LegacyControllerMapping::ScriptFileInfo::Type::JAVASCRIPT, + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, false)); EXPECT_CALL(*mapping, addScreenInfo(QString("main"), QSize(480, 360), 20, 1, - 2000, + std::chrono::milliseconds(2000), QImage::Format_RGBA8888, - std::endian::little, + LegacyControllerMapping::ScreenInfo::ColorEndian::Little, false, false)); EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); diff --git a/src/test/controllerrenderingengine_test.cpp b/src/test/controllerrenderingengine_test.cpp index 49b5d2e16e77..faf912394454 100644 --- a/src/test/controllerrenderingengine_test.cpp +++ b/src/test/controllerrenderingengine_test.cpp @@ -34,15 +34,15 @@ class MockRenderingEngine : public ControllerRenderingEngine { TEST_F(ControllerRenderingEngineTest, createValidRendererWithSupportedTypes) { for (auto pixelFormat : supportedPixelFormat()) { MockRenderingEngine screenTest(LegacyControllerMapping::ScreenInfo{ - "", // identifier - QSize(0, 0), // size - 10, // target_fps - 1, // msaa - 10, // splash_off - pixelFormat, // pixelFormat - std::endian::big, // endian - false, // reversedColor - false // rawData + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 1, // msaa + std::chrono::milliseconds(10), // splash_off + pixelFormat, // pixelFormat + LegacyControllerMapping::ScreenInfo::ColorEndian::Big, // endian + false, // reversedColor + false // rawData }); EXPECT_TRUE(screenTest.isValid()); EXPECT_TRUE(screenTest.stop()); diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index b21cb72bc4a5..a26d150d63c4 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -663,28 +663,31 @@ class MockScreenRender : public ControllerRenderingEngine { public: MockScreenRender(const LegacyControllerMapping::ScreenInfo& info) : ControllerRenderingEngine(info, nullptr){}; - MOCK_METHOD(void, requestSend, (Controller * controller, const QByteArray& frame), (override)); + MOCK_METHOD(void, + requestSendingFrameData, + (Controller * controller, const QByteArray& frame), + (override)); }; TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { SETUP_LOG_CAPTURE(); LegacyControllerMapping::ScreenInfo dummyScreen{ - "", // identifier - QSize(0, 0), // size - 10, // target_fps - 1, // msaa - 10, // splash_off - QImage::Format_RGB16, // pixelFormat - std::endian::big, // endian - false, // rawData - false // reversedColor + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 1, // msaa + std::chrono::milliseconds(10), // splash_off + QImage::Format_RGB16, // pixelFormat + LegacyControllerMapping::ScreenInfo::ColorEndian::Big, // endian + false, // rawData + false // reversedColor }; QImage dummyFrame; // Allocate screen on the heap as it need to outlive the this function, // since the engine will take ownership of it std::shared_ptr pDummyRender = std::make_shared(dummyScreen); - EXPECT_CALL(*pDummyRender, requestSend(_, _)).Times(0); + EXPECT_CALL(*pDummyRender, requestSendingFrameData(_, _)).Times(0); EXPECT_LOG_MSG(QtWarningMsg, "Could not find a valid transform function but the screen doesn't " "accept raw data. Aborting screen rendering."); @@ -708,22 +711,22 @@ TEST_F(ControllerScriptEngineLegacyTest, screenWontSentRawDataIfNotConfigured) { TEST_F(ControllerScriptEngineLegacyTest, screenWillSentRawDataIfConfigured) { SETUP_LOG_CAPTURE(); LegacyControllerMapping::ScreenInfo dummyScreen{ - "", // identifier - QSize(0, 0), // size - 10, // target_fps - 1, // msaa - 10, // splash_off - QImage::Format_RGB16, // pixelFormat - std::endian::big, // endian - false, // reversedColor - true // rawData + "", // identifier + QSize(0, 0), // size + 10, // target_fps + 1, // msaa + std::chrono::milliseconds(10), // splash_off + QImage::Format_RGB16, // pixelFormat + LegacyControllerMapping::ScreenInfo::ColorEndian::Big, // endian + false, // reversedColor + true // rawData }; QImage dummyFrame; // Allocate screen on the heap as it need to outlive the this function, // since the engine will take ownership of it std::shared_ptr pDummyRender = std::make_shared(dummyScreen); - EXPECT_CALL(*pDummyRender, requestSend(_, QByteArray())); + EXPECT_CALL(*pDummyRender, requestSendingFrameData(_, QByteArray())); transformScreenFrameFunctions().insert( dummyScreen.identifier, From 10cb8a67ea3ce2e464c38b1e7fd27905611a5323 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 9 May 2024 14:50:09 +0100 Subject: [PATCH 10/26] Updating var naming, add comments and small refactor --- src/controllers/dlgprefcontroller.cpp | 63 +++++++++---------- .../legacycontrollermappingfilehandler.cpp | 16 ++--- .../legacycontrollermappingfilehandler.h | 6 +- .../rendering/controllerrenderingengine.cpp | 4 +- .../rendering/controllerrenderingengine.h | 7 +++ .../controller_mapping_file_handler_test.cpp | 14 ++--- 6 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 21419dce8548..640271898a37 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -38,6 +38,29 @@ QString mappingNameToPath(const QString& directory, const QString& mappingName) return directory + fileName + kMappingExt; } +const QString kBuiltinFileSuffix = + QStringLiteral(" (") + QObject::tr("built-in") + QStringLiteral(")"); + +/// @brief Format a controller file to display attributes (system, missing) in the UI +/// @return The formatted string +QString formatFilePath(UserSettingsPointer pConfig, + QColor linkColor, + QString name, + QFileInfo file) { + QString systemMappingPath = resourceMappingsPath(pConfig); + QString scriptFileLink = coloredLinkString( + linkColor, + name, + file.absoluteFilePath()); + if (!file.exists()) { + scriptFileLink += + QStringLiteral(" (") + QObject::tr("missing") + QStringLiteral(")"); + } else if (file.absoluteFilePath().startsWith(systemMappingPath)) { + scriptFileLink += kBuiltinFileSuffix; + } + return scriptFileLink; +} + } // namespace DlgPrefController::DlgPrefController( @@ -397,49 +420,21 @@ QString DlgPrefController::mappingFileLinks( return QString(); } - const QString builtinFileSuffix = QStringLiteral(" (") + tr("built-in") + QStringLiteral(")"); - QString systemMappingPath = resourceMappingsPath(m_pConfig); QStringList linkList; - QString xmlFileLink = coloredLinkString( + linkList.append(formatFilePath(m_pConfig, m_pLinkColor, QFileInfo(pMapping->filePath()).fileName(), - pMapping->filePath()); - if (pMapping->filePath().startsWith(systemMappingPath)) { - xmlFileLink += builtinFileSuffix; - } - linkList << xmlFileLink; - + QFileInfo(pMapping->filePath()))); for (const auto& script : pMapping->getScriptFiles()) { - QString scriptFileLink = coloredLinkString( + linkList.append(formatFilePath(m_pConfig, m_pLinkColor, script.name, - script.file.absoluteFilePath()); - if (!script.file.exists()) { - scriptFileLink += - QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); - } else if (script.file.absoluteFilePath().startsWith( - systemMappingPath)) { - scriptFileLink += builtinFileSuffix; - } - - linkList << scriptFileLink; + QFileInfo(script.file.absoluteFilePath()))); } - #ifdef MIXXX_USE_QML for (const auto& qmlLibrary : pMapping->getModules()) { - QString scriptFileLink = coloredLinkString( - m_pLinkColor, - qmlLibrary.dirinfo.fileName(), - qmlLibrary.dirinfo.absoluteFilePath()); - if (!qmlLibrary.dirinfo.exists()) { - scriptFileLink += - QStringLiteral(" (") + tr("missing") + QStringLiteral(")"); - } else if (qmlLibrary.dirinfo.absoluteFilePath().startsWith( - systemMappingPath)) { - scriptFileLink += builtinFileSuffix; - } - - linkList << scriptFileLink; + auto fileInfo = QFileInfo(qmlLibrary.dirinfo.absoluteFilePath()); + linkList.append(formatFilePath(m_pConfig, m_pLinkColor, fileInfo.fileName(), fileInfo)); } #endif return linkList.join("
"); diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 321b806b8daa..9ca61b0f0bd7 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -111,31 +111,31 @@ bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapp uint splashOff = screen.attribute("splashoff", "0").toUInt(&ok); LOG_OF_NOT_OK("splashoff", "an unsigned integer"); - if (!targetFps || targetFps > LegacyControllerMappingFileHandler::s_maxTargetFps) { + if (!targetFps || targetFps > LegacyControllerMappingFileHandler::kMaxTargetFps) { kLogger.warning() << "Invalid target FPS. Target FPS must be between 1 and" - << LegacyControllerMappingFileHandler::s_maxTargetFps; + << LegacyControllerMappingFileHandler::kMaxTargetFps; return false; } - if (!msaa || msaa > LegacyControllerMappingFileHandler::s_maxMsaa) { + if (!msaa || msaa > LegacyControllerMappingFileHandler::kMaxMsaa) { kLogger.warning() << "Invalid MSAA value. MSAA value must be between 1 and" - << LegacyControllerMappingFileHandler::s_maxMsaa; + << LegacyControllerMappingFileHandler::kMaxMsaa; return false; } - if (splashOff > LegacyControllerMappingFileHandler::s_maxSplashOffDuration) { + if (splashOff > LegacyControllerMappingFileHandler::kMaxSplashOffDuration) { kLogger.warning() << QString("Invalid splashoff duration. Splashoff duration " "must be " "between 0 and %1. Clamping to %2") .arg(QString::number( LegacyControllerMappingFileHandler:: - s_maxSplashOffDuration), + kMaxSplashOffDuration), QString::number( LegacyControllerMappingFileHandler:: - s_maxSplashOffDuration)); - splashOff = LegacyControllerMappingFileHandler::s_maxSplashOffDuration; + kMaxSplashOffDuration)); + splashOff = LegacyControllerMappingFileHandler::kMaxSplashOffDuration; } if (!LegacyControllerMappingFileHandler::kSupportedPixelFormat.contains(pixelFormatName)) { diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index c6a490c4ad5f..383585f0eda9 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -97,11 +97,11 @@ class LegacyControllerMappingFileHandler { static QMap kSupportedPixelFormat; static QMap kEndianFormat; // Maximum target frame per request for a screen controller - static const int s_maxTargetFps = 240; + static constexpr int kMaxTargetFps = 240; // Maximum MSAA value that can be used - static const int s_maxMsaa = 16; + static constexpr int kMaxMsaa = 16; // Maximum time allowed for a screen to run a splash off animation - static const int s_maxSplashOffDuration = 3000; + static constexpr int kMaxSplashOffDuration = 3000; friend class ControllerRenderingEngineTest; #endif diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 64c07eabce9c..d6f4914bdc57 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -351,11 +351,11 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f if (CmdlineArgs::Instance() .getControllerDebug()) { - auto endOfRender = Clock::now(); + auto endOfFrameCycle = Clock::now(); kLogger.debug() << "Frame took " << std::chrono::duration_cast( - endOfRender - m_nextFrameStart) + endOfFrameCycle - m_nextFrameStart) .count() << "milliseconds and frame has" << frame.size() << "bytes"; } diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index d5bafc4b80fc..71193ea15e3a 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -52,7 +52,14 @@ class ControllerRenderingEngine : public QObject { } public slots: + // Request sending frame data to the device. The task will be run in the + // rendering event loop. This method should only be called once received the + // `frameRendered` signal virtual void requestSendingFrameData(Controller* controller, const QByteArray& frame); + // Request setting up the rendering context for QML engine and wait till it + // is completed. The task will be run in the rendering event loop to ensure + // thread affinity of engine components. `isValid` can be used to ensure + // that the setup was successful void requestEngineSetup(std::shared_ptr qmlEngine); void start(); virtual bool stop(); diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index 290307e799fe..e2913f3db2a6 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -209,7 +209,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); EXPECT_LOG_MSG(QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") - .arg(s_maxTargetFps)); + .arg(kMaxTargetFps)); addScriptFilesToMapping( doc.documentElement(), @@ -242,7 +242,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") - .arg(s_maxTargetFps)); + .arg(kMaxTargetFps)); addScriptFilesToMapping( doc.documentElement(), @@ -272,7 +272,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") - .arg(s_maxTargetFps)); + .arg(kMaxTargetFps)); addScriptFilesToMapping( doc.documentElement(), @@ -305,7 +305,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") - .arg(s_maxTargetFps)); + .arg(kMaxTargetFps)); addScriptFilesToMapping( doc.documentElement(), @@ -875,7 +875,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, _, _, - std::chrono::milliseconds(s_maxSplashOffDuration), + std::chrono::milliseconds(kMaxSplashOffDuration), _, _, _, @@ -884,8 +884,8 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe QtWarningMsg, QString("Invalid splashoff duration. Splashoff duration must " "be between 0 and %0. Clamping to %1") - .arg(s_maxSplashOffDuration) - .arg(s_maxSplashOffDuration)); + .arg(kMaxSplashOffDuration) + .arg(kMaxSplashOffDuration)); addScriptFilesToMapping( doc.documentElement(), From ede6993ef7f1f8a355386333ba89f096cab6ed30 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 9 May 2024 17:01:35 +0100 Subject: [PATCH 11/26] Fix clazy warning --- src/controllers/dlgprefcontroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 640271898a37..132a57ccc194 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -45,8 +45,8 @@ const QString kBuiltinFileSuffix = /// @return The formatted string QString formatFilePath(UserSettingsPointer pConfig, QColor linkColor, - QString name, - QFileInfo file) { + const QString& name, + const QFileInfo& file) { QString systemMappingPath = resourceMappingsPath(pConfig); QString scriptFileLink = coloredLinkString( linkColor, From c815d09ba10f50ba794e98a721232c78769f56b4 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 12 May 2024 19:36:34 +0100 Subject: [PATCH 12/26] Enforce OpenGL as QQuickWindow RHI --- src/coreservices.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreservices.cpp b/src/coreservices.cpp index 8ff74d909ced..c7f368ce30a9 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -29,6 +29,9 @@ #endif #include "skin/skincontrols.h" #ifdef MIXXX_USE_QML +#include +#include + #include "controllers/scripting/controllerscriptenginebase.h" #include "qml/qmlconfigproxy.h" #include "qml/qmlcontrolproxy.h" @@ -481,6 +484,11 @@ void CoreServices::initializeQMLSingletons() { mixxx::qml::QmlLibraryProxy::registerLibrary(getLibrary()); ControllerScriptEngineBase::registerTrackCollectionManager(getTrackCollectionManager()); + + // Currently, it is required to enforce QQuickWindow RHI backend to use + // OpenGL on all platforms to allow offscreen rendering to function as + // expected + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); #endif } From 42158b58404848711141a40c6ac8a89714b3ad03 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Mon, 13 May 2024 16:40:51 +0100 Subject: [PATCH 13/26] Comment and nits --- src/controllers/legacycontrollermapping.h | 7 ++++-- .../rendering/controllerrenderingengine.cpp | 9 ++------ .../scripting/controllerscriptenginebase.cpp | 4 ++++ .../legacy/controllerscriptenginelegacy.cpp | 22 +++++++++---------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index fd918a9236b1..f7a49b4840e5 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -11,6 +11,9 @@ #include #include #include +#ifdef MIXXX_USE_QML +#include +#endif #include "controllers/legacycontrollersettings.h" #include "controllers/legacycontrollersettingslayout.h" @@ -104,8 +107,8 @@ class LegacyControllerMapping { // confusing and will have unpredictable behaviour depending of the // platform. enum class ColorEndian { - Big = __ORDER_BIG_ENDIAN__, - Little = __ORDER_LITTLE_ENDIAN__, + Big = std::endian::big, + Little = std::endian::little, }; QString identifier; // The screen identifier diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index d6f4914bdc57..e88a1f5ff9ae 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -167,6 +167,7 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { m_offscreenSurface = std::make_unique(); m_offscreenSurface->setFormat(m_context->format()); + // offscreen surface needs to be created from application main thread VERIFY_OR_DEBUG_ASSERT(QMetaObject::invokeMethod( qApp, [this] { @@ -275,13 +276,7 @@ void ControllerRenderingEngine::renderFrame() { { ScopedTimer t(u"ControllerRenderingEngine::renderFrame::sync"); VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { - kLogger.warning() << "Couldn't sync the render control."; - finish(); - if (m_pControllerEngine) { - m_pControllerEngine->resume(); - } - - return; + kLogger.warning() << "Couldn't sync the render control. Scene may be stuck"; }; } diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index f186f974e1b6..1691ac446d26 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -184,6 +184,10 @@ void ControllerScriptEngineBase::setCanPause(bool canPause) { &ControllerScriptEngineBase::doPause, Qt::UniqueConnection); } else { + // New signals may have been queued emitted requesting for pause, so we + // manually process the event loop now to clear and handle those, before + // disabling pausing. Without this, thread requesting pause will stay + // stuck waiting on the condvar lock.unlock(); QCoreApplication::processEvents(); lock.relock(); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index a23acd5dc2dd..6633a596d3cb 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -804,19 +804,19 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( std::shared_ptr pScreen) { VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || qmlScript.type != - LegacyControllerMapping::ScriptFileInfo::Type::Qml) { - return std::shared_ptr(nullptr); - } + LegacyControllerMapping::ScriptFileInfo::Type::Qml){ + return nullptr} - std::unique_ptr qmlComponent = - std::make_unique( - std::dynamic_pointer_cast(m_pJSEngine).get()); + std::unique_ptr + qmlComponent = + std::make_unique( + std::dynamic_pointer_cast(m_pJSEngine).get()); QFile scene = QFile(qmlScript.file.absoluteFilePath()); if (!scene.exists()) { qCWarning(m_logger) << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() << "does not exist."; - return std::shared_ptr(nullptr); + return nullptr } QDir dir(m_resourcePath + "/qml/"); @@ -843,12 +843,12 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( << "at line" << error.line() << ", error: " << error; showQMLExceptionDialog(error, true); } - return std::shared_ptr(nullptr); + return nullptr } VERIFY_OR_DEBUG_ASSERT(qmlComponent->isReady()) { qCWarning(m_logger) << "QMLComponent isn't ready although synchronous load was requested."; - return std::shared_ptr(nullptr); + return nullptr } QObject* pRootObject = qmlComponent->createWithInitialProperties( @@ -858,7 +858,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( for (const QQmlError& error : errorList) { qCWarning(m_logger) << error.url() << error.line() << error; } - return std::shared_ptr(nullptr); + return nullptr } std::shared_ptr rootItem = @@ -866,7 +866,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( if (!rootItem) { qWarning("run: Not a QQuickItem"); delete pRootObject; - return std::shared_ptr(nullptr); + return nullptr } watchFilePath(qmlScript.file.absoluteFilePath()); From ea12d82690991cdd19d42baed67acc26e188f190 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 15 May 2024 16:41:34 +0100 Subject: [PATCH 14/26] Improve rendering engine teardown --- src/controllers/legacycontrollermapping.h | 6 ++--- .../rendering/controllerrenderingengine.cpp | 15 +++++++----- .../rendering/controllerrenderingengine.h | 2 +- .../legacy/controllerscriptenginelegacy.cpp | 24 +++++++++++++------ 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index f7a49b4840e5..dc800edf2a5c 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -12,7 +12,7 @@ #include #include #ifdef MIXXX_USE_QML -#include +#include #endif #include "controllers/legacycontrollersettings.h" @@ -107,8 +107,8 @@ class LegacyControllerMapping { // confusing and will have unpredictable behaviour depending of the // platform. enum class ColorEndian { - Big = std::endian::big, - Little = std::endian::little, + Big = static_cast(std::endian::big), + Little = static_cast(std::endian::little), }; QString identifier; // The screen identifier diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index e88a1f5ff9ae..1264f4ddc5a8 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -25,7 +25,7 @@ #define VERIFY_OR_TERMINATE(cond, msg) \ VERIFY_OR_DEBUG_ASSERT(cond) { \ kLogger.warning() << msg; \ - finish(); \ + m_pThread->quit(); \ return; \ } @@ -91,8 +91,8 @@ void ControllerRenderingEngine::prepare() { &ControllerRenderingEngine::sendFrameDataRequested, this, &ControllerRenderingEngine::send); - connect(this, - &ControllerRenderingEngine::stopRequested, + connect(m_pThread.get(), + &QThread::finished, this, &ControllerRenderingEngine::finish); @@ -100,6 +100,8 @@ void ControllerRenderingEngine::prepare() { } ControllerRenderingEngine::~ControllerRenderingEngine() { + DEBUG_ASSERT(QThread::currentThread() != thread()); + m_pThread->wait(); VERIFY_OR_DEBUG_ASSERT(!m_fbo) { kLogger.critical() << "The ControllerEngine is being deleted but hasn't been " "cleaned up. Brace for impact"; @@ -197,7 +199,9 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { } void ControllerRenderingEngine::finish() { - disconnect(this); + DEBUG_ASSERT(QThread::currentThread() == thread()); + emit stopping(); + m_isValid = false; if (m_context && m_context->isValid()) { @@ -222,7 +226,6 @@ void ControllerRenderingEngine::finish() { m_context->doneCurrent(); } m_context.reset(); - m_pThread->quit(); } void ControllerRenderingEngine::renderFrame() { @@ -333,7 +336,7 @@ void ControllerRenderingEngine::renderFrame() { } bool ControllerRenderingEngine::stop() { - emit stopRequested(); + m_pThread->quit(); return m_pThread->wait(); } diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 71193ea15e3a..45785c54b06a 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -75,7 +75,7 @@ class ControllerRenderingEngine : public QObject { QImage frame, const QDateTime& timestamp); void engineSetupRequested(std::shared_ptr engine); - void stopRequested(); + void stopping(); /// @brief Request the screen thread to send a frame to the device /// @param controller the controller to send the frame to /// @param frame the frame data, ready to be sent diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 6633a596d3cb..ff01314651a2 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -491,7 +491,7 @@ void ControllerScriptEngineLegacy::extractTransformFunction( VERIFY_OR_DEBUG_ASSERT(metaObject) { qCWarning(m_logger) << "Invalid meta object for screen" << screenIdentifier - << "It may be that an unhandled issue occurred when imnporting " + << "It may be that an unhandled issue occurred when importing " "the scene."; return; } @@ -550,6 +550,11 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( &ControllerScriptEngineLegacy::handleScreenFrame); m_renderingScreens.insert(screenIdentifier, pScreen); m_rootItems.insert(screenIdentifier, pScene); + // In case a rendering issue occurs, we need to shutdown the controller + connect(pScreen.get(), + &ControllerRenderingEngine::stopping, + this, + &ControllerScriptEngineLegacy::shutdown); return true; } @@ -700,6 +705,10 @@ void ControllerScriptEngineLegacy::shutdown() { m_rootItems.clear(); for (const auto& pScreen : std::as_const(m_renderingScreens)) { + // When stopping, the rendering engine emits an event which triggers the + // shutdown in case it was initiated following a rendering issue. We + // need to disconnect first before stopping + pScreen->disconnect(this); VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || !pScreen->isRunning() || pScreen->stop()) { qCWarning(m_logger) << "Unable to stop the screen"; @@ -805,7 +814,8 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || qmlScript.type != LegacyControllerMapping::ScriptFileInfo::Type::Qml){ - return nullptr} + return nullptr; + } std::unique_ptr qmlComponent = @@ -816,7 +826,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( if (!scene.exists()) { qCWarning(m_logger) << "Unable to load the QML scene:" << qmlScript.file.absoluteFilePath() << "does not exist."; - return nullptr + return nullptr; } QDir dir(m_resourcePath + "/qml/"); @@ -843,12 +853,12 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( << "at line" << error.line() << ", error: " << error; showQMLExceptionDialog(error, true); } - return nullptr + return nullptr; } VERIFY_OR_DEBUG_ASSERT(qmlComponent->isReady()) { qCWarning(m_logger) << "QMLComponent isn't ready although synchronous load was requested."; - return nullptr + return nullptr; } QObject* pRootObject = qmlComponent->createWithInitialProperties( @@ -858,7 +868,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( for (const QQmlError& error : errorList) { qCWarning(m_logger) << error.url() << error.line() << error; } - return nullptr + return nullptr; } std::shared_ptr rootItem = @@ -866,7 +876,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( if (!rootItem) { qWarning("run: Not a QQuickItem"); delete pRootObject; - return nullptr + return nullptr; } watchFilePath(qmlScript.file.absoluteFilePath()); From a87fd88344156303d71dd2de2532cecacb6b21ee Mon Sep 17 00:00:00 2001 From: Antoine C Date: Wed, 15 May 2024 17:28:11 +0100 Subject: [PATCH 15/26] Fix linting --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index ff01314651a2..422cc5f05741 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -813,7 +813,7 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( std::shared_ptr pScreen) { VERIFY_OR_DEBUG_ASSERT(m_pJSEngine || qmlScript.type != - LegacyControllerMapping::ScriptFileInfo::Type::Qml){ + LegacyControllerMapping::ScriptFileInfo::Type::Qml) { return nullptr; } From 633c84d9bdb4b3fc22febba2a93d4925ac72dd5e Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 16 May 2024 20:20:55 +0100 Subject: [PATCH 16/26] Move the engine pause logic in a dedicated object --- CMakeLists.txt | 1 + .../controllerenginethreadcontrol.cpp | 97 +++++++++++++++++++ .../controllerenginethreadcontrol.h | 45 +++++++++ .../rendering/controllerrenderingengine.cpp | 53 +++++----- .../rendering/controllerrenderingengine.h | 18 ++-- .../scripting/controllerscriptenginebase.cpp | 69 ------------- .../scripting/controllerscriptenginebase.h | 20 +--- .../legacy/controllerscriptenginelegacy.cpp | 6 +- src/util/thread_affinity.h | 12 +++ 9 files changed, 196 insertions(+), 125 deletions(-) create mode 100644 src/controllers/controllerenginethreadcontrol.cpp create mode 100644 src/controllers/controllerenginethreadcontrol.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aef66013fbad..12efa12745f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1503,6 +1503,7 @@ else() target_sources(mixxx-lib PRIVATE # The following source depends of QML being available but aren't part of the new QML UI src/controllers/rendering/controllerrenderingengine.cpp + src/controllers/controllerenginethreadcontrol.cpp src/controllers/controllerscreenpreview.cpp ) endif() diff --git a/src/controllers/controllerenginethreadcontrol.cpp b/src/controllers/controllerenginethreadcontrol.cpp new file mode 100644 index 000000000000..91b61a43d9da --- /dev/null +++ b/src/controllers/controllerenginethreadcontrol.cpp @@ -0,0 +1,97 @@ +#include "controllers/controllerenginethreadcontrol.h" + +#include + +#include "moc_controllerenginethreadcontrol.cpp" +#include "util/assert.h" +#include "util/logger.h" +#include "util/mutex.h" +#include "util/thread_affinity.h" + +namespace { +const mixxx::Logger kLogger("ControllerEngineThreadControl"); +constexpr int kMaxPauseDurationMilliseconds = 1000; +} // namespace + +ControllerEngineThreadControl::ControllerEngineThreadControl(QObject* parent) + : QObject(parent) { +} +bool ControllerEngineThreadControl::pause() { + VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY() { + return false; + } + const auto lock = lockMutex(&m_pauseMutex); + m_pauseCount++; + + if (m_canPause && !m_isPaused) { + emit pauseRequested(); + } + + while (m_canPause && !m_isPaused) { + if (!m_isPausedCondition.wait(&m_pauseMutex, kMaxPauseDurationMilliseconds)) { + kLogger.warning() << "Pause request timed out!"; + m_pauseCount--; + return false; + } + } + return !m_canPause || m_isPaused; +} +void ControllerEngineThreadControl::resume() { + VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY() { + return; + } + const auto lock = lockMutex(&m_pauseMutex); + if (m_pauseCount > 0) { + m_pauseCount--; + } + m_isPaused = m_pauseCount > 0; + m_isPausedCondition.wakeOne(); +} +void ControllerEngineThreadControl::setCanPause(bool canPause) { + DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY(); + auto lock = lockMutex(&m_pauseMutex); + m_canPause = canPause; + + if (m_canPause) { + connect(this, + &ControllerEngineThreadControl::pauseRequested, + this, + &ControllerEngineThreadControl::doPause, + Qt::UniqueConnection); + } else { + // New signals may have been queued emitted requesting for pause, so we + // manually process the event loop now to clear and handle those, before + // disabling pausing. Without this, thread requesting pause will stay + // stuck waiting on the condvar + lock.unlock(); + QCoreApplication::processEvents(); + lock.relock(); + + disconnect(this, + &ControllerEngineThreadControl::pauseRequested, + this, + &ControllerEngineThreadControl::doPause); + + m_isPaused = false; + m_pauseCount = 0; + m_isPausedCondition.wakeOne(); + } +} +void ControllerEngineThreadControl::doPause() { + VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY() { + return; + } + const auto lock = lockMutex(&m_pauseMutex); + m_isPaused = m_pauseCount > 0; + m_isPausedCondition.wakeOne(); + + while (m_canPause && m_isPaused) { + VERIFY_OR_DEBUG_ASSERT(m_isPausedCondition.wait( + &m_pauseMutex, kMaxPauseDurationMilliseconds)) { + kLogger.warning() << "Engine pause timed out!"; + m_isPaused = false; + m_pauseCount = 0; + }; + } + m_isPausedCondition.wakeAll(); +} diff --git a/src/controllers/controllerenginethreadcontrol.h b/src/controllers/controllerenginethreadcontrol.h new file mode 100644 index 000000000000..73ccffc64d80 --- /dev/null +++ b/src/controllers/controllerenginethreadcontrol.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +/// @brief A ControllerEngineThreadControl can be use to orchestrate thread +/// pause on a ControllerScriptEngineBase. It used Pause is required by +/// rendering thread (https://doc.qt.io/qt-6/qquickrendercontrol.html#sync). +/// This offscreen render thread to pause the main "GUI thread" for onboard +/// screens +/// The documentation isn't completely clear about this, but after +/// testing, it appears that the "GUI main thread" is the thread where the QML +/// engine leaves in (also the main thread if we were using a +/// QMLApplication, which isn't the case here) +class ControllerEngineThreadControl : public QObject { + Q_OBJECT + public: + explicit ControllerEngineThreadControl(QObject* parent = nullptr); + + public slots: + // The following slots may be used by rendering engine to pause the thread. + // They must be called from different thread than + // ControllerEngineThreadControl's + bool pause(); + void resume(); + + // Change whether or not it is possible to pause the thread. Should be + // called from eh same thread than ControllerEngineThreadControl + void setCanPause(bool canPause); + private slots: + // Used to effectively pause the thread. Must be called from the same thread + // than ControllerEngineThreadControl + void doPause(); + + signals: + void pauseRequested(); + + private: + QWaitCondition m_isPausedCondition; + QMutex m_pauseMutex; + int m_pauseCount{0}; + bool m_isPaused{false}; + bool m_canPause{false}; +}; diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 1264f4ddc5a8..b3e1433f9385 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -12,12 +12,14 @@ #include #include "controllers/controller.h" +#include "controllers/controllerenginethreadcontrol.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "moc_controllerrenderingengine.cpp" #include "qml/qmlwaveformoverview.h" #include "util/cmdlineargs.h" #include "util/logger.h" +#include "util/thread_affinity.h" #include "util/time.h" #include "util/timer.h" @@ -37,13 +39,13 @@ using Clock = std::chrono::steady_clock; ControllerRenderingEngine::ControllerRenderingEngine( const LegacyControllerMapping::ScreenInfo& info, - ControllerScriptEngineBase* parent) + ControllerEngineThreadControl* engineThreadControl) : QObject(), m_screenInfo(info), m_GLDataFormat(GL_RGBA), m_GLDataType(GL_UNSIGNED_BYTE), m_isValid(true), - m_pControllerEngine(parent) { + m_pEngineThreadControl(engineThreadControl) { switch (m_screenInfo.pixelFormat) { case QImage::Format_RGB16: m_GLDataFormat = GL_RGB; @@ -83,10 +85,6 @@ void ControllerRenderingEngine::prepare() { #endif // these at first sight weird-looking connections facilitate thread-safe communication. - connect(this, - &ControllerRenderingEngine::engineSetupRequested, - this, - &ControllerRenderingEngine::setup); connect(this, &ControllerRenderingEngine::sendFrameDataRequested, this, @@ -100,7 +98,7 @@ void ControllerRenderingEngine::prepare() { } ControllerRenderingEngine::~ControllerRenderingEngine() { - DEBUG_ASSERT(QThread::currentThread() != thread()); + DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY(); m_pThread->wait(); VERIFY_OR_DEBUG_ASSERT(!m_fbo) { kLogger.critical() << "The ControllerEngine is being deleted but hasn't been " @@ -121,23 +119,26 @@ bool ControllerRenderingEngine::isRunning() const { } void ControllerRenderingEngine::requestEngineSetup(std::shared_ptr qmlEngine) { - m_isValid = false; + DEBUG_ASSERT(!m_quickWindow); VERIFY_OR_DEBUG_ASSERT(qmlEngine) { kLogger.critical() << "No QML engine was passed!"; return; } - VERIFY_OR_DEBUG_ASSERT(QThread::currentThread() != thread()) { + VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY() { kLogger.warning() << "Unable to setup OpenGL rendering context from the same " "thread as the render object"; return; } - emit engineSetupRequested(qmlEngine); - const auto lock = lockMutex(&m_mutex); - if (!m_quickWindow) { - m_waitCondition.wait(&m_mutex); - } - if (m_isValid) { + QMetaObject::invokeMethod( + this, + [this, qmlEngine]() { + setup(qmlEngine); + }, + // This invocation will block the current thread! + Qt::BlockingQueuedConnection); + + if (m_quickWindow) { m_renderControl->prepareThread(m_pThread.get()); } } @@ -148,7 +149,10 @@ void ControllerRenderingEngine::requestSendingFrameData( } void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { - DEBUG_ASSERT(QThread::currentThread() == thread()); + VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY() { + kLogger.warning() << "The ControllerRenderingEngine setup must be done by its own thread!"; + return; + } QSurfaceFormat format; format.setSamples(m_screenInfo.msaa); format.setDepthBufferSize(16); @@ -158,7 +162,6 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { m_context->setFormat(format); VERIFY_OR_DEBUG_ASSERT(m_context->create()) { kLogger.warning() << "Unable to initialize controller screen rendering. Giving up"; - m_waitCondition.wakeAll(); return; } connect(m_context.get(), @@ -181,7 +184,6 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { kLogger.warning() << "Unable to create the OffscreenSurface for controller " "screen rendering. Giving up"; m_offscreenSurface.reset(); - m_waitCondition.wakeAll(); return; } @@ -193,13 +195,10 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { } m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height()); - - m_isValid = true; - m_waitCondition.wakeAll(); } void ControllerRenderingEngine::finish() { - DEBUG_ASSERT(QThread::currentThread() == thread()); + DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY(); emit stopping(); m_isValid = false; @@ -270,8 +269,8 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->beginFrame(); - if (m_pControllerEngine) { - m_pControllerEngine->pause(); + if (m_pEngineThreadControl) { + m_pEngineThreadControl->pause(); } m_renderControl->polishItems(); @@ -283,8 +282,8 @@ void ControllerRenderingEngine::renderFrame() { }; } - if (m_pControllerEngine) { - m_pControllerEngine->resume(); + if (m_pEngineThreadControl) { + m_pEngineThreadControl->resume(); } QImage fboImage(m_screenInfo.size, m_screenInfo.pixelFormat); @@ -341,7 +340,7 @@ bool ControllerRenderingEngine::stop() { } void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { - DEBUG_ASSERT(QThread::currentThread() == thread()); + DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY(); ScopedTimer t(u"ControllerRenderingEngine::send"); if (!frame.isEmpty()) { controller->sendBytes(frame); diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 45785c54b06a..9af682352041 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -2,17 +2,15 @@ #include -#include #include -#include #include #include "controllers/legacycontrollermapping.h" -#include "controllers/scripting/controllerscriptenginebase.h" #include "preferences/configobject.h" #include "util/time.h" class Controller; +class ControllerEngineThreadControl; class QOffscreenSurface; class QOpenGLContext; class QOpenGLFramebufferObject; @@ -27,7 +25,7 @@ class ControllerRenderingEngine : public QObject { Q_OBJECT public: ControllerRenderingEngine(const LegacyControllerMapping::ScreenInfo& info, - ControllerScriptEngineBase* parent); + ControllerEngineThreadControl* parent); ~ControllerRenderingEngine(); bool event(QEvent* event) override; @@ -74,7 +72,6 @@ class ControllerRenderingEngine : public QObject { void frameRendered(const LegacyControllerMapping::ScreenInfo& screeninfo, QImage frame, const QDateTime& timestamp); - void engineSetupRequested(std::shared_ptr engine); void stopping(); /// @brief Request the screen thread to send a frame to the device /// @param controller the controller to send the frame to @@ -101,10 +98,9 @@ class ControllerRenderingEngine : public QObject { GLenum m_GLDataType; bool m_isValid; - - // These mutexes components are used to ensure internal object synchronicity - QWaitCondition m_waitCondition; - QMutex m_mutex; - - ControllerScriptEngineBase* m_pControllerEngine; + // Engine control is owned by ControllerScriptEngineBase. The assumption is + // made that ControllerScriptEngineBase always outlive + // ControllerRenderingEngine as it is in charge of stopping and joining the + // thread + ControllerEngineThreadControl* m_pEngineThreadControl; }; diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 1691ac446d26..e31faad90887 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -173,75 +173,6 @@ void ControllerScriptEngineBase::showScriptExceptionDialog( } #ifdef MIXXX_USE_QML -void ControllerScriptEngineBase::setCanPause(bool canPause) { - auto lock = lockMutex(&m_pauseMutex); - m_canPause = canPause; - - if (m_canPause) { - connect(this, - &ControllerScriptEngineBase::pauseRequested, - this, - &ControllerScriptEngineBase::doPause, - Qt::UniqueConnection); - } else { - // New signals may have been queued emitted requesting for pause, so we - // manually process the event loop now to clear and handle those, before - // disabling pausing. Without this, thread requesting pause will stay - // stuck waiting on the condvar - lock.unlock(); - QCoreApplication::processEvents(); - lock.relock(); - - disconnect(this, - &ControllerScriptEngineBase::pauseRequested, - this, - &ControllerScriptEngineBase::doPause); - - m_isPaused = false; - m_pauseCount = 0; - m_isPausedCondition.wakeOne(); - } -} -bool ControllerScriptEngineBase::pause() { - const auto lock = lockMutex(&m_pauseMutex); - m_pauseCount++; - - if (m_canPause && !m_isPaused) { - emit pauseRequested(); - } - - while (m_canPause && !m_isPaused) { - if (!m_isPausedCondition.wait(&m_pauseMutex, 1000)) { - qCWarning(m_logger) << "Pause request timed out!"; - m_pauseCount--; - return false; - } - } - return !m_canPause || m_isPaused; -} -void ControllerScriptEngineBase::resume() { - const auto lock = lockMutex(&m_pauseMutex); - if (m_pauseCount > 0) { - m_pauseCount--; - } - m_isPaused = m_pauseCount > 0; - m_isPausedCondition.wakeOne(); -} -void ControllerScriptEngineBase::doPause() { - const auto lock = lockMutex(&m_pauseMutex); - m_isPaused = m_pauseCount > 0; - m_isPausedCondition.wakeOne(); - - while (m_canPause && m_isPaused) { - VERIFY_OR_DEBUG_ASSERT(m_isPausedCondition.wait(&m_pauseMutex, 1000)) { - qCWarning(m_logger) << "Main GUI pause timed out!"; - m_isPaused = false; - m_pauseCount = 0; - }; - } - m_isPausedCondition.wakeAll(); -} - void ControllerScriptEngineBase::showQMLExceptionDialog( const QQmlError& error, bool bFatalError) { VERIFY_OR_DEBUG_ASSERT(error.isValid()) { diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index d2dd9abcc4f3..2129184b641b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -8,6 +8,9 @@ #include #include "util/runtimeloggingcategory.h" +#ifdef MIXXX_USE_QML +#include "controllers/controllerenginethreadcontrol.h" +#endif class Controller; class QJSEngine; @@ -92,6 +95,7 @@ class ControllerScriptEngineBase : public QObject { private: static inline std::shared_ptr s_pTrackCollectionManager; + protected: /// Pause the GUI main thread. Pause is required by rendering /// thread (https://doc.qt.io/qt-6/qquickrendercontrol.html#sync). This /// offscreen render thread to pause the main "GUI thread" for onboard @@ -100,21 +104,7 @@ class ControllerScriptEngineBase : public QObject { /// testing, it appears that the "GUI main thread" is the thread where the QML /// engine leaves in (also the main thread if we were using a /// QMLApplication, which isn't the case here) - QWaitCondition m_isPausedCondition; - QMutex m_pauseMutex; - int m_pauseCount{0}; - bool m_isPaused{false}; - bool m_canPause{false}; - - public slots: - bool pause(); - void resume(); - void setCanPause(bool canPause); - private slots: - void doPause(); - - signals: - void pauseRequested(); + ControllerEngineThreadControl m_engineThreadControl; #endif protected slots: diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 422cc5f05741..c1bd84954028 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -297,7 +297,7 @@ bool ControllerScriptEngineLegacy::initialize() { return false; } availableScreens.insert(screen.identifier, - std::make_shared(screen, this)); + std::make_shared(screen, &m_engineThreadControl)); if (!availableScreens.value(screen.identifier)->isValid()) { qCWarning(m_logger) << QString( @@ -466,7 +466,7 @@ bool ControllerScriptEngineLegacy::initialize() { }; #ifdef MIXXX_USE_QML - setCanPause(true); + m_engineThreadControl.setCanPause(true); for (const auto& pScreen : std::as_const(m_renderingScreens)) { pScreen->start(); } @@ -685,7 +685,7 @@ void ControllerScriptEngineLegacy::shutdown() { } #ifdef MIXXX_USE_QML - setCanPause(false); + m_engineThreadControl.setCanPause(false); // Wait till the splash off animation has finished rendering std::chrono::milliseconds maxSplashOffDuration{}; for (const auto& pScreen : std::as_const(m_renderingScreens)) { diff --git a/src/util/thread_affinity.h b/src/util/thread_affinity.h index 22f5919a0a25..04d1f77c8964 100644 --- a/src/util/thread_affinity.h +++ b/src/util/thread_affinity.h @@ -17,3 +17,15 @@ /// thread of the application. #define DEBUG_ASSERT_MAIN_THREAD_AFFINITY() \ DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(QCoreApplication::instance()) + +#define DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY() \ + DEBUG_ASSERT(thread() != QThread::currentThread()) + +#define VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_ANTI_AFFINITY() \ + VERIFY_OR_DEBUG_ASSERT(thread() != QThread::currentThread()) + +#define DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY() \ + DEBUG_ASSERT(thread() == QThread::currentThread()) + +#define VERIFY_OR_DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY() \ + VERIFY_OR_DEBUG_ASSERT(thread() == QThread::currentThread()) From ee6dfe545ae417e40ca1bfe0ef8625b344d63acd Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 16 May 2024 22:56:28 +0100 Subject: [PATCH 17/26] Some more nits --- .../controllerenginethreadcontrol.h | 2 +- .../rendering/controllerrenderingengine.h | 5 ++- .../legacy/controllerscriptenginelegacy.cpp | 33 ++++++++++--------- .../legacy/controllerscriptenginelegacy.h | 2 +- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/controllers/controllerenginethreadcontrol.h b/src/controllers/controllerenginethreadcontrol.h index 73ccffc64d80..a31298de545b 100644 --- a/src/controllers/controllerenginethreadcontrol.h +++ b/src/controllers/controllerenginethreadcontrol.h @@ -26,7 +26,7 @@ class ControllerEngineThreadControl : public QObject { void resume(); // Change whether or not it is possible to pause the thread. Should be - // called from eh same thread than ControllerEngineThreadControl + // called from the same thread than ControllerEngineThreadControl void setCanPause(bool canPause); private slots: // Used to effectively pause the thread. Must be called from the same thread diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 9af682352041..8606367be949 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -4,6 +4,7 @@ #include #include +#include #include "controllers/legacycontrollermapping.h" #include "preferences/configobject.h" @@ -26,6 +27,8 @@ class ControllerRenderingEngine : public QObject { public: ControllerRenderingEngine(const LegacyControllerMapping::ScreenInfo& info, ControllerEngineThreadControl* parent); + // Destructor will wait for the ControllerRenderingEngine's thread to + // complete. It should be called from the Controller thread. ~ControllerRenderingEngine(); bool event(QEvent* event) override; @@ -102,5 +105,5 @@ class ControllerRenderingEngine : public QObject { // made that ControllerScriptEngineBase always outlive // ControllerRenderingEngine as it is in charge of stopping and joining the // thread - ControllerEngineThreadControl* m_pEngineThreadControl; + gsl::not_null m_pEngineThreadControl; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index c1bd84954028..22839dabe5fe 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -6,6 +6,13 @@ #include #include #include +#include + +// Prevent conflict with methods called 'emit' in source +#pragma push_macro("emit") +#undef emit +#include +#pragma pop_macro("emit") #endif #include "control/controlobject.h" @@ -25,20 +32,15 @@ using Clock = std::chrono::steady_clock; #endif -namespace { #ifdef MIXXX_USE_QML +namespace { const QByteArray kScreenTransformFunctionUntypedSignature = QMetaObject::normalizedSignature( "transformFrame(QVariant,QVariant)"); const QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); - -template -void delete_later(T* screen) { - screen->deleteLater(); -} -#endif } // anonymous namespace +#endif ControllerScriptEngineLegacy::ControllerScriptEngineLegacy( Controller* controller, const RuntimeLoggingCategory& logger) @@ -255,14 +257,13 @@ void ControllerScriptEngineLegacy::setScriptFiles( m_scriptFiles = scripts; #ifdef MIXXX_USE_QML - for (const LegacyControllerMapping::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { - if (script.type == LegacyControllerMapping::ScriptFileInfo::Type::Qml) { - setQMLMode(true); - return; - } - } - - setQMLMode(false); + setQMLMode(std::any_of(std::execution::par_unseq, + m_scriptFiles.cbegin(), + m_scriptFiles.cend(), + [](const auto& scriptFileInfo) { + return scriptFileInfo.type == + LegacyControllerMapping::ScriptFileInfo::Type::Qml; + })); #endif } @@ -597,7 +598,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( emit previewRenderedScreen(screeninfo, screenDebug); } - QByteArray input((const char*)frame.constBits(), frame.sizeInBytes()); + QByteArray input(reinterpret_cast(frame.constBits()), frame.sizeInBytes()); const TransformScreenFrameFunction& tranformMethod = m_transformScreenFrameFunctions[screeninfo.identifier]; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 3b5c391057dd..e4c6e3365f37 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -110,7 +110,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QHash m_transformScreenFrameFunctions; QList m_modules; QList m_infoScreens; - QString m_resourcePath{"."}; + QString m_resourcePath{QStringLiteral(".")}; #endif QList m_incomingDataFunctions; QHash m_scriptWrappedFunctionCache; From d60c2bd0fca56c0d6741a61796736ba1aafef55b Mon Sep 17 00:00:00 2001 From: Antoine C Date: Thu, 16 May 2024 23:02:59 +0100 Subject: [PATCH 18/26] Explicit copy of the image frame --- src/controllers/rendering/controllerrenderingengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index b3e1433f9385..c23e3fabd27d 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -329,7 +329,7 @@ void ControllerRenderingEngine::renderFrame() { fboImage.mirror(false, true); - emit frameRendered(m_screenInfo, fboImage, timestamp); + emit frameRendered(m_screenInfo, fboImage.copy(), timestamp); m_context->doneCurrent(); } From d9b135cf2785ca60043bc0204b6e082a9975ef53 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Fri, 17 May 2024 17:53:11 +0100 Subject: [PATCH 19/26] use bit_cast --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 22839dabe5fe..d2a0bae3ec62 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -598,7 +598,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( emit previewRenderedScreen(screeninfo, screenDebug); } - QByteArray input(reinterpret_cast(frame.constBits()), frame.sizeInBytes()); + QByteArray input(std::bit_cast(frame.constBits()), frame.sizeInBytes()); const TransformScreenFrameFunction& tranformMethod = m_transformScreenFrameFunctions[screeninfo.identifier]; From 65d3286c18528899986e1f48b352459b3b385a9c Mon Sep 17 00:00:00 2001 From: Antoine Colombier <7086688+acolombier@users.noreply.github.com> Date: Sun, 19 May 2024 15:57:24 +0100 Subject: [PATCH 20/26] Fix grammar issues Co-authored-by: Owen Williams --- .../controllerenginethreadcontrol.cpp | 2 +- .../controllerenginethreadcontrol.h | 21 ++++++------ src/controllers/dlgprefcontroller.cpp | 4 +-- src/controllers/dlgprefcontroller.h | 4 +-- src/controllers/legacycontrollermapping.h | 32 +++++++++---------- .../rendering/controllerrenderingengine.cpp | 10 +++--- .../rendering/controllerrenderingengine.h | 12 +++---- .../legacy/controllerscriptenginelegacy.cpp | 16 +++++----- .../legacy/controllerscriptenginelegacy.h | 4 +-- 9 files changed, 52 insertions(+), 53 deletions(-) diff --git a/src/controllers/controllerenginethreadcontrol.cpp b/src/controllers/controllerenginethreadcontrol.cpp index 91b61a43d9da..24afdd78ed5e 100644 --- a/src/controllers/controllerenginethreadcontrol.cpp +++ b/src/controllers/controllerenginethreadcontrol.cpp @@ -62,7 +62,7 @@ void ControllerEngineThreadControl::setCanPause(bool canPause) { // New signals may have been queued emitted requesting for pause, so we // manually process the event loop now to clear and handle those, before // disabling pausing. Without this, thread requesting pause will stay - // stuck waiting on the condvar + // stuck waiting on the condvar. lock.unlock(); QCoreApplication::processEvents(); lock.relock(); diff --git a/src/controllers/controllerenginethreadcontrol.h b/src/controllers/controllerenginethreadcontrol.h index a31298de545b..ae5cba333f7e 100644 --- a/src/controllers/controllerenginethreadcontrol.h +++ b/src/controllers/controllerenginethreadcontrol.h @@ -4,14 +4,14 @@ #include #include -/// @brief A ControllerEngineThreadControl can be use to orchestrate thread -/// pause on a ControllerScriptEngineBase. It used Pause is required by +/// @brief A ControllerEngineThreadControl can be used to orchestrate thread +/// pausing on a ControllerScriptEngineBase. Pausing is required by the /// rendering thread (https://doc.qt.io/qt-6/qquickrendercontrol.html#sync). -/// This offscreen render thread to pause the main "GUI thread" for onboard -/// screens -/// The documentation isn't completely clear about this, but after +/// This offscreen render thread is used to pause the main "GUI thread" for onboard +/// screens. +/// The Qt documentation isn't completely clear about this, but after /// testing, it appears that the "GUI main thread" is the thread where the QML -/// engine leaves in (also the main thread if we were using a +/// engine lives in (also the main thread if we were using a /// QMLApplication, which isn't the case here) class ControllerEngineThreadControl : public QObject { Q_OBJECT @@ -19,18 +19,17 @@ class ControllerEngineThreadControl : public QObject { explicit ControllerEngineThreadControl(QObject* parent = nullptr); public slots: - // The following slots may be used by rendering engine to pause the thread. - // They must be called from different thread than - // ControllerEngineThreadControl's + // The following slots may be used by the rendering engine to pause the thread. + // They must be called from a different thread than ControllerEngineThreadControl's. bool pause(); void resume(); // Change whether or not it is possible to pause the thread. Should be - // called from the same thread than ControllerEngineThreadControl + // called from the same thread as ControllerEngineThreadControl. void setCanPause(bool canPause); private slots: // Used to effectively pause the thread. Must be called from the same thread - // than ControllerEngineThreadControl + // as ControllerEngineThreadControl. void doPause(); signals: diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 132a57ccc194..f68face7a5ab 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -41,8 +41,8 @@ QString mappingNameToPath(const QString& directory, const QString& mappingName) const QString kBuiltinFileSuffix = QStringLiteral(" (") + QObject::tr("built-in") + QStringLiteral(")"); -/// @brief Format a controller file to display attributes (system, missing) in the UI -/// @return The formatted string +/// @brief Format a controller file to display attributes (system, missing) in the UI. +/// @return The formatted string. QString formatFilePath(UserSettingsPointer pConfig, QColor linkColor, const QString& name, diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 72938fbafbb4..43de384dce46 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -65,9 +65,9 @@ class DlgPrefController : public DlgPreferencePage { void enableWizardAndIOTabs(bool enable); #ifdef MIXXX_USE_QML - // Onboard screen controller + // Onboard screen controller. void slotShowPreviewScreens(std::shared_ptr scriptEngine); - // Wrapper used on shutdown + // Wrapper used on shutdown. void slotClearPreviewScreens() { slotShowPreviewScreens(nullptr); } diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 4678ecd1c78d..e82cece758ff 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -110,28 +110,28 @@ class LegacyControllerMapping { Little = static_cast(std::endian::little), }; - QString identifier; // The screen identifier - QSize size; // the size of the screen - uint target_fps; // the maximum FPS to render - uint msaa; // the MSAA value to use for render + QString identifier; // The screen identifier. + QSize size; // The size of the screen. + uint target_fps; // The maximum FPS to render. + uint msaa; // The MSAA value to use for render. std::chrono::milliseconds - splash_off; // the rendering grace time given when the screen is - // requested to shutdown - QImage::Format pixelFormat; // the pixel encoding format - ColorEndian endian; // the pixel endian format - bool reversedColor; // whether or not the RGB is swapped BGR - bool rawData; // whether or not the screen is allowed to receive bare - // data, not transformed + splash_off; // The rendering grace time given when the screen is + // requested to shutdown. + QImage::Format pixelFormat; // The pixel encoding format. + ColorEndian endian; // The pixel endian format. + bool reversedColor; // Whether or not the RGB is swapped BGR. + bool rawData; // Whether or not the screen is allowed to receive bare + // data, not transformed. }; #endif /// Adds a script file to the list of controller scripts for this mapping. - /// @param filename Name of the script file to add + /// @param filename Name of the script file to add. /// @param identifier The script's function prefix with Javascript OR the - /// screen identifier this QML should be run for (or empty string) - /// @param file A FileInfo object pointing to the script file - /// @param type A ScriptFileInfo::Type the specify script file type - /// @param builtin If this is true, the script won't be written to the XML + /// screen identifier this QML should be run for (or empty string). + /// @param file A FileInfo object pointing to the script file. + /// @param type A ScriptFileInfo::Type the specify script file type. + /// @param builtin If this is true, the script won't be written to the XML. virtual void addScriptFile(const QString& name, const QString& identifier, const QFileInfo& file, diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index c23e3fabd27d..5413f14737f3 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -23,7 +23,7 @@ #include "util/time.h" #include "util/timer.h" -// Used in the renderFrame method to properly abort the rendering and terminate the engine +// Used in the renderFrame method to properly abort the rendering and terminate the engine. #define VERIFY_OR_TERMINATE(cond, msg) \ VERIFY_OR_DEBUG_ASSERT(cond) { \ kLogger.warning() << msg; \ @@ -172,7 +172,7 @@ void ControllerRenderingEngine::setup(std::shared_ptr qmlEngine) { m_offscreenSurface = std::make_unique(); m_offscreenSurface->setFormat(m_context->format()); - // offscreen surface needs to be created from application main thread + // offscreen surface needs to be created from application main thread. VERIFY_OR_DEBUG_ASSERT(QMetaObject::invokeMethod( qApp, [this] { @@ -219,7 +219,7 @@ void ControllerRenderingEngine::finish() { }); m_quickWindow.reset(); - // Free the engine and FBO + // Free the engine and FBO. m_fbo.reset(); m_context->doneCurrent(); @@ -304,7 +304,7 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->render(); m_renderControl->endFrame(); - // Flush any remaining GL errors + // Flush any remaining GL errors. while ((glError = m_context->functions()->glGetError()) != GL_NO_ERROR) { kLogger.debug() << "Retrieved a previously unhandled GL error: " << glError; } @@ -381,7 +381,7 @@ void ControllerRenderingEngine::send(Controller* controller, const QByteArray& f bool ControllerRenderingEngine::event(QEvent* event) { // In case there is a request for update (e.g using QWindow::requestUpdate), - // we emit the signal to request rendering using the engine + // we emit the signal to request rendering using the engine. if (event->type() == QEvent::UpdateRequest) { renderFrame(); return true; diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index 8606367be949..d2044a0b965f 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -55,12 +55,12 @@ class ControllerRenderingEngine : public QObject { public slots: // Request sending frame data to the device. The task will be run in the // rendering event loop. This method should only be called once received the - // `frameRendered` signal + // `frameRendered` signal. virtual void requestSendingFrameData(Controller* controller, const QByteArray& frame); // Request setting up the rendering context for QML engine and wait till it // is completed. The task will be run in the rendering event loop to ensure // thread affinity of engine components. `isValid` can be used to ensure - // that the setup was successful + // that the setup was successful. void requestEngineSetup(std::shared_ptr qmlEngine); void start(); virtual bool stop(); @@ -76,9 +76,9 @@ class ControllerRenderingEngine : public QObject { QImage frame, const QDateTime& timestamp); void stopping(); - /// @brief Request the screen thread to send a frame to the device - /// @param controller the controller to send the frame to - /// @param frame the frame data, ready to be sent + /// @brief Request the screen thread to send a frame to the device. + /// @param controller the controller to send the frame to. + /// @param frame the frame data, ready to be sent. void sendFrameDataRequested(Controller* controller, const QByteArray& frame); private: @@ -104,6 +104,6 @@ class ControllerRenderingEngine : public QObject { // Engine control is owned by ControllerScriptEngineBase. The assumption is // made that ControllerScriptEngineBase always outlive // ControllerRenderingEngine as it is in charge of stopping and joining the - // thread + // thread. gsl::not_null m_pEngineThreadControl; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index d2a0bae3ec62..ac03ac2ea4c1 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -285,7 +285,7 @@ bool ControllerScriptEngineLegacy::initialize() { } #ifdef MIXXX_USE_QML - // During the initialisation, any QML errors are considered fatal + // During the initialisation, any QML errors are considered fatal. setErrorsAreFatal(true); QMap> availableScreens; @@ -312,7 +312,7 @@ bool ControllerScriptEngineLegacy::initialize() { } // Rename the ControllerRenderingEngine with the actual screen - // identifier to help debbuging + // identifier to help debugging. availableScreens.value(screen.identifier) ->thread() ->setObjectName( @@ -479,7 +479,7 @@ bool ControllerScriptEngineLegacy::initialize() { return false; } #ifdef MIXXX_USE_QML - // At runtime, QML errors aren't considered fatal anymore now that the engine has started + // At runtime, QML errors aren't considered fatal anymore now that the engine has started. setErrorsAreFatal(false); #endif @@ -551,7 +551,7 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( &ControllerScriptEngineLegacy::handleScreenFrame); m_renderingScreens.insert(screenIdentifier, pScreen); m_rootItems.insert(screenIdentifier, pScene); - // In case a rendering issue occurs, we need to shutdown the controller + // In case a rendering issue occurs, we need to shutdown the controller. connect(pScreen.get(), &ControllerRenderingEngine::stopping, this, @@ -621,7 +621,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; qCDebug(m_logger) << "Controller JS error is:" << m_pJSEngine->catchError().toString(); } - // During the frame transformation, any QML errors are considered fatal + // During the frame transformation, any QML errors are considered fatal. setErrorsAreFatal(true); bool isSuccessful = tranformMethod.typed ? tranformMethod.method.invoke( @@ -644,7 +644,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( // We manually stop the screen before we trigger the shutdown procedure // as this last one may continue rendering process in order to perform - // screen splash off + // screen splash off. shutdown(); return; } @@ -687,7 +687,7 @@ void ControllerScriptEngineLegacy::shutdown() { #ifdef MIXXX_USE_QML m_engineThreadControl.setCanPause(false); - // Wait till the splash off animation has finished rendering + // Wait till the splash off animation has finished rendering. std::chrono::milliseconds maxSplashOffDuration{}; for (const auto& pScreen : std::as_const(m_renderingScreens)) { if (!pScreen->isRunning()) { @@ -708,7 +708,7 @@ void ControllerScriptEngineLegacy::shutdown() { for (const auto& pScreen : std::as_const(m_renderingScreens)) { // When stopping, the rendering engine emits an event which triggers the // shutdown in case it was initiated following a rendering issue. We - // need to disconnect first before stopping + // need to disconnect first before stopping. pScreen->disconnect(this); VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || !pScreen->isRunning() || pScreen->stop()) { diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index e4c6e3365f37..c3174de44761 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -64,9 +64,9 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { const QDateTime& timestamp); signals: - /// Emitted when a screen has been rendered + /// Emitted when a screen has been rendered. // TODO (XXX) Move this signal in ControllerScriptEngineBase when ScreenInfo - // isn't tight to LegacyControllerMapping anymore + // isn't tight to LegacyControllerMapping anymore. void previewRenderedScreen(const LegacyControllerMapping::ScreenInfo& screen, QImage frame); #endif From b6c158bc96cbf2dbe4dfeffe1215df34b8ba7adc Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 19 May 2024 16:36:44 +0100 Subject: [PATCH 21/26] Add mapping resource by struct instead or arg --- src/controllers/defs_controllers.h | 1 - src/controllers/legacycontrollermapping.h | 57 +---- .../legacycontrollermappingfilehandler.cpp | 25 +- .../controller_mapping_file_handler_test.cpp | 227 +++++++++--------- 4 files changed, 140 insertions(+), 170 deletions(-) diff --git a/src/controllers/defs_controllers.h b/src/controllers/defs_controllers.h index 7e3471671b97..5872f3953a86 100644 --- a/src/controllers/defs_controllers.h +++ b/src/controllers/defs_controllers.h @@ -25,5 +25,4 @@ inline QString userMappingsPath(UserSettingsPointer pConfig) { #define HID_MAPPING_EXTENSION ".hid.xml" #define MIDI_MAPPING_EXTENSION ".midi.xml" #define BULK_MAPPING_EXTENSION ".bulk.xml" -#define REQUIRED_SCRIPT_FILE "common-controller-scripts.js" #define XML_SCHEMA_VERSION "1" diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index e82cece758ff..3c74c7c85889 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -65,15 +65,13 @@ class LegacyControllerMapping { #endif }; - ScriptFileInfo() - : builtin(false) { - } - - QString name; - QString identifier; - QFileInfo file; - Type type; - bool builtin; + QString name; // Name of the script file to add. + QString identifier; // The script's function prefix with Javascript OR + // the screen identifier this QML should be run for + // (or empty string). + QFileInfo file; // A FileInfo object pointing to the script file. + Type type; // A ScriptFileInfo::Type the specify script file type. + bool builtin; // If this is true, the script won't be written to the XML. }; // TODO (xxx): this is a temporary solution to address devices that don't @@ -126,24 +124,9 @@ class LegacyControllerMapping { #endif /// Adds a script file to the list of controller scripts for this mapping. - /// @param filename Name of the script file to add. - /// @param identifier The script's function prefix with Javascript OR the - /// screen identifier this QML should be run for (or empty string). - /// @param file A FileInfo object pointing to the script file. - /// @param type A ScriptFileInfo::Type the specify script file type. - /// @param builtin If this is true, the script won't be written to the XML. - virtual void addScriptFile(const QString& name, - const QString& identifier, - const QFileInfo& file, - ScriptFileInfo::Type type = ScriptFileInfo::Type::Javascript, - bool builtin = false) { - ScriptFileInfo info; - info.name = name; - info.identifier = identifier; - info.file = file; - info.type = type; - info.builtin = builtin; - m_scripts.append(info); + /// @param info The script info to add. + virtual void addScriptFile(ScriptFileInfo info) { + m_scripts.append(std::move(info)); setDirty(true); } @@ -233,24 +216,8 @@ class LegacyControllerMapping { /// @param endian the pixel endian format /// @param reversedColor whether or not the RGB is swapped BGR /// @param rawData whether or not the screen is allowed to reserve bare data, not transformed - virtual void addScreenInfo(const QString& identifier, - const QSize& size, - uint targetFps, - uint msaa, - std::chrono::milliseconds splashoff, - QImage::Format pixelFormat, - LegacyControllerMapping::ScreenInfo::ColorEndian endian, - bool reversedColor, - bool rawData) { - m_screens.append(ScreenInfo{identifier, - size, - targetFps, - msaa, - splashoff, - pixelFormat, - endian, - reversedColor, - rawData}); + virtual void addScreenInfo(ScreenInfo info) { + m_screens.append(std::move(info)); setDirty(true); } diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 9ca61b0f0bd7..dbcd8e23bba9 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -27,6 +27,8 @@ QMap namespace { const mixxx::Logger kLogger("LegacyControllerMappingFileHandler"); +const QString kRequiredScriptFile = QStringLiteral("common-controller-scripts.js"); + #ifdef MIXXX_USE_QML /// Find a module directory (QML) in the mapping or system path. @@ -165,7 +167,8 @@ bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapp } kLogger.debug() << "Adding screen" << identifier; - mapping->addScreenInfo(identifier, + mapping->addScreenInfo(LegacyControllerMapping::ScreenInfo{ + identifier, QSize(width, height), targetFps, msaa, @@ -173,7 +176,7 @@ bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapp pixelFormat, endian, reversedColor, - rawData); + rawData}); return true; } #endif @@ -374,11 +377,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( .firstChildElement("file"); // Default currently required file - mapping->addScriptFile(REQUIRED_SCRIPT_FILE, + mapping->addScriptFile(LegacyControllerMapping::ScriptFileInfo{ + kRequiredScriptFile, "", - findScriptFile(mapping, REQUIRED_SCRIPT_FILE, systemMappingsPath), + findScriptFile(mapping, kRequiredScriptFile, systemMappingsPath), LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true); + true}); // Look for additional ones while (!scriptFile.isNull()) { @@ -387,10 +391,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( if (file.suffix() == "qml") { #ifdef MIXXX_USE_QML QString identifier = scriptFile.attribute("identifier", ""); - mapping->addScriptFile(filename, + mapping->addScriptFile(LegacyControllerMapping::ScriptFileInfo{ + filename, identifier, file, - LegacyControllerMapping::ScriptFileInfo::Type::Qml); + LegacyControllerMapping::ScriptFileInfo::Type::Qml, + false}); #else kLogger.warning() << "Unsupported render scene for file" << file.filePath() @@ -399,10 +405,11 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( #endif } else { QString functionPrefix = scriptFile.attribute("functionprefix", ""); - mapping->addScriptFile(filename, + mapping->addScriptFile(LegacyControllerMapping::ScriptFileInfo{filename, functionPrefix, file, - LegacyControllerMapping::ScriptFileInfo::Type::Javascript); + LegacyControllerMapping::ScriptFileInfo::Type::Javascript, + false}); } scriptFile = scriptFile.nextSiblingElement("file"); } diff --git a/src/test/controller_mapping_file_handler_test.cpp b/src/test/controller_mapping_file_handler_test.cpp index e2913f3db2a6..57dd8594f347 100644 --- a/src/test/controller_mapping_file_handler_test.cpp +++ b/src/test/controller_mapping_file_handler_test.cpp @@ -12,6 +12,7 @@ #include "util/time.h" using ::testing::_; +using ::testing::FieldsAre; class LegacyControllerMappingFileHandlerTest : public LegacyControllerMappingFileHandler, @@ -47,23 +48,11 @@ class MockLegacyControllerMapping : public LegacyControllerMapping { public: MOCK_METHOD(void, addScriptFile, - (const QString& name, - const QString& identifier, - const QFileInfo& file, - ScriptFileInfo::Type type, - bool builtin), + (LegacyControllerMapping::ScriptFileInfo info), (override)); MOCK_METHOD(void, addScreenInfo, - (const QString& identifier, - const QSize& size, - uint targetFps, - uint msaa, - std::chrono::milliseconds splashoff, - QImage::Format pixelFormat, - LegacyControllerMapping::ScreenInfo::ColorEndian endian, - bool reversedColors, - bool rawData), + (LegacyControllerMapping::ScreenInfo info), (override)); MOCK_METHOD(void, addModule, (const QFileInfo& dirinfo, bool builtin), (override)); @@ -92,19 +81,19 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseSimpleMapping) { auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScriptFile(QString("DummyDeviceDefaultScreen.js"), + addScriptFile(FieldsAre(QString("DummyDeviceDefaultScreen.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - false)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + false))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_CALL(*mapping, addModule(_, _)).Times(0); addScriptFilesToMapping( @@ -132,20 +121,20 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScriptFile(QString("DummyDeviceDefaultScreen.qml"), + addScriptFile(FieldsAre(QString("DummyDeviceDefaultScreen.qml"), QString(""), QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), LegacyControllerMapping::ScriptFileInfo::Type::Qml, - false)); + false))); EXPECT_CALL(*mapping, - addScreenInfo(QString("main"), + addScreenInfo(FieldsAre(QString("main"), QSize(480, 360), 20, 1, @@ -153,7 +142,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseScreenMapping) { QImage::Format_RGBA8888, LegacyControllerMapping::ScreenInfo::ColorEndian::Little, false, - false)); + false))); EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); addScriptFilesToMapping( @@ -176,12 +165,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, 20, _, _, _, _, _, _)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, 20, _, _, _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -201,12 +190,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + true))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG(QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") .arg(kMaxTargetFps)); @@ -229,16 +218,16 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"targetFps\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -262,13 +251,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -292,16 +281,16 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingTargetFPS) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"targetFps\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid target FPS. Target FPS must be between 1 and %0") @@ -328,12 +317,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, QSize(10, 10), _, _, _, _, _, _, _)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, QSize(10, 10), _, _, _, _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -353,12 +342,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + true))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -381,15 +370,15 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"height\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -412,15 +401,15 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"height\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -443,12 +432,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingSize) { mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + true))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Invalid screen size. Screen size must have a width and height above 1 pixel"); @@ -478,12 +467,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB888, _, _, _)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, QImage::Format_RGB888, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -502,12 +491,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, QImage::Format_RGB16, _, _, _)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, QImage::Format_RGB16, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -526,12 +515,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + true))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Unsupported pixel format \"FOOBAR\""); @@ -554,13 +543,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScreenInfo(_, + addScreenInfo(FieldsAre(_, _, _, _, @@ -568,7 +557,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, LegacyControllerMapping::ScreenInfo::ColorEndian::Little, _, - _)); + _))); addScriptFilesToMapping( doc.documentElement(), @@ -587,13 +576,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScreenInfo(_, + addScreenInfo(FieldsAre(_, _, _, _, @@ -601,7 +590,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, LegacyControllerMapping::ScreenInfo::ColorEndian::Little, _, - _)); + _))); addScriptFilesToMapping( doc.documentElement(), @@ -620,13 +609,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScreenInfo(_, + addScreenInfo(FieldsAre(_, _, _, _, @@ -634,7 +623,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) _, LegacyControllerMapping::ScreenInfo::ColorEndian::Big, _, - _)); + _))); addScriptFilesToMapping( doc.documentElement(), @@ -653,12 +642,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingBitFormatDefinition) mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, _)).Times(0); + true))); + EXPECT_CALL(*mapping, addScreenInfo(_)).Times(0); EXPECT_LOG_MSG( QtWarningMsg, "Unknown endian format \"enormous\""); @@ -694,17 +683,17 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); if (expectedWarning++) { EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"reversed\" as a " "boolean in the screen definition.")); } - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, false, _)); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, _, _, false, _))); addScriptFilesToMapping( doc.documentElement(), @@ -726,12 +715,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, true, _)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, _, _, true, _))); addScriptFilesToMapping( doc.documentElement(), @@ -755,12 +744,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, false)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, _, _, _, false))); if (expectedWarning++) { EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"raw\" as a boolean in " @@ -787,12 +776,12 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraBoolPropertiesD mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, _, _, _, _, true)); + true))); + EXPECT_CALL(*mapping, addScreenInfo(FieldsAre(_, _, _, _, _, _, _, _, true))); addScriptFilesToMapping( doc.documentElement(), @@ -817,12 +806,14 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); + true))); + EXPECT_CALL(*mapping, + addScreenInfo(FieldsAre( + _, _, _, _, std::chrono::milliseconds(0), _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -841,12 +832,14 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(500), _, _, _, _)); + true))); + EXPECT_CALL(*mapping, + addScreenInfo(FieldsAre( + _, _, _, _, std::chrono::milliseconds(500), _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -865,13 +858,13 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScreenInfo(_, + addScreenInfo(FieldsAre(_, _, _, _, @@ -879,7 +872,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe _, _, _, - _)); + _))); EXPECT_LOG_MSG( QtWarningMsg, QString("Invalid splashoff duration. Splashoff duration must " @@ -905,15 +898,17 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"splashoff\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(FieldsAre( + _, _, _, _, std::chrono::milliseconds(0), _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -932,15 +927,17 @@ TEST_F(LegacyControllerMappingFileHandlerTest, screenMappingExtraIntPropertiesDe mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_LOG_MSG(QtWarningMsg, QString("Unable to parse the field \"splashoff\" as an unsigned " "integer in the screen definition.")); - EXPECT_CALL(*mapping, addScreenInfo(_, _, _, _, std::chrono::milliseconds(0), _, _, _, _)); + EXPECT_CALL(*mapping, + addScreenInfo(FieldsAre( + _, _, _, _, std::chrono::milliseconds(0), _, _, _, _))); addScriptFilesToMapping( doc.documentElement(), @@ -969,26 +966,26 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { auto mapping = std::make_shared(); // This file always gets added EXPECT_CALL(*mapping, - addScriptFile(QString(REQUIRED_SCRIPT_FILE), + addScriptFile(FieldsAre(QString("common-controller-scripts.js"), QString(""), _, // gmock seems unable to assert QFileInfo LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - true)); + true))); EXPECT_CALL(*mapping, - addScriptFile(QString("DummyDeviceDefaultScreen.qml"), + addScriptFile(FieldsAre(QString("DummyDeviceDefaultScreen.qml"), QString(""), QFileInfo("/dummy/path/DummyDeviceDefaultScreen.qml"), LegacyControllerMapping::ScriptFileInfo::Type::Qml, - false)); + false))); EXPECT_CALL(*mapping, - addScriptFile(QString("LegacyScript.js"), + addScriptFile(FieldsAre(QString("LegacyScript.js"), QString(""), QFileInfo("/dummy/path/LegacyScript.js"), LegacyControllerMapping::ScriptFileInfo::Type::Javascript, - false)); + false))); EXPECT_CALL(*mapping, - addScreenInfo(QString("main"), + addScreenInfo(FieldsAre(QString("main"), QSize(480, 360), 20, 1, @@ -996,7 +993,7 @@ TEST_F(LegacyControllerMappingFileHandlerTest, canParseHybridMapping) { QImage::Format_RGBA8888, LegacyControllerMapping::ScreenInfo::ColorEndian::Little, false, - false)); + false))); EXPECT_CALL(*mapping, addModule(QFileInfo("/dummy/path/foobar"), false)); addScriptFilesToMapping( From 5861649ead3d73beb1b81391c41f0c739f0a70c2 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 19 May 2024 18:38:09 +0100 Subject: [PATCH 22/26] Don't use callFunctionOnObjects for QML --- .../rendering/controllerrenderingengine.h | 5 +- .../legacy/controllerscriptenginelegacy.cpp | 246 +++++++++++------- .../legacy/controllerscriptenginelegacy.h | 9 + 3 files changed, 161 insertions(+), 99 deletions(-) diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index d2044a0b965f..fd71f8926235 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -4,7 +4,6 @@ #include #include -#include #include "controllers/legacycontrollermapping.h" #include "preferences/configobject.h" @@ -104,6 +103,6 @@ class ControllerRenderingEngine : public QObject { // Engine control is owned by ControllerScriptEngineBase. The assumption is // made that ControllerScriptEngineBase always outlive // ControllerRenderingEngine as it is in charge of stopping and joining the - // thread. - gsl::not_null m_pEngineThreadControl; + // thread. This may be null if there is no need for thread synchronisation. + ControllerEngineThreadControl* m_pEngineThreadControl; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index ac03ac2ea4c1..160422e4db1a 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -39,6 +39,13 @@ const QByteArray kScreenTransformFunctionUntypedSignature = "transformFrame(QVariant,QVariant)"); const QByteArray kScreenTransformFunctionTypedSignature = QMetaObject::normalizedSignature("transformFrame(QVariant,QDateTime)"); +const QByteArray kScreenInitFunctionUntypedSignature = + QMetaObject::normalizedSignature( + "init(QVariant,QVariant)"); +const QByteArray kScreenInitFunctionTypedSignature = + QMetaObject::normalizedSignature("init(QString,bool)"); +const QByteArray kScreenShutdownFunctionSignature = + QMetaObject::normalizedSignature("shutdown()"); } // anonymous namespace #endif @@ -116,28 +123,54 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( success = false; } } + return success; +} + +bool ControllerScriptEngineLegacy::callShutdownFunction() { + // There is no js engine if the mapping was not loaded from a file but by + // creating a new, empty mapping LegacyMidiControllerMapping with the wizard + if (!m_pJSEngine) { + return true; + } + #ifdef MIXXX_USE_QML - if (m_bQmlMode) { + if (!m_bQmlMode) { +#endif + return callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); +#ifdef MIXXX_USE_QML + } else { + VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { + qCWarning(m_logger) << "Controller JS engine has an unhandled error."; + qCDebug(m_logger) << "Unhandled controller JS error is:" + << m_pJSEngine->catchError().toString(); + } QHashIterator> i(m_rootItems); + bool success = true; while (i.hasNext()) { i.next(); const QMetaObject* metaObject = i.value()->metaObject(); - QStringList argList; - for (int i = 0; i < args.size(); i++) { - argList << "QVariant"; + const QString& screenIdentifier = i.key(); + + VERIFY_OR_DEBUG_ASSERT(metaObject) { + qCWarning(m_logger) + << "Invalid meta object for screen" << screenIdentifier + << "It may be that an unhandled issue occurred when importing " + "the scene."; + continue; } - int methodIdx = - metaObject->indexOfMethod(QString("%1(%2)") - .arg(function, argList.join(',')) - .toUtf8()); - if (methodIdx == -1) { - qCWarning(m_logger) << "QML Scene " << i.key() << "has no" - << function << " method"; + + QMetaMethod shutdownFunction; + int methodIdx = metaObject->indexOfMethod(kScreenShutdownFunctionSignature); + + if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { + qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier + << "has no valid shutdown method."; continue; } - QMetaMethod method = metaObject->method(methodIdx); - qCDebug(m_logger) << "Executing" - << function << "on QML Scene " << i.key(); + + shutdownFunction = metaObject->method(methodIdx); + + qCDebug(m_logger) << "Executing shutdown on QML Scene " << screenIdentifier; VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { qCWarning(m_logger) << "Controller JS engine has an unhandled error. Discarding."; @@ -145,55 +178,88 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects( << m_pJSEngine->catchError().toString(); } - switch (args.size()) { - case 0: - success &= method.invoke(i.value().get(), - Qt::DirectConnection); - break; - case 1: - success &= method.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QVariant, args[0].toVariant())); - break; - case 2: - success &= method.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QVariant, args[0].toVariant()), - Q_ARG(QVariant, args[1].toVariant())); - break; - case 3: - success &= method.invoke(i.value().get(), - Qt::DirectConnection, - Q_ARG(QVariant, args[0].toVariant()), - Q_ARG(QVariant, args[1].toVariant()), - Q_ARG(QVariant, args[2].toVariant())); - break; - case 4: - success &= method.invoke(i.value().get(), + success &= shutdownFunction.invoke(i.value().get(), + Qt::DirectConnection); + // Error handling is done in ControllerScriptEngineBase, with the + // connection QQmlEngine::warnings -> + // ControllerScriptEngineBase::handleQMLErrors + } + return success; + } +#endif +} +bool ControllerScriptEngineLegacy::callInitFunction() { + // m_pController is nullptr in tests. + const auto controllerName = m_pController ? m_pController->getName() : QString{}; + +#ifdef MIXXX_USE_QML + if (!m_bQmlMode) { +#endif + const auto args = QJSValueList{ + controllerName, + m_logger().isDebugEnabled(), + }; + return callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true); +#ifdef MIXXX_USE_QML + } else { + VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine->hasError()) { + qCWarning(m_logger) << "Controller JS engine has an unhandled error."; + qCDebug(m_logger) << "Unhandled controller JS error is:" + << m_pJSEngine->catchError().toString(); + } + QHashIterator> i(m_rootItems); + bool success = true; + while (i.hasNext()) { + i.next(); + const QMetaObject* metaObject = i.value()->metaObject(); + const QString& screenIdentifier = i.key(); + + VERIFY_OR_DEBUG_ASSERT(metaObject) { + qCWarning(m_logger) + << "Invalid meta object for screen" << screenIdentifier + << "It may be that an unhandled issue occurred when importing " + "the scene."; + continue; + } + + QMetaMethod initFunction; + bool typed = false; + int methodIdx = metaObject->indexOfMethod(kScreenInitFunctionUntypedSignature); + + if (methodIdx == -1 || !metaObject->method(methodIdx).isValid()) { + qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier + << "has no valid untyped init method."; + methodIdx = metaObject->indexOfMethod(kScreenInitFunctionTypedSignature); + typed = true; + } + + initFunction = metaObject->method(methodIdx); + + if (!initFunction.isValid()) { + qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier + << "has no valid typed init method. Skipping."; + continue; + } + + qCDebug(m_logger) << "Executing init on QML Scene " << screenIdentifier; + if (typed) { + success &= initFunction.invoke(i.value().get(), Qt::DirectConnection, - Q_ARG(QVariant, args[0].toVariant()), - Q_ARG(QVariant, args[1].toVariant()), - Q_ARG(QVariant, args[2].toVariant()), - Q_ARG(QVariant, args[3].toVariant())); - break; - default: - qCDebug(m_logger) << "Trying to call a controller lifecycle method with " - "more than 5 args. Ignoring extra args"; - [[fallthrough]]; - case 5: - success &= method.invoke(i.value().get(), + Q_ARG(QString, controllerName), + Q_ARG(bool, m_logger().isDebugEnabled())); + } else { + success &= initFunction.invoke(i.value().get(), Qt::DirectConnection, - Q_ARG(QVariant, args[0].toVariant()), - Q_ARG(QVariant, args[1].toVariant()), - Q_ARG(QVariant, args[2].toVariant()), - Q_ARG(QVariant, args[3].toVariant()), - Q_ARG(QVariant, args[4].toVariant())); - break; + Q_ARG(QVariant, controllerName), + Q_ARG(QVariant, m_logger().isDebugEnabled())); } + // Error handling is done in ControllerScriptEngineBase, with the + // connection QQmlEngine::warnings -> + // ControllerScriptEngineBase::handleQMLErrors } + return success; } #endif - return success; } QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( @@ -459,13 +525,6 @@ bool ControllerScriptEngineLegacy::initialize() { wrapFunctionCode(functionName, 2))); } - // m_pController is nullptr in tests. - const auto controllerName = m_pController ? m_pController->getName() : QString{}; - const auto args = QJSValueList{ - controllerName, - m_logger().isDebugEnabled(), - }; - #ifdef MIXXX_USE_QML m_engineThreadControl.setCanPause(true); for (const auto& pScreen : std::as_const(m_renderingScreens)) { @@ -473,8 +532,7 @@ bool ControllerScriptEngineLegacy::initialize() { } #endif - if ( - !callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true)) { + if (!callInitFunction()) { shutdown(); return false; } @@ -497,7 +555,7 @@ void ControllerScriptEngineLegacy::extractTransformFunction( return; } - QMetaMethod tranformFunction; + QMetaMethod transformFunction; bool typed = false; int methodIdx = metaObject->indexOfMethod(kScreenTransformFunctionUntypedSignature); @@ -508,9 +566,9 @@ void ControllerScriptEngineLegacy::extractTransformFunction( typed = true; } - tranformFunction = metaObject->method(methodIdx); + transformFunction = metaObject->method(methodIdx); - if (!tranformFunction.isValid()) { + if (!transformFunction.isValid()) { qCDebug(m_logger) << "QML Scene for screen" << screenIdentifier << "has no valid typed transformFrame method. The " "frame data will be sent " @@ -523,7 +581,7 @@ void ControllerScriptEngineLegacy::extractTransformFunction( } m_transformScreenFrameFunctions.insert(screenIdentifier, - TransformScreenFrameFunction{tranformFunction, typed}); + TransformScreenFrameFunction{transformFunction, typed}); } bool ControllerScriptEngineLegacy::bindSceneToScreen( @@ -560,16 +618,16 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( } void ControllerScriptEngineLegacy::handleScreenFrame( - const LegacyControllerMapping::ScreenInfo& screeninfo, + const LegacyControllerMapping::ScreenInfo& screenInfo, const QImage& frame, const QDateTime& timestamp) { VERIFY_OR_DEBUG_ASSERT( - m_transformScreenFrameFunctions.contains(screeninfo.identifier) || - m_renderingScreens.contains(screeninfo.identifier)) { + m_transformScreenFrameFunctions.contains(screenInfo.identifier) || + m_renderingScreens.contains(screenInfo.identifier)) { qCWarning(m_logger) << "Unable to find transform function info for the given screen"; return; }; - VERIFY_OR_DEBUG_ASSERT(m_rootItems.contains(screeninfo.identifier)) { + VERIFY_OR_DEBUG_ASSERT(m_rootItems.contains(screenInfo.identifier)) { qCWarning(m_logger) << "Unable to find a root item for the given screen"; return; }; @@ -577,7 +635,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( if (CmdlineArgs::Instance().getControllerPreviewScreens()) { QImage screenDebug(frame); - switch (screeninfo.endian) { + switch (screenInfo.endian) { case LegacyControllerMapping::ScreenInfo::ColorEndian::Big: qFromBigEndian(frame.constBits(), frame.sizeInBytes() / 2, @@ -591,27 +649,27 @@ void ControllerScriptEngineLegacy::handleScreenFrame( default: break; } - if (screeninfo.reversedColor) { + if (screenInfo.reversedColor) { screenDebug.rgbSwap(); } - emit previewRenderedScreen(screeninfo, screenDebug); + emit previewRenderedScreen(screenInfo, screenDebug); } QByteArray input(std::bit_cast(frame.constBits()), frame.sizeInBytes()); - const TransformScreenFrameFunction& tranformMethod = - m_transformScreenFrameFunctions[screeninfo.identifier]; + const TransformScreenFrameFunction& transformMethod = + m_transformScreenFrameFunctions[screenInfo.identifier]; - if (!tranformMethod.method.isValid() && screeninfo.rawData) { - m_renderingScreens[screeninfo.identifier]->requestSendingFrameData(m_pController, input); + if (!transformMethod.method.isValid() && screenInfo.rawData) { + m_renderingScreens[screenInfo.identifier]->requestSendingFrameData(m_pController, input); return; } - if (!tranformMethod.method.isValid()) { + if (!transformMethod.method.isValid()) { qCWarning(m_logger) << "Could not find a valid transform function but the screen " "doesn't accept raw data. Aborting screen rendering."; - m_renderingScreens[screeninfo.identifier]->stop(); + m_renderingScreens[screenInfo.identifier]->stop(); return; } @@ -623,15 +681,15 @@ void ControllerScriptEngineLegacy::handleScreenFrame( } // During the frame transformation, any QML errors are considered fatal. setErrorsAreFatal(true); - bool isSuccessful = tranformMethod.typed - ? tranformMethod.method.invoke( - m_rootItems.value(screeninfo.identifier).get(), + bool isSuccessful = transformMethod.typed + ? transformMethod.method.invoke( + m_rootItems.value(screenInfo.identifier).get(), Qt::DirectConnection, Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, input), Q_ARG(QDateTime, timestamp)) - : tranformMethod.method.invoke( - m_rootItems.value(screeninfo.identifier).get(), + : transformMethod.method.invoke( + m_rootItems.value(screenInfo.identifier).get(), Qt::DirectConnection, Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, input), @@ -640,7 +698,7 @@ void ControllerScriptEngineLegacy::handleScreenFrame( if (!isSuccessful) { qCWarning(m_logger) << "Could not transform rendering buffer for screen" - << screeninfo.identifier; + << screenInfo.identifier; // We manually stop the screen before we trigger the shutdown procedure // as this last one may continue rendering process in order to perform @@ -667,23 +725,19 @@ void ControllerScriptEngineLegacy::handleScreenFrame( } if (CmdlineArgs::Instance().getControllerDebug()) { - qCDebug(m_logger) << "Transform screen data for screen " << screeninfo.identifier + qCDebug(m_logger) << "Transform screen data for screen " << screenInfo.identifier << "(first 64 bytes)" << QByteArray(transformedFrame.toHex(' '), 128); m_pController->sendBytes(returnedValue.view()); } - m_renderingScreens[screeninfo.identifier]->requestSendingFrameData( + m_renderingScreens[screenInfo.identifier]->requestSendingFrameData( m_pController, transformedFrame); } #endif void ControllerScriptEngineLegacy::shutdown() { - // There is no js engine if the mapping was not loaded from a file but by - // creating a new, empty mapping LegacyMidiControllerMapping with the wizard - if (m_pJSEngine) { - callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); - } + callShutdownFunction(); #ifdef MIXXX_USE_QML m_engineThreadControl.setCanPause(false); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index c3174de44761..b0605f4b584e 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -94,6 +94,12 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { }; #endif + /// @brief Call the shutdown hook on the mapping script. + /// @return true if the hook was run successfully, or if there was none. + bool callShutdownFunction(); + /// @brief Call the init hook on the mapping script. + /// @return true if the hook was run successfully, or if there was none. + bool callInitFunction(); void shutdown() override; QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool callFunctionOnObjects(const QList& scriptFunctionPrefixes, @@ -106,6 +112,9 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QList m_scriptFunctionPrefixes; #ifdef MIXXX_USE_QML QHash> m_renderingScreens; + // Contains all the scenes loaded for this mapping. Key is the scene + // identifier (LegacyControllerMapping::ScreenInfo::identifier), value in + // the QML root item QHash> m_rootItems; QHash m_transformScreenFrameFunctions; QList m_modules; From b63577cef37dcded42583b1b422e4e111399cb65 Mon Sep 17 00:00:00 2001 From: Antoine Colombier <7086688+acolombier@users.noreply.github.com> Date: Fri, 24 May 2024 17:11:54 +0100 Subject: [PATCH 23/26] Nits Co-authored-by: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- src/controllers/legacycontrollermappingfilehandler.cpp | 2 +- .../scripting/legacy/controllerscriptenginelegacy.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index dbcd8e23bba9..fcb23df7b77d 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -86,7 +86,7 @@ QFileInfo findScriptFile(std::shared_ptr mapping, } #ifdef MIXXX_USE_QML -#define LOG_OF_NOT_OK(FIELD, TYPE) \ +#define LOG_IF_NOT_OK(FIELD, TYPE) \ if (!ok) { \ kLogger.warning().nospace() \ << "Unable to parse the field \"" << FIELD << "\" as " << TYPE \ diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 160422e4db1a..a34b4b5f6939 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -367,9 +367,7 @@ bool ControllerScriptEngineLegacy::initialize() { std::make_shared(screen, &m_engineThreadControl)); if (!availableScreens.value(screen.identifier)->isValid()) { - qCWarning(m_logger) << QString( - "Unable to start the screen render for %1.") - .arg(screen.identifier); + qCWarning(m_logger) << "Unable to start the screen render for" << screen.identifier; return false; } From cb9a5c20cdd962e095ec409751486443aa05af69 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Fri, 24 May 2024 17:20:57 +0100 Subject: [PATCH 24/26] Comments and co --- src/controllers/controller.cpp | 2 +- src/controllers/controller.h | 2 +- src/controllers/dlgprefcontroller.cpp | 6 +++--- src/controllers/dlgprefcontroller.h | 2 +- .../legacycontrollermappingfilehandler.cpp | 20 +++++++++---------- .../rendering/controllerrenderingengine.cpp | 2 +- .../rendering/controllerrenderingengine.h | 7 ++++--- .../legacy/controllerscriptenginelegacy.cpp | 14 +++++++++---- src/test/controllerrenderingengine_test.cpp | 3 ++- .../controllerscriptenginelegacy_test.cpp | 3 ++- 10 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 7ec97809c624..9cca33f14ad2 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -49,7 +49,7 @@ void Controller::startEngine() &ControllerScriptEngineBase::beforeShutdown, this, &Controller::slotBeforeEngineShutdown); - emit engineStarted(m_pScriptEngineLegacy); + emit engineStarted(m_pScriptEngineLegacy.get()); } void Controller::stopEngine() { diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 14e9d5d40a2f..dcc7ba05032c 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -64,7 +64,7 @@ class Controller : public QObject { void openChanged(bool bOpen); /// Emitted when the controller has started a new engine. - void engineStarted(std::shared_ptr engine); + void engineStarted(const ControllerScriptEngineLegacy* engine); /// Emitted when the controller has stopped its engine. void engineStopped(); diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index f68face7a5ab..d58fbf6b6731 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -866,7 +866,7 @@ void DlgPrefController::initTableView(QTableView* pTable) { #ifdef MIXXX_USE_QML void DlgPrefController::slotShowPreviewScreens( - std::shared_ptr scriptEngine) { + const ControllerScriptEngineLegacy* scriptEngine) { QLayoutItem* pItem; VERIFY_OR_DEBUG_ASSERT(m_ui.groupBoxScreens->layout()) { return; @@ -893,7 +893,7 @@ void DlgPrefController::slotShowPreviewScreens( new ControllerScreenPreview(m_ui.groupBoxScreens, screen); m_ui.groupBoxScreens->layout()->addWidget(pPreviewScreen); - connect(scriptEngine.get(), + connect(scriptEngine, &ControllerScriptEngineLegacy::previewRenderedScreen, pPreviewScreen, &ControllerScreenPreview::updateFrame); @@ -950,7 +950,7 @@ void DlgPrefController::slotShowMapping(std::shared_ptr .getControllerPreviewScreens() && pMapping && !pMapping->getInfoScreens().isEmpty()) { - slotShowPreviewScreens(m_pController->getScriptEngine()); + slotShowPreviewScreens(m_pController->getScriptEngine().get()); } else #endif { diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 43de384dce46..292a0406afec 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -66,7 +66,7 @@ class DlgPrefController : public DlgPreferencePage { #ifdef MIXXX_USE_QML // Onboard screen controller. - void slotShowPreviewScreens(std::shared_ptr scriptEngine); + void slotShowPreviewScreens(const ControllerScriptEngineLegacy* scriptEngine); // Wrapper used on shutdown. void slotClearPreviewScreens() { slotShowPreviewScreens(nullptr); diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index fcb23df7b77d..7782ec8186ca 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -40,11 +40,11 @@ const QString kRequiredScriptFile = QStringLiteral("common-controller-scripts.js /// of the search directories, the QFileInfo object might point to a /// non-existing file. QFileInfo findLibraryPath( - std::shared_ptr mapping, + const LegacyControllerMapping& mapping, const QString& dirname, const QDir& systemMappingsPath) { // Always try to load module directory from the mapping's directory first - QFileInfo dir = QFileInfo(mapping->dirPath().absoluteFilePath(dirname)); + QFileInfo dir = QFileInfo(mapping.dirPath().absoluteFilePath(dirname)); // If the module directory does not exist, try to find it in the fallback dir if (!dir.isDir()) { @@ -100,18 +100,18 @@ bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapp bool ok; QString identifier = screen.attribute("identifier", ""); uint targetFps = screen.attribute("targetFps", "30").toUInt(&ok); - LOG_OF_NOT_OK("targetFps", "an unsigned integer"); + LOG_IF_NOT_OK("targetFps", "an unsigned integer"); uint msaa = screen.attribute("msaa", "1").toUInt(&ok); - LOG_OF_NOT_OK("msaa", "an unsigned integer"); + LOG_IF_NOT_OK("msaa", "an unsigned integer"); QString pixelFormatName = screen.attribute("pixelType", "RBG"); QString endianName = screen.attribute("endian", "little"); bool reversedColor = parseHumanBoolean( screen.attribute("reversed", "false").toLower().trimmed(), &ok); - LOG_OF_NOT_OK("reversed", "a boolean"); + LOG_IF_NOT_OK("reversed", "a boolean"); bool rawData = parseHumanBoolean(screen.attribute("raw", "false").toLower().trimmed(), &ok); - LOG_OF_NOT_OK("raw", "a boolean"); + LOG_IF_NOT_OK("raw", "a boolean"); uint splashOff = screen.attribute("splashoff", "0").toUInt(&ok); - LOG_OF_NOT_OK("splashoff", "an unsigned integer"); + LOG_IF_NOT_OK("splashoff", "an unsigned integer"); if (!targetFps || targetFps > LegacyControllerMappingFileHandler::kMaxTargetFps) { kLogger.warning() @@ -156,9 +156,9 @@ bool parseAndAddScreenDefinition(const QDomElement& screen, LegacyControllerMapp auto endian = LegacyControllerMappingFileHandler::kEndianFormat.value(endianName); uint width = screen.attribute("width", "0").toUInt(&ok); - LOG_OF_NOT_OK("width", "an unsigned integer"); + LOG_IF_NOT_OK("width", "an unsigned integer"); uint height = screen.attribute("height", "0").toUInt(&ok); - LOG_OF_NOT_OK("height", "an unsigned integer"); + LOG_IF_NOT_OK("height", "an unsigned integer"); if (!width || !height) { kLogger.warning() << "Invalid screen size. Screen size must have a width " @@ -433,7 +433,7 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping( // Look for additional ones while (!qmlLibrary.isNull()) { QString libFilename = qmlLibrary.attribute("path", ""); - QFileInfo path = findLibraryPath(mapping, libFilename, systemMappingsPath); + QFileInfo path = findLibraryPath(*mapping, libFilename, systemMappingsPath); kLogger.debug() << "Adding QML directory " << libFilename; mapping->addModule(path); qmlLibrary = qmlLibrary.nextSiblingElement("library"); diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index 5413f14737f3..afcce227168b 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -39,7 +39,7 @@ using Clock = std::chrono::steady_clock; ControllerRenderingEngine::ControllerRenderingEngine( const LegacyControllerMapping::ScreenInfo& info, - ControllerEngineThreadControl* engineThreadControl) + gsl::not_null engineThreadControl) : QObject(), m_screenInfo(info), m_GLDataFormat(GL_RGBA), diff --git a/src/controllers/rendering/controllerrenderingengine.h b/src/controllers/rendering/controllerrenderingengine.h index fd71f8926235..770e2b722716 100644 --- a/src/controllers/rendering/controllerrenderingengine.h +++ b/src/controllers/rendering/controllerrenderingengine.h @@ -4,6 +4,7 @@ #include #include +#include #include "controllers/legacycontrollermapping.h" #include "preferences/configobject.h" @@ -25,7 +26,7 @@ class ControllerRenderingEngine : public QObject { Q_OBJECT public: ControllerRenderingEngine(const LegacyControllerMapping::ScreenInfo& info, - ControllerEngineThreadControl* parent); + gsl::not_null engineThreadControl); // Destructor will wait for the ControllerRenderingEngine's thread to // complete. It should be called from the Controller thread. ~ControllerRenderingEngine(); @@ -103,6 +104,6 @@ class ControllerRenderingEngine : public QObject { // Engine control is owned by ControllerScriptEngineBase. The assumption is // made that ControllerScriptEngineBase always outlive // ControllerRenderingEngine as it is in charge of stopping and joining the - // thread. This may be null if there is no need for thread synchronisation. - ControllerEngineThreadControl* m_pEngineThreadControl; + // thread. + gsl::not_null m_pEngineThreadControl; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index a34b4b5f6939..5a14075b881b 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -371,6 +371,8 @@ bool ControllerScriptEngineLegacy::initialize() { return false; } + // For testing, do not actually initialize the rendering engine, just check for + // compatibility above. if (m_bTesting) { continue; } @@ -428,7 +430,7 @@ bool ControllerScriptEngineLegacy::initialize() { std::as_const(m_modules)) { auto path = module.dirinfo.absoluteFilePath(); QDirIterator it(path, - QStringList() << "*.qml", + {"*.qml"}, QDir::Files, QDirIterator::Subdirectories); while (it.hasNext()) { @@ -593,8 +595,10 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( auto pScene = loadQMLFile(qmlFile, pScreen); if (!pScene) { - pScreen->stop(); - std::move(pScreen)->deleteLater(); + VERIFY_OR_DEBUG_ASSERT(!pScreen->isValid() || + !pScreen->isRunning() || pScreen->stop()) { + qCWarning(m_logger) << "Unable to stop the screen"; + }; return false; } const QMetaObject* metaObject = pScene->metaObject(); @@ -607,7 +611,9 @@ bool ControllerScriptEngineLegacy::bindSceneToScreen( &ControllerScriptEngineLegacy::handleScreenFrame); m_renderingScreens.insert(screenIdentifier, pScreen); m_rootItems.insert(screenIdentifier, pScene); - // In case a rendering issue occurs, we need to shutdown the controller. + // In case a rendering issue occurs, we need to shutdown the controller + // since its only purpose is to render screens. This might not be the case + // in the future controller modules connect(pScreen.get(), &ControllerRenderingEngine::stopping, this, diff --git a/src/test/controllerrenderingengine_test.cpp b/src/test/controllerrenderingengine_test.cpp index faf912394454..864497861f15 100644 --- a/src/test/controllerrenderingengine_test.cpp +++ b/src/test/controllerrenderingengine_test.cpp @@ -6,6 +6,7 @@ #include #include +#include "controllers/controllerenginethreadcontrol.h" #include "controllers/legacycontrollermappingfilehandler.h" #include "helpers/log_test.h" #include "test/mixxxtest.h" @@ -28,7 +29,7 @@ class ControllerRenderingEngineTest : public MixxxTest { class MockRenderingEngine : public ControllerRenderingEngine { public: MockRenderingEngine(const LegacyControllerMapping::ScreenInfo& info) - : ControllerRenderingEngine(info, nullptr){}; + : ControllerRenderingEngine(info, new ControllerEngineThreadControl){}; }; TEST_F(ControllerRenderingEngineTest, createValidRendererWithSupportedTypes) { diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index a26d150d63c4..9fd4c5e34f42 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -15,6 +15,7 @@ #ifdef MIXXX_USE_QML #include +#include "controllers/controllerenginethreadcontrol.h" #include "controllers/rendering/controllerrenderingengine.h" #endif #include "controllers/softtakeover.h" @@ -662,7 +663,7 @@ TEST_F(ControllerScriptEngineLegacyTest, connectionExecutesWithCorrectThisObject class MockScreenRender : public ControllerRenderingEngine { public: MockScreenRender(const LegacyControllerMapping::ScreenInfo& info) - : ControllerRenderingEngine(info, nullptr){}; + : ControllerRenderingEngine(info, new ControllerEngineThreadControl){}; MOCK_METHOD(void, requestSendingFrameData, (Controller * controller, const QByteArray& frame), From 6197a076da751a6b80e99c1052c1c154c96e4fe9 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 25 May 2024 18:01:49 +0100 Subject: [PATCH 25/26] Use stack allocation for QML component --- .../legacy/controllerscriptenginelegacy.cpp | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 5a14075b881b..4dd7fb9f4b9f 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -876,10 +876,8 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( return nullptr; } - std::unique_ptr - qmlComponent = - std::make_unique( - std::dynamic_pointer_cast(m_pJSEngine).get()); + QQmlComponent qmlComponent = QQmlComponent( + std::dynamic_pointer_cast(m_pJSEngine).get()); QFile scene = QFile(qmlScript.file.absoluteFilePath()); if (!scene.exists()) { @@ -891,22 +889,22 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( QDir dir(m_resourcePath + "/qml/"); scene.open(QIODevice::ReadOnly); - qmlComponent->setData(scene.readAll(), + qmlComponent.setData(scene.readAll(), // Obfuscate the scene filename to make it appear in the QML folder. // This allows a smooth integration with QML components. QUrl::fromLocalFile( dir.absoluteFilePath(qmlScript.file.fileName()))); scene.close(); - while (qmlComponent->isLoading()) { + while (qmlComponent.isLoading()) { qCDebug(m_logger) << "Waiting for component " << qmlScript.file.absoluteFilePath() - << " to be ready: " << qmlComponent->progress(); + << " to be ready: " << qmlComponent.progress(); QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 500); } - if (qmlComponent->isError()) { - const QList errorList = qmlComponent->errors(); + if (qmlComponent.isError()) { + const QList errorList = qmlComponent.errors(); for (const QQmlError& error : errorList) { qCWarning(m_logger) << "Unable to load the QML scene:" << error.url() << "at line" << error.line() << ", error: " << error; @@ -915,15 +913,15 @@ std::shared_ptr ControllerScriptEngineLegacy::loadQMLFile( return nullptr; } - VERIFY_OR_DEBUG_ASSERT(qmlComponent->isReady()) { + VERIFY_OR_DEBUG_ASSERT(qmlComponent.isReady()) { qCWarning(m_logger) << "QMLComponent isn't ready although synchronous load was requested."; return nullptr; } - QObject* pRootObject = qmlComponent->createWithInitialProperties( + QObject* pRootObject = qmlComponent.createWithInitialProperties( QVariantMap{{"screenId", pScreen->info().identifier}}); - if (qmlComponent->isError()) { - const QList errorList = qmlComponent->errors(); + if (qmlComponent.isError()) { + const QList errorList = qmlComponent.errors(); for (const QQmlError& error : errorList) { qCWarning(m_logger) << error.url() << error.line() << error; } From d7ce6d856a1e80a910c2f057dcbe9ec577cb7d45 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Mon, 3 Jun 2024 11:26:37 +0100 Subject: [PATCH 26/26] Use QStringLiteral on ScopedTimer --- .../rendering/controllerrenderingengine.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/rendering/controllerrenderingengine.cpp b/src/controllers/rendering/controllerrenderingengine.cpp index afcce227168b..d2c19a0ac7aa 100644 --- a/src/controllers/rendering/controllerrenderingengine.cpp +++ b/src/controllers/rendering/controllerrenderingengine.cpp @@ -228,7 +228,7 @@ void ControllerRenderingEngine::finish() { } void ControllerRenderingEngine::renderFrame() { - ScopedTimer t(u"ControllerRenderingEngine::renderFrame"); + ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame")); if (!m_isValid) { DEBUG_ASSERT(!"Trying to render frame on an invalid engine"); return; @@ -240,7 +240,7 @@ void ControllerRenderingEngine::renderFrame() { "Couldn't make the GLContext current to the OffscreenSurface."); if (!m_fbo) { - ScopedTimer t(u"ControllerRenderingEngine::renderFrame::initFBO"); + ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame::initFBO")); VERIFY_OR_TERMINATE( QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(), "OpenGL doesn't support FBO"); @@ -276,7 +276,7 @@ void ControllerRenderingEngine::renderFrame() { m_renderControl->polishItems(); { - ScopedTimer t(u"ControllerRenderingEngine::renderFrame::sync"); + ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame::sync")); VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) { kLogger.warning() << "Couldn't sync the render control. Scene may be stuck"; }; @@ -309,7 +309,7 @@ void ControllerRenderingEngine::renderFrame() { kLogger.debug() << "Retrieved a previously unhandled GL error: " << glError; } { - ScopedTimer t(u"ControllerRenderingEngine::renderFrame::glReadPixels"); + ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame::glReadPixels")); m_context->functions()->glReadPixels(0, 0, m_screenInfo.size.width(), @@ -341,7 +341,7 @@ bool ControllerRenderingEngine::stop() { void ControllerRenderingEngine::send(Controller* controller, const QByteArray& frame) { DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY(); - ScopedTimer t(u"ControllerRenderingEngine::send"); + ScopedTimer t(QStringLiteral("ControllerRenderingEngine::send")); if (!frame.isEmpty()) { controller->sendBytes(frame); }