From 7dd2b4c751b7f7a84c18e2ed60aadab1a6ddffd2 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 4 Nov 2023 20:15:11 +0000 Subject: [PATCH] feat: inter controller communication --- CMakeLists.txt | 1 + src/controllers/controller.h | 1 - src/controllers/controllermanager.cpp | 18 +++- src/controllers/controllermanager.h | 2 + src/controllers/controllerruntimedata.cpp | 3 + src/controllers/controllerruntimedata.h | 34 +++++++ .../scripting/controllerscriptenginebase.h | 11 +++ .../controllerscriptinterfacelegacy.cpp | 90 +++++++++++++++++++ .../legacy/controllerscriptinterfacelegacy.h | 10 +++ 9 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/controllers/controllerruntimedata.cpp create mode 100644 src/controllers/controllerruntimedata.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a5f370561ffc..2436c0c6ae0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -654,6 +654,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/controllermappingtablemodel.cpp src/controllers/controlleroutputmappingtablemodel.cpp src/controllers/controlpickermenu.cpp + src/controllers/controllerruntimedata.cpp src/controllers/delegates/controldelegate.cpp src/controllers/delegates/midibytedelegate.cpp src/controllers/delegates/midichanneldelegate.cpp diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 562ed3777c6b..5821f9de9181 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -144,7 +144,6 @@ class Controller : public QObject { virtual void sendBytes(const QByteArray& data) = 0; private: // but used by ControllerManager - virtual int open() = 0; virtual int close() = 0; // Requests that the device poll if it is a polling device. Returns true diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 971c9dc634b9..02e4e993295d 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -6,8 +6,10 @@ #include "controllers/controller.h" #include "controllers/controllerlearningeventfilter.h" #include "controllers/controllermappinginfoenumerator.h" +#include "controllers/controllerruntimedata.h" #include "controllers/defs_controllers.h" #include "controllers/midi/portmidienumerator.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "moc_controllermanager.cpp" #include "util/cmdlineargs.h" #include "util/compatibility/qmutex.h" @@ -89,7 +91,8 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig) // its own event loop. m_pControllerLearningEventFilter(new ControllerLearningEventFilter()), m_pollTimer(this), - m_skipPoll(false) { + m_skipPoll(false), + m_pRuntimeData(std::make_shared(this)) { qRegisterMetaType>( "std::shared_ptr"); @@ -293,6 +296,12 @@ void ControllerManager::slotSetUpDevices() { qWarning() << "There was a problem opening" << name; continue; } + VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) { + qWarning() << "Unable to acquire the controller engine. Has the " + "controller open successfully?"; + continue; + } + pController->getScriptEngine()->setRuntimeData(m_pRuntimeData); pController->applyMapping(); } @@ -385,6 +394,13 @@ void ControllerManager::openController(Controller* pController) { // If successfully opened the device, apply the mapping and save the // preference setting. if (result == 0) { + VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) { + qWarning() << "Unable to acquire the controller engine. Has the " + "controller open successfully?"; + return; + } + pController->getScriptEngine()->setRuntimeData(m_pRuntimeData); + pController->applyMapping(); // Update configuration to reflect controller is enabled. diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index a41b015c020a..06fa135a92c5 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -13,6 +13,7 @@ class Controller; class ControllerLearningEventFilter; class MappingInfoEnumerator; class LegacyControllerMapping; +class ControllerRuntimeData; class ControllerEnumerator; /// Function to sort controllers by name @@ -85,4 +86,5 @@ class ControllerManager : public QObject { QSharedPointer m_pMainThreadUserMappingEnumerator; QSharedPointer m_pMainThreadSystemMappingEnumerator; bool m_skipPoll; + std::shared_ptr m_pRuntimeData; }; diff --git a/src/controllers/controllerruntimedata.cpp b/src/controllers/controllerruntimedata.cpp new file mode 100644 index 000000000000..83bc96ad66ac --- /dev/null +++ b/src/controllers/controllerruntimedata.cpp @@ -0,0 +1,3 @@ +#include + +#include "moc_controllerruntimedata.cpp" diff --git a/src/controllers/controllerruntimedata.h b/src/controllers/controllerruntimedata.h new file mode 100644 index 000000000000..db3f72e4819a --- /dev/null +++ b/src/controllers/controllerruntimedata.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "util/assert.h" + +/// ControllerRuntimeData is a wrapper that allows controllers script runtimes +/// to share arbitrary data via a the JavaScript interface. It doesn't enforce +/// any type consistency and it is the script responsibility to use this data in +/// a considerate, non-destructive way (append to lists, extend to objects, +/// ...), as well as expecting that others won't do so. +class ControllerRuntimeData : public QObject { + Q_OBJECT + public: + ControllerRuntimeData(QObject* parent) + : QObject(parent), m_value() { + } + + const QVariant& get() const { + return m_value; + } + + public slots: + void set(const QVariant& value) { + m_value = value; + emit updated(m_value); + } + + signals: + void updated(const QVariant& value); + + private: + QVariant m_value; +}; diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 493826dac3d0..2a922d4c5cab 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -8,6 +8,7 @@ class Controller; class QJSEngine; +class ControllerRuntimeData; #ifdef MIXXX_USE_QML class TrackCollectionManager; #endif @@ -47,12 +48,22 @@ class ControllerScriptEngineBase : public QObject { return m_bTesting; } + void setRuntimeData(std::shared_ptr runtimeData) { + m_pRuntimeData = std::move(runtimeData); + } + + std::shared_ptr getRuntimeData() const { + return m_pRuntimeData; + } + #ifdef MIXXX_USE_QML static void registerTrackCollectionManager( std::shared_ptr pTrackCollectionManager); #endif protected: + std::shared_ptr m_pRuntimeData; + virtual void shutdown(); void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 33a60e3cae05..d1d3c16038d7 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -2,6 +2,7 @@ #include "control/controlobject.h" #include "control/controlobjectscript.h" +#include "controllers/controllerruntimedata.h" #include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/scripting/legacy/scriptconnectionjsproxy.h" #include "mixer/playermanager.h" @@ -47,6 +48,11 @@ ControllerScriptInterfaceLegacy::ControllerScriptInterfaceLegacy( m_spinbackActive[i] = false; m_softStartActive[i] = false; } + + connect(m_pEngine->getRuntimeData().get(), + &ControllerRuntimeData::updated, + this, + &ControllerScriptInterfaceLegacy::onRuntimeDataUpdated); } ControllerScriptInterfaceLegacy::~ControllerScriptInterfaceLegacy() { @@ -141,6 +147,88 @@ void ControllerScriptInterfaceLegacy::setValue( } } +QJSValue ControllerScriptInterfaceLegacy::getRuntimeData() { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return QJSValue(); + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return QJSValue(); + } + + return pJsEngine->toScriptValue(pRuntimeData->get()); +} + +void ControllerScriptInterfaceLegacy::setRuntimeData(const QJSValue& value) { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return; + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return; + } + + pRuntimeData->set(value.toVariant()); + qDebug() << "runtime data set successfully"; +} + +QJSValue ControllerScriptInterfaceLegacy::onRuntimeDataUpdate(const QJSValue& callback) { + if (!callback.isCallable()) { + m_pScriptEngineLegacy->throwJSError( + "Tried to connect runtime data update handler" + " to an invalid callback. Make sure that your code contains no " + "syntax errors."); + return QJSValue(); + } + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { + return QJSValue(); + } + auto pRuntimeData = m_pScriptEngineLegacy->getRuntimeData(); + + if (!pRuntimeData) { + qWarning() << "Unable to fetch the runtime data"; + return QJSValue(); + } + + ScriptConnection connection; + connection.engineJSProxy = this; + connection.controllerEngine = m_pScriptEngineLegacy; + connection.callback = callback; + connection.id = QUuid::createUuid(); + + m_runtimeDataConnections.append(connection); + + return pJsEngine->newQObject( + new ScriptConnectionJSProxy(m_runtimeDataConnections.last())); +} + +void ControllerScriptInterfaceLegacy::onRuntimeDataUpdated(const QVariant& value) { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + const auto args = QJSValueList{ + pJsEngine->toScriptValue(value), + }; + + for (const auto& connection : m_runtimeDataConnections) { + QJSValue func = connection.callback; // copy function because QJSValue::call is not const + QJSValue result = func.call(args); + if (result.isError()) { + if (m_pScriptEngineLegacy != nullptr) { + m_pScriptEngineLegacy->showScriptExceptionDialog(result); + } + qWarning() << "ControllerEngine: Invocation of connection " << connection.id.toString() + << "connected to runtime data failed:" + << result.toString(); + } + } +} + double ControllerScriptInterfaceLegacy::getParameter(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { @@ -283,6 +371,7 @@ QJSValue ControllerScriptInterfaceLegacy::makeConnectionInternal( bool ControllerScriptInterfaceLegacy::removeScriptConnection( const ScriptConnection& connection) { + // TODO handle runtimeData connection ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); @@ -299,6 +388,7 @@ void ControllerScriptInterfaceLegacy::triggerScriptConnection( return; } + // TODO handle runtimeData connection ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); if (coScript == nullptr) { diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h index be8589f4d1a6..52c5e597ff84 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h @@ -24,6 +24,11 @@ class ControllerScriptInterfaceLegacy : public QObject { Q_INVOKABLE double getValue(const QString& group, const QString& name); Q_INVOKABLE void setValue(const QString& group, const QString& name, double newValue); + + Q_INVOKABLE QJSValue getRuntimeData(); + Q_INVOKABLE void setRuntimeData(const QJSValue& value); + Q_INVOKABLE QJSValue onRuntimeDataUpdate(const QJSValue& callback); + Q_INVOKABLE double getParameter(const QString& group, const QString& name); Q_INVOKABLE void setParameter(const QString& group, const QString& name, double newValue); Q_INVOKABLE double getParameterForValue( @@ -76,6 +81,9 @@ class ControllerScriptInterfaceLegacy : public QObject { /// Handler for timers that scripts set. virtual void timerEvent(QTimerEvent* event); + private slots: + void onRuntimeDataUpdated(const QVariant& value); + private: QJSValue makeConnectionInternal(const QString& group, const QString& name, @@ -108,4 +116,6 @@ class ControllerScriptInterfaceLegacy : public QObject { ControllerScriptEngineLegacy* m_pScriptEngineLegacy; const RuntimeLoggingCategory m_logger; + + QList m_runtimeDataConnections; };