Skip to content

Commit 8051d37

Browse files
committed
shared data API
1 parent 139ae83 commit 8051d37

21 files changed

+531
-14
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ add_library(
10161016
src/controllers/controlpickermenu.cpp
10171017
src/controllers/legacycontrollermappingfilehandler.cpp
10181018
src/controllers/legacycontrollermapping.cpp
1019+
src/controllers/controllershareddata.cpp
10191020
src/controllers/delegates/controldelegate.cpp
10201021
src/controllers/delegates/midibytedelegate.cpp
10211022
src/controllers/delegates/midichanneldelegate.cpp
@@ -2470,6 +2471,7 @@ if(BUILD_TESTING)
24702471
src/test/controller_mapping_settings_test.cpp
24712472
src/test/controllers/controller_columnid_regression_test.cpp
24722473
src/test/controllerscriptenginelegacy_test.cpp
2474+
src/test/controllershareddata_test.cpp
24732475
src/test/controlobjecttest.cpp
24742476
src/test/controlobjectaliastest.cpp
24752477
src/test/controlobjectscripttest.cpp

res/controllers/engine-api.d.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ declare interface ScriptConnection {
3636

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

87+
/**
88+
* Gets the shared runtime data.
89+
* @returns Runtime shared data value
90+
*/
91+
function getSharedData(): SharedDataValue;
92+
93+
/**
94+
* Override the the shared runtime data with a new value.
95+
*
96+
* It is suggested to make additive changes (e.g add new attribute to existing object) in order to ease integration with other controller mapping
97+
* @param newValue Runtime shared data value to be set
98+
*/
99+
function setSharedData(newValue: SharedDataValue): void;
100+
101+
/**
102+
* Sets the control value specified with normalized range of 0..1
103+
* @param group Group of the control e.g. "[Channel1]"
104+
* @param name Name of the control e.g. "play_indicator"
105+
* @param newValue Value to be set, normalized to a range of 0..1
106+
*/
107+
function setParameter(group: string, name: string, newValue: number): void;
108+
86109
/**
87110
* Normalizes a specified value using the range of the given control,
88111
* to the range of 0..1
@@ -123,6 +146,7 @@ declare namespace engine {
123146
function getDefaultParameter(group: string, name: string): number;
124147

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

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

163+
/**
164+
* Register callback function to be triggered when the shared data is updated
165+
*
166+
* Note that local update will also trigger the callback. Make sure to make your callback safe against recursion.
167+
* @param callback JS function, which will be called every time, the shared controller value changes.
168+
* @returns Returns script connection object on success, otherwise 'undefined'
169+
*/
170+
function makeSharedDataConnection(callback: RuntimeSharedDataCallback): ScriptConnection | undefined;
171+
139172
/**
140173
* Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes
141174
*

src/controllers/controller.cpp

+8-1
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"
@@ -79,7 +80,8 @@ void Controller::stopEngine() {
7980
emit engineStopped();
8081
}
8182

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

8587
// Load the script code into the engine
@@ -107,6 +109,11 @@ bool Controller::applyMapping(const QString& resourcePath) {
107109
#else
108110
Q_UNUSED(resourcePath);
109111
#endif
112+
113+
const auto& ns = pMapping->sharedDataNamespace();
114+
if (!ns.isEmpty() && runtimeData != nullptr) {
115+
m_pScriptEngineLegacy->setSharedData(runtimeData->namespaced(ns));
116+
}
110117
return m_pScriptEngineLegacy->initialize();
111118
}
112119

src/controllers/controller.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
class ControllerJSProxy;
1010
class ControllerScriptEngineLegacy;
11+
class ControllerSharedData;
1112

1213
enum class PhysicalTransportProtocol {
1314
UNKNOWN,
@@ -103,7 +104,8 @@ class Controller : public QObject {
103104
// this if they have an alternate way of handling such data.)
104105
virtual void receive(const QByteArray& data, mixxx::Duration timestamp);
105106

106-
virtual bool applyMapping(const QString& resourcePath);
107+
virtual bool applyMapping(const QString& resourcePath,
108+
std::shared_ptr<ControllerSharedData> runtimeData);
107109
virtual void slotBeforeEngineShutdown();
108110

109111
// Puts the controller in and out of learning mode.
@@ -166,7 +168,6 @@ class Controller : public QObject {
166168
virtual bool sendBytes(const QByteArray& data) = 0;
167169

168170
private: // but used by ControllerManager
169-
170171
virtual int open() = 0;
171172
virtual int close() = 0;
172173
// Requests that the device poll if it is a polling device. Returns true

src/controllers/controllermanager.cpp

+17-3
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"
@@ -94,7 +96,8 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
9496
// its own event loop.
9597
m_pControllerLearningEventFilter(new ControllerLearningEventFilter()),
9698
m_pollTimer(this),
97-
m_skipPoll(false) {
99+
m_skipPoll(false),
100+
m_pRuntimeData(std::make_shared<ControllerSharedData>(this)) {
98101
qRegisterMetaType<std::shared_ptr<LegacyControllerMapping>>(
99102
"std::shared_ptr<LegacyControllerMapping>");
100103

@@ -301,7 +304,12 @@ void ControllerManager::slotSetUpDevices() {
301304
qWarning() << "There was a problem opening" << name;
302305
continue;
303306
}
304-
pController->applyMapping(m_pConfig->getResourcePath());
307+
VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) {
308+
qWarning() << "Unable to acquire the controller engine. Has the "
309+
"controller open successfully?";
310+
continue;
311+
}
312+
pController->applyMapping(m_pConfig->getResourcePath(), m_pRuntimeData);
305313
}
306314

307315
pollIfAnyControllersOpen();
@@ -393,7 +401,13 @@ void ControllerManager::openController(Controller* pController) {
393401
// If successfully opened the device, apply the mapping and save the
394402
// preference setting.
395403
if (result == 0) {
396-
pController->applyMapping(m_pConfig->getResourcePath());
404+
VERIFY_OR_DEBUG_ASSERT(pController->getScriptEngine()) {
405+
qWarning() << "Unable to acquire the controller engine. Has the "
406+
"controller open successfully?";
407+
return;
408+
}
409+
410+
pController->applyMapping(m_pConfig->getResourcePath(), m_pRuntimeData);
397411

398412
// Update configuration to reflect controller is enabled.
399413
m_pConfig->setValue(

src/controllers/controllermanager.h

+2
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
};
+22
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+
}
+68
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+
};

src/controllers/legacycontrollermapping.h

+11
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class LegacyControllerMapping {
3333
: m_productMatches(other.m_productMatches),
3434
m_bDirty(other.m_bDirty),
3535
m_deviceId(other.m_deviceId),
36+
m_sharedDataNamespace(other.m_sharedDataNamespace),
3637
m_filePath(other.m_filePath),
3738
m_name(other.m_name),
3839
m_author(other.m_author),
@@ -245,6 +246,15 @@ class LegacyControllerMapping {
245246
return m_deviceId;
246247
}
247248

249+
void setSharedDataNamespace(QString sharedDataNamespace) {
250+
m_sharedDataNamespace = std::move(sharedDataNamespace);
251+
setDirty(true);
252+
}
253+
254+
const QString& sharedDataNamespace() const {
255+
return m_sharedDataNamespace;
256+
}
257+
248258
void setFilePath(const QString& filePath) {
249259
m_filePath = filePath;
250260
setDirty(true);
@@ -361,6 +371,7 @@ class LegacyControllerMapping {
361371
bool m_bDirty;
362372

363373
QString m_deviceId;
374+
QString m_sharedDataNamespace;
364375
QString m_filePath;
365376
QString m_name;
366377
QString m_author;

src/controllers/legacycontrollermappingfilehandler.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ void LegacyControllerMappingFileHandler::addScriptFilesToMapping(
360360

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

364370
// See TODO in LegacyControllerMapping::DeviceDirection - `direction` should
365371
// only be used as a workaround till the bulk integration gets refactored

src/controllers/midi/midicontroller.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ bool MidiController::matchMapping(const MappingInfo& mapping) {
7777
return false;
7878
}
7979

80-
bool MidiController::applyMapping(const QString& resourcePath) {
80+
bool MidiController::applyMapping(const QString& resourcePath,
81+
std::shared_ptr<ControllerSharedData> runtimeData) {
8182
// Handles the engine
82-
bool result = Controller::applyMapping(resourcePath);
83+
bool result = Controller::applyMapping(resourcePath, std::move(runtimeData));
8384

8485
// Only execute this code if this is an output device
8586
if (isOutputDevice()) {

src/controllers/midi/midicontroller.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class MidiOutputHandler;
1111
class MidiController;
12+
class ControllerSharedData;
1213

1314
class MidiInputHandleJSProxy final : public QObject {
1415
Q_OBJECT
@@ -88,7 +89,7 @@ class MidiController : public Controller {
8889
void slotBeforeEngineShutdown() override;
8990

9091
private slots:
91-
bool applyMapping(const QString& resourcePath) override;
92+
bool applyMapping(const QString& resourcePath, std::shared_ptr<ControllerSharedData>) override;
9293

9394
void learnTemporaryInputMappings(const MidiInputMappings& mappings);
9495
void clearTemporaryInputMappings();

src/controllers/scripting/controllerscriptenginebase.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,7 @@ void ControllerScriptEngineBase::errorDialogButton(
288288
void ControllerScriptEngineBase::throwJSError(const QString& message) {
289289
m_pJSEngine->throwError(message);
290290
}
291+
292+
void ControllerScriptEngineBase::setSharedData(ControllerNamespacedSharedData* runtimeData) {
293+
m_runtimeData = std::unique_ptr<ControllerNamespacedSharedData>(runtimeData);
294+
}

0 commit comments

Comments
 (0)