Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit e5e1d6e
Author: Antoine C <[email protected]>
Date:   Thu Oct 17 01:16:02 2024 +0100

    fixup! feat: inter controller communication

commit 0fe8c3b
Author: Antoine C <[email protected]>
Date:   Mon May 13 16:28:58 2024 +0100

    Throw JS error instead of logging and prompting an error message

commit a01c37c
Author: Antoine C <[email protected]>
Date:   Mon May 13 14:57:47 2024 +0100

    Add namespace isolation to prevent clash

commit c960ff9
Author: Antoine C <[email protected]>
Date:   Fri Mar 29 11:41:15 2024 +0000

    Update method naming to have better consistency with existing one

commit 14a1a6a
Author: Antoine C <[email protected]>
Date:   Sat Nov 4 20:15:11 2023 +0000

    feat: inter controller communication
  • Loading branch information
acolombier committed Oct 17, 2024
1 parent cdcbd4e commit 42f29c1
Show file tree
Hide file tree
Showing 21 changed files with 532 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/controllers/controlpickermenu.cpp
src/controllers/legacycontrollermappingfilehandler.cpp
src/controllers/legacycontrollermapping.cpp
src/controllers/controllershareddata.cpp
src/controllers/delegates/controldelegate.cpp
src/controllers/delegates/midibytedelegate.cpp
src/controllers/delegates/midichanneldelegate.cpp
Expand Down Expand Up @@ -2071,6 +2072,7 @@ add_executable(mixxx-test
src/test/controller_mapping_settings_test.cpp
src/test/controllers/controller_columnid_regression_test.cpp
src/test/controllerscriptenginelegacy_test.cpp
src/test/controllershareddata_test.cpp
src/test/controlobjecttest.cpp
src/test/controlobjectaliastest.cpp
src/test/controlobjectscripttest.cpp
Expand Down
37 changes: 35 additions & 2 deletions res/controllers/engine-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare interface ScriptConnection {

declare namespace engine {
type SettingValue = string | number | boolean;
type SharedDataValue = string | number | boolean | object | array | undefined;
/**
* Gets the value of a controller setting
* The value is either set in the preferences dialog,
Expand Down Expand Up @@ -83,6 +84,28 @@ declare namespace engine {
*/
function setParameter(group: string, name: string, newValue: number): void;

/**
* Gets the shared runtime data.
* @returns Runtime shared data value
*/
function getSharedData(): SharedDataValue;

/**
* Override the the shared runtime data with a new value.
*
* It is suggested to make additive changes (e.g add new attribute to existing object) in order to ease integration with other controller mapping
* @param newValue Runtime shared data value to be set
*/
function setSharedData(newValue: SharedDataValue): void;

/**
* Sets the control value specified with normalized range of 0..1
* @param group Group of the control e.g. "[Channel1]"
* @param name Name of the control e.g. "play_indicator"
* @param newValue Value to be set, normalized to a range of 0..1
*/
function setParameter(group: string, name: string, newValue: number): void;

/**
* Normalizes a specified value using the range of the given control,
* to the range of 0..1
Expand Down Expand Up @@ -123,6 +146,7 @@ declare namespace engine {
function getDefaultParameter(group: string, name: string): number;

type CoCallback = (value: number, group: string, name: string) => void
type RuntimeSharedDataCallback = (value: SharedDataValue) => void

/**
* Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes
Expand All @@ -132,9 +156,18 @@ declare namespace engine {
* @param group Group of the control e.g. "[Channel1]"
* @param name Name of the control e.g. "play_indicator"
* @param callback JS function, which will be called every time, the value of the connected control changes.
* @returns Returns script connection object on success, otherwise 'undefined''
* @returns Returns script connection object on success, otherwise 'undefined'
*/
function makeConnection(group: string, name: string, callback: CoCallback): ScriptConnection | undefined;

/**
* Register callback function to be triggered when the shared data is updated
*
* Note that local update will also trigger the callback. Make sure to make your callback safe against recursion.
* @param callback JS function, which will be called every time, the shared controller value changes.
* @returns Returns script connection object on success, otherwise 'undefined'
*/
function makeConnection(group: string, name: string, callback: CoCallback): ScriptConnection |undefined;
function makeSharedDataConnection(callback: RuntimeSharedDataCallback): ScriptConnection | undefined;

/**
* Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes
Expand Down
9 changes: 8 additions & 1 deletion src/controllers/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <QJSEngine>
#include <algorithm>

#include "controllers/controllershareddata.h"
#include "controllers/scripting/legacy/controllerscriptenginelegacy.h"
#include "moc_controller.cpp"
#include "util/cmdlineargs.h"
Expand Down Expand Up @@ -61,7 +62,8 @@ void Controller::stopEngine() {
emit engineStopped();
}

bool Controller::applyMapping(const QString& resourcePath) {
bool Controller::applyMapping(const QString& resourcePath,
std::shared_ptr<ControllerSharedData> runtimeData) {
qCInfo(m_logBase) << "Applying controller mapping...";

// Load the script code into the engine
Expand Down Expand Up @@ -89,6 +91,11 @@ bool Controller::applyMapping(const QString& resourcePath) {
#else
Q_UNUSED(resourcePath);
#endif

const auto& ns = pMapping->sharedDataNamespace();
if (!ns.isEmpty() && runtimeData != nullptr) {
m_pScriptEngineLegacy->setSharedData(runtimeData->namespaced(ns));
}
return m_pScriptEngineLegacy->initialize();
}

Expand Down
5 changes: 3 additions & 2 deletions src/controllers/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class ControllerJSProxy;
class ControllerScriptEngineLegacy;
class ControllerSharedData;

/// This is a base class representing a physical (or software) controller. It
/// must be inherited by a class that implements it on some API. Note that the
Expand Down Expand Up @@ -80,7 +81,8 @@ 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(const QString& resourcePath);
virtual bool applyMapping(const QString& resourcePath,
std::shared_ptr<ControllerSharedData> runtimeData);
virtual void slotBeforeEngineShutdown();

// Puts the controller in and out of learning mode.
Expand Down Expand Up @@ -145,7 +147,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
Expand Down
20 changes: 17 additions & 3 deletions src/controllers/controllermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include "controllers/controller.h"
#include "controllers/controllerlearningeventfilter.h"
#include "controllers/controllermappinginfoenumerator.h"
#include "controllers/controllershareddata.h"
#include "controllers/defs_controllers.h"
#include "controllers/scripting/legacy/controllerscriptenginelegacy.h"
#include "moc_controllermanager.cpp"
#include "util/cmdlineargs.h"
#include "util/compatibility/qmutex.h"
Expand Down Expand Up @@ -93,7 +95,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<ControllerSharedData>(this)) {
qRegisterMetaType<std::shared_ptr<LegacyControllerMapping>>(
"std::shared_ptr<LegacyControllerMapping>");

Expand Down Expand Up @@ -300,7 +303,12 @@ void ControllerManager::slotSetUpDevices() {
qWarning() << "There was a problem opening" << name;
continue;
}
pController->applyMapping(m_pConfig->getResourcePath());
VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) {
qWarning() << "Unable to acquire the controller engine. Has the "
"controller open successfully?";
continue;
}
pController->applyMapping(m_pConfig->getResourcePath(), m_pRuntimeData);
}

pollIfAnyControllersOpen();
Expand Down Expand Up @@ -392,7 +400,13 @@ void ControllerManager::openController(Controller* pController) {
// If successfully opened the device, apply the mapping and save the
// preference setting.
if (result == 0) {
pController->applyMapping(m_pConfig->getResourcePath());
VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) {
qWarning() << "Unable to acquire the controller engine. Has the "
"controller open successfully?";
return;
}

pController->applyMapping(m_pConfig->getResourcePath(), m_pRuntimeData);

// Update configuration to reflect controller is enabled.
m_pConfig->setValue(
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/controllermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Controller;
class ControllerLearningEventFilter;
class MappingInfoEnumerator;
class LegacyControllerMapping;
class ControllerSharedData;
class ControllerEnumerator;

/// Function to sort controllers by name
Expand Down Expand Up @@ -86,4 +87,5 @@ class ControllerManager : public QObject {
QSharedPointer<MappingInfoEnumerator> m_pMainThreadUserMappingEnumerator;
QSharedPointer<MappingInfoEnumerator> m_pMainThreadSystemMappingEnumerator;
bool m_skipPoll;
std::shared_ptr<ControllerSharedData> m_pRuntimeData;
};
22 changes: 22 additions & 0 deletions src/controllers/controllershareddata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <controllers/controllershareddata.h>

#include "moc_controllershareddata.cpp"

ControllerNamespacedSharedData* ControllerSharedData::namespaced(const QString& ns) {
return new ControllerNamespacedSharedData(this, ns);
}

ControllerNamespacedSharedData::ControllerNamespacedSharedData(
ControllerSharedData* parent, const QString& ns)
: QObject(parent),
m_namespace(ns) {
connect(parent,
&ControllerSharedData::updated,
this,
[this](const QString& ns, const QVariant& value) {
if (ns != m_namespace) {
return;
}
emit updated(value);
});
}
68 changes: 68 additions & 0 deletions src/controllers/controllershareddata.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <QVariant>

#include "util/assert.h"

class ControllerNamespacedSharedData;

/// ControllerSharedData is a wrapper that allows controllers script runtimes
/// to share arbitrary data via a the JavaScript interface. Controllers don't
/// access this object directly, and instead uses the
/// ControllerNamespacedSharedData wrapper to isolate a specific namespace and
/// prevent potential clash
class ControllerSharedData : public QObject {
Q_OBJECT
public:
ControllerSharedData(QObject* parent)
: QObject(parent),
m_value() {
}

QVariant get(const QString& ns) const {
return m_value.value(ns);
}

/// @brief Create a a namespace wrapper that can be used by a controller.
/// The caller is owning the wrapper
/// @param ns The namespace to restrict access to
/// @return The pointer to the newly allocated wrapper
ControllerNamespacedSharedData* namespaced(const QString& ns);

public slots:
void set(const QString& ns, const QVariant& value) {
m_value[ns] = value;
emit updated(ns, m_value[ns]);
}

signals:
void updated(const QString& ns, const QVariant& value);

private:
QHash<QString, QVariant> m_value;
};

/// ControllerNamespacedSharedData is a wrapper that restrict access to a given
/// namespace. It doesn't hold any data and can safely be deleted at all time,
/// but only provide the namespace abstraction for controller to interact with
/// via a the JavaScript interface
class ControllerNamespacedSharedData : public QObject {
Q_OBJECT
public:
ControllerNamespacedSharedData(ControllerSharedData* parent, const QString& ns);

QVariant get() const {
return static_cast<ControllerSharedData*>(parent())->get(m_namespace);
}

public slots:
void set(const QVariant& value) {
static_cast<ControllerSharedData*>(parent())->set(m_namespace, value);
}

signals:
void updated(const QVariant& value);

private:
QString m_namespace;
};
11 changes: 11 additions & 0 deletions src/controllers/legacycontrollermapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class LegacyControllerMapping {
: m_productMatches(other.m_productMatches),
m_bDirty(other.m_bDirty),
m_deviceId(other.m_deviceId),
m_sharedDataNamespace(other.m_sharedDataNamespace),
m_filePath(other.m_filePath),
m_name(other.m_name),
m_author(other.m_author),
Expand Down Expand Up @@ -243,6 +244,15 @@ class LegacyControllerMapping {
return m_deviceId;
}

void setSharedDataNamespace(QString sharedDataNamespace) {
m_sharedDataNamespace = std::move(sharedDataNamespace);
setDirty(true);
}

const QString& sharedDataNamespace() const {
return m_sharedDataNamespace;
}

void setFilePath(const QString& filePath) {
m_filePath = filePath;
setDirty(true);
Expand Down Expand Up @@ -359,6 +369,7 @@ class LegacyControllerMapping {
bool m_bDirty;

QString m_deviceId;
QString m_sharedDataNamespace;
QString m_filePath;
QString m_name;
QString m_author;
Expand Down
6 changes: 6 additions & 0 deletions src/controllers/legacycontrollermappingfilehandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping(

QString deviceId = controller.attribute("id", "");
mapping->setDeviceId(deviceId);
// Empty namespace is forbidden. If a controller wants to use shared data,
// they must specify an non-empty string.
QString sharedDataNamespace = controller.attribute("namespace", "");
if (!sharedDataNamespace.isEmpty()) {
mapping->setSharedDataNamespace(sharedDataNamespace);
}

// See TODO in LegacyControllerMapping::DeviceDirection - `direction` should
// only be used as a workaround till the bulk integration gets refactored
Expand Down
5 changes: 3 additions & 2 deletions src/controllers/midi/midicontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ bool MidiController::matchMapping(const MappingInfo& mapping) {
return false;
}

bool MidiController::applyMapping(const QString& resourcePath) {
bool MidiController::applyMapping(const QString& resourcePath,
std::shared_ptr<ControllerSharedData> runtimeData) {
// Handles the engine
bool result = Controller::applyMapping(resourcePath);
bool result = Controller::applyMapping(resourcePath, std::move(runtimeData));

// Only execute this code if this is an output device
if (isOutputDevice()) {
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/midi/midicontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "controllers/softtakeover.h"

class MidiOutputHandler;
class ControllerSharedData;

class MidiInputHandleJSProxy final : public QObject {
Q_OBJECT
Expand Down Expand Up @@ -80,7 +81,7 @@ class MidiController : public Controller {
void slotBeforeEngineShutdown() override;

private slots:
bool applyMapping(const QString& resourcePath) override;
bool applyMapping(const QString& resourcePath, std::shared_ptr<ControllerSharedData>) override;

void learnTemporaryInputMappings(const MidiInputMappings& mappings);
void clearTemporaryInputMappings();
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/scripting/controllerscriptenginebase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,7 @@ void ControllerScriptEngineBase::errorDialogButton(
void ControllerScriptEngineBase::throwJSError(const QString& message) {
m_pJSEngine->throwError(message);
}

void ControllerScriptEngineBase::setSharedData(ControllerNamespacedSharedData* runtimeData) {
m_runtimeData = std::unique_ptr<ControllerNamespacedSharedData>(runtimeData);
}
Loading

0 comments on commit 42f29c1

Please sign in to comment.