Skip to content

Commit 3a06e04

Browse files
committed
shared data API
1 parent 8b726bc commit 3a06e04

27 files changed

+566
-25
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,7 @@ add_library(
11331133
src/controllers/controlpickermenu.cpp
11341134
src/controllers/legacycontrollermappingfilehandler.cpp
11351135
src/controllers/legacycontrollermapping.cpp
1136+
src/controllers/controllershareddata.cpp
11361137
src/controllers/delegates/controldelegate.cpp
11371138
src/controllers/delegates/midibytedelegate.cpp
11381139
src/controllers/delegates/midichanneldelegate.cpp
@@ -2598,6 +2599,7 @@ if(BUILD_TESTING)
25982599
src/test/controller_mapping_settings_test.cpp
25992600
src/test/controllers/controller_columnid_regression_test.cpp
26002601
src/test/controllerscriptenginelegacy_test.cpp
2602+
src/test/controllershareddata_test.cpp
26012603
src/test/controlobjecttest.cpp
26022604
src/test/controlobjectaliastest.cpp
26032605
src/test/controlobjectscripttest.cpp

res/controllers/engine-api.d.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ declare namespace engine {
125125
function getPlayer(group: string): Player | undefined
126126

127127
type SettingValue = string | number | boolean;
128+
type SharedDataValue = string | number | boolean | object | array | undefined;
128129
/**
129130
* Gets the value of a controller setting
130131
* The value is either set in the preferences dialog,
@@ -172,6 +173,28 @@ declare namespace engine {
172173
*/
173174
function setParameter(group: string, name: string, newValue: number): void;
174175

176+
/**
177+
* Gets the shared runtime data.
178+
* @returns Runtime shared data value
179+
*/
180+
function getSharedData(): SharedDataValue;
181+
182+
/**
183+
* Override the the shared runtime data with a new value.
184+
*
185+
* It is suggested to make additive changes (e.g add new attribute to existing object) in order to ease integration with other controller mapping
186+
* @param newValue Runtime shared data value to be set
187+
*/
188+
function setSharedData(newValue: SharedDataValue): void;
189+
190+
/**
191+
* Sets the control value specified with normalized range of 0..1
192+
* @param group Group of the control e.g. "[Channel1]"
193+
* @param name Name of the control e.g. "play_indicator"
194+
* @param newValue Value to be set, normalized to a range of 0..1
195+
*/
196+
function setParameter(group: string, name: string, newValue: number): void;
197+
175198
/**
176199
* Normalizes a specified value using the range of the given control,
177200
* to the range of 0..1
@@ -212,6 +235,7 @@ declare namespace engine {
212235
function getDefaultParameter(group: string, name: string): number;
213236

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

216240
/**
217241
* Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes
@@ -221,10 +245,19 @@ declare namespace engine {
221245
* @param group Group of the control e.g. "[Channel1]"
222246
* @param name Name of the control e.g. "play_indicator"
223247
* @param callback JS function, which will be called every time, the value of the connected control changes.
224-
* @returns Returns script connection object on success, otherwise 'undefined''
248+
* @returns Returns script connection object on success, otherwise 'undefined'
225249
*/
226250
function makeConnection(group: string, name: string, callback: CoCallback): ScriptConnection | undefined;
227251

252+
/**
253+
* Register callback function to be triggered when the shared data is updated
254+
*
255+
* Note that local update will also trigger the callback. Make sure to make your callback safe against recursion.
256+
* @param callback JS function, which will be called every time, the shared controller value changes.
257+
* @returns Returns script connection object on success, otherwise 'undefined'
258+
*/
259+
function makeSharedDataConnection(callback: RuntimeSharedDataCallback): ScriptConnection | undefined;
260+
228261
/**
229262
* Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes
230263
*

src/controllers/bulk/bulkcontroller.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "controllers/bulk/bulkcontroller.h"
22

33
#include <libusb.h>
4+
#include <qstringliteral.h>
45

56
#include <algorithm>
67

@@ -116,6 +117,13 @@ QList<std::shared_ptr<AbstractLegacyControllerSetting>> BulkController::getMappi
116117
return m_pMapping->getSettings();
117118
}
118119

120+
const QString& BulkController::getSharedDataNamespace() {
121+
if (!m_pMapping) {
122+
return QStringLiteral("");
123+
}
124+
return m_pMapping->sharedDataNamespace();
125+
}
126+
119127
#ifdef MIXXX_USE_QML
120128
QList<LegacyControllerMapping::QMLModuleInfo> BulkController::getMappingModules() {
121129
if (!m_pMapping) {
@@ -164,7 +172,8 @@ bool BulkController::matchProductInfo(const ProductInfo& product) {
164172
return true;
165173
}
166174

167-
int BulkController::open(const QString& resourcePath) {
175+
int BulkController::open(const QString& resourcePath,
176+
std::shared_ptr<ControllerSharedData> runtimeData) {
168177
if (isOpen()) {
169178
qCWarning(m_logBase) << "USB Bulk device" << getName() << "already open";
170179
return -1;
@@ -230,7 +239,7 @@ int BulkController::open(const QString& resourcePath) {
230239
// audio directly, like when scratching
231240
m_pReader->start(QThread::HighPriority);
232241
}
233-
applyMapping(resourcePath);
242+
applyMapping(resourcePath, runtimeData);
234243
setOpen(true);
235244
return 0;
236245
}

src/controllers/bulk/bulkcontroller.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class BulkController : public Controller {
4646

4747
QList<LegacyControllerMapping::ScriptFileInfo> getMappingScriptFiles() override;
4848
QList<std::shared_ptr<AbstractLegacyControllerSetting>> getMappingSettings() override;
49+
const QString& getSharedDataNamespace() override;
4950
#ifdef MIXXX_USE_QML
5051
QList<LegacyControllerMapping::QMLModuleInfo> getMappingModules() override;
5152
QList<LegacyControllerMapping::ScreenInfo> getMappingInfoScreens() override;
@@ -89,7 +90,8 @@ class BulkController : public Controller {
8990
void send(const QList<int>& data, unsigned int length) override;
9091

9192
private:
92-
int open(const QString& resourcePath) override;
93+
int open(const QString& resourcePath,
94+
std::shared_ptr<ControllerSharedData> runtimeData) override;
9395
int close() override;
9496

9597
// For devices which only support a single report, reportID must be set to

src/controllers/controller.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <QJSEngine>
44
#include <algorithm>
55

6+
#include "controllers/controllershareddata.h"
67
#include "controllers/scripting/legacy/controllerscriptenginelegacy.h"
78
#include "moc_controller.cpp"
89
#include "util/cmdlineargs.h"
@@ -16,8 +17,7 @@ QString loggingCategoryPrefix(const QString& deviceName) {
1617
} // namespace
1718

1819
Controller::Controller(const QString& deviceName)
19-
: m_sDeviceName(deviceName),
20-
m_logBase(loggingCategoryPrefix(deviceName)),
20+
: m_sDeviceName(deviceName),m_logBase(loggingCategoryPrefix(deviceName)),
2121
m_logInput(loggingCategoryPrefix(deviceName) + QStringLiteral(".input")),
2222
m_logOutput(loggingCategoryPrefix(deviceName) + QStringLiteral(".output")),
2323
m_pScriptEngineLegacy(nullptr),
@@ -79,7 +79,8 @@ void Controller::stopEngine() {
7979
emit engineStopped();
8080
}
8181

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

8586
// Load the script code into the engine
@@ -106,6 +107,10 @@ bool Controller::applyMapping(const QString& resourcePath) {
106107
#else
107108
Q_UNUSED(resourcePath);
108109
#endif
110+
111+
if (runtimeData != nullptr) {
112+
m_pScriptEngineLegacy->setSharedData(runtimeData->namespaced(getSharedDataNamespace()));
113+
}
109114
return m_pScriptEngineLegacy->initialize();
110115
}
111116

src/controllers/controller.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class ControllerJSProxy;
1111
class ControllerScriptEngineLegacy;
12+
class ControllerSharedData;
1213

1314
enum class PhysicalTransportProtocol {
1415
UNKNOWN,
@@ -53,6 +54,7 @@ class Controller : public QObject {
5354

5455
virtual QList<LegacyControllerMapping::ScriptFileInfo> getMappingScriptFiles() = 0;
5556
virtual QList<std::shared_ptr<AbstractLegacyControllerSetting>> getMappingSettings() = 0;
57+
virtual const QString& getSharedDataNamespace() = 0;
5658
#ifdef MIXXX_USE_QML
5759
virtual QList<LegacyControllerMapping::QMLModuleInfo> getMappingModules() = 0;
5860
virtual QList<LegacyControllerMapping::ScreenInfo> getMappingInfoScreens() = 0;
@@ -119,7 +121,8 @@ class Controller : public QObject {
119121
void stopLearning();
120122

121123
protected:
122-
virtual bool applyMapping(const QString& resourcePath);
124+
virtual bool applyMapping(const QString& resourcePath,
125+
std::shared_ptr<ControllerSharedData> runtimeData);
123126

124127
template<typename SpecificMappingType>
125128
requires(std::is_final_v<SpecificMappingType> == true)
@@ -177,7 +180,8 @@ class Controller : public QObject {
177180
virtual bool sendBytes(const QByteArray& data) = 0;
178181

179182
private: // but used by ControllerManager
180-
virtual int open(const QString& resourcePath) = 0;
183+
virtual int open(const QString& resourcePath,
184+
std::shared_ptr<ControllerSharedData> runtimeData) = 0;
181185
virtual int close() = 0;
182186
// Requests that the device poll if it is a polling device. Returns true
183187
// if events were handled.

src/controllers/controllermanager.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#include "controllers/controller.h"
77
#include "controllers/controllerlearningeventfilter.h"
88
#include "controllers/controllermappinginfoenumerator.h"
9+
#include "controllers/controllershareddata.h"
910
#include "controllers/defs_controllers.h"
11+
#include "controllers/scripting/legacy/controllerscriptenginelegacy.h"
1012
#include "moc_controllermanager.cpp"
1113
#include "util/cmdlineargs.h"
1214
#include "util/compatibility/qmutex.h"
@@ -95,7 +97,8 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
9597
// its own event loop.
9698
m_pControllerLearningEventFilter(new ControllerLearningEventFilter()),
9799
m_pollTimer(this),
98-
m_skipPoll(false) {
100+
m_skipPoll(false),
101+
m_pRuntimeData(std::make_shared<ControllerSharedData>(this)) {
99102
qRegisterMetaType<std::shared_ptr<LegacyControllerMapping>>(
100103
"std::shared_ptr<LegacyControllerMapping>");
101104

@@ -297,7 +300,7 @@ void ControllerManager::slotSetUpDevices() {
297300

298301
qDebug() << "Opening controller:" << name;
299302

300-
int value = pController->open(m_pConfig->getResourcePath());
303+
int value = pController->open(m_pConfig->getResourcePath(), m_pRuntimeData);
301304
if (value != 0) {
302305
qWarning() << "There was a problem opening" << name;
303306
continue;
@@ -388,7 +391,7 @@ void ControllerManager::openController(Controller* pController) {
388391
if (pController->isOpen()) {
389392
pController->close();
390393
}
391-
int result = pController->open(m_pConfig->getResourcePath());
394+
int result = pController->open(m_pConfig->getResourcePath(), m_pRuntimeData);
392395
pollIfAnyControllersOpen();
393396

394397
// If successfully opened the device, apply the mapping and save the

src/controllers/controllermanager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Controller;
1414
class ControllerLearningEventFilter;
1515
class MappingInfoEnumerator;
1616
class LegacyControllerMapping;
17+
class ControllerSharedData;
1718
class ControllerEnumerator;
1819

1920
/// Function to sort controllers by name
@@ -86,4 +87,5 @@ class ControllerManager : public QObject {
8687
QSharedPointer<MappingInfoEnumerator> m_pMainThreadUserMappingEnumerator;
8788
QSharedPointer<MappingInfoEnumerator> m_pMainThreadSystemMappingEnumerator;
8889
bool m_skipPoll;
90+
std::shared_ptr<ControllerSharedData> m_pRuntimeData;
8991
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <controllers/controllershareddata.h>
2+
3+
#include "moc_controllershareddata.cpp"
4+
5+
ControllerNamespacedSharedData* ControllerSharedData::namespaced(const QString& ns) {
6+
return new ControllerNamespacedSharedData(this, ns);
7+
}
8+
9+
ControllerNamespacedSharedData::ControllerNamespacedSharedData(
10+
ControllerSharedData* parent, const QString& ns)
11+
: QObject(parent),
12+
m_namespace(ns) {
13+
connect(parent,
14+
&ControllerSharedData::updated,
15+
this,
16+
[this](const QString& ns, const QVariant& value) {
17+
if (ns != m_namespace) {
18+
return;
19+
}
20+
emit updated(value);
21+
});
22+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma once
2+
3+
#include <QVariant>
4+
5+
#include "util/assert.h"
6+
7+
class ControllerNamespacedSharedData;
8+
9+
/// ControllerSharedData is a wrapper that allows controllers script runtimes
10+
/// to share arbitrary data via a the JavaScript interface. Controllers don't
11+
/// access this object directly, and instead uses the
12+
/// ControllerNamespacedSharedData wrapper to isolate a specific namespace and
13+
/// prevent potential clash
14+
class ControllerSharedData : public QObject {
15+
Q_OBJECT
16+
public:
17+
ControllerSharedData(QObject* parent)
18+
: QObject(parent),
19+
m_value() {
20+
}
21+
22+
QVariant get(const QString& ns) const {
23+
return m_value.value(ns);
24+
}
25+
26+
/// @brief Create a a namespace wrapper that can be used by a controller.
27+
/// The caller is owning the wrapper
28+
/// @param ns The namespace to restrict access to
29+
/// @return The pointer to the newly allocated wrapper
30+
ControllerNamespacedSharedData* namespaced(const QString& ns);
31+
32+
public slots:
33+
void set(const QString& ns, const QVariant& value) {
34+
m_value[ns] = value;
35+
emit updated(ns, m_value[ns]);
36+
}
37+
38+
signals:
39+
void updated(const QString& ns, const QVariant& value);
40+
41+
private:
42+
QHash<QString, QVariant> m_value;
43+
};
44+
45+
/// ControllerNamespacedSharedData is a wrapper that restrict access to a given
46+
/// namespace. It doesn't hold any data and can safely be deleted at all time,
47+
/// but only provide the namespace abstraction for controller to interact with
48+
/// via a the JavaScript interface
49+
class ControllerNamespacedSharedData : public QObject {
50+
Q_OBJECT
51+
public:
52+
ControllerNamespacedSharedData(ControllerSharedData* parent, const QString& ns);
53+
54+
QVariant get() const {
55+
return static_cast<ControllerSharedData*>(parent())->get(m_namespace);
56+
}
57+
58+
public slots:
59+
void set(const QVariant& value) {
60+
static_cast<ControllerSharedData*>(parent())->set(m_namespace, value);
61+
}
62+
63+
signals:
64+
void updated(const QVariant& value);
65+
66+
private:
67+
QString m_namespace;
68+
};

0 commit comments

Comments
 (0)