|
| 1 | +# Controller Shared Data |
| 2 | + |
| 3 | +* **Owners:** |
| 4 | + * `@ywwg` |
| 5 | + * `@acolombier` |
| 6 | + |
| 7 | +* **Implementation Status:** `Partially implemented` |
| 8 | + |
| 9 | +* **Related Issues and PRs:** |
| 10 | + * [Ability for controller to share data at |
| 11 | + runtime](https://github.com/mixxxdj/mixxx/pull/12199) |
| 12 | + |
| 13 | +> TL;DR: Allow controllers mappings to set and retrieve variables of different |
| 14 | +> data types in order to exchange them between the controller code and the |
| 15 | +> engine. Think: ControlObjects of arbitrary type that controllers can declare. |
| 16 | +
|
| 17 | +## Why |
| 18 | + |
| 19 | +There are multiple scenarios where controller mapping scripts need to share and |
| 20 | +access data outside the container of their own controller script engine. This |
| 21 | +includes situations where some controllers expose more than one USB interface |
| 22 | +that need to communicate with each other, or when a DJ connects multiple |
| 23 | +instances of the same hardware to Mixxx. |
| 24 | + |
| 25 | +### Pitfalls of the current solution |
| 26 | + |
| 27 | +ControlObjects are the normal way we share data, but: |
| 28 | + |
| 29 | +* Controllers can't declare control objects. |
| 30 | +* Putting controller-specific data inside Mixxx itself would be bad and lead to |
| 31 | + bloat. |
| 32 | +* Controllers need to share more types of data than just double values. |
| 33 | + |
| 34 | +## Goals |
| 35 | + |
| 36 | +Goals and use cases for the solution as proposed in [How](#how): |
| 37 | + |
| 38 | +* Namespacing: Allow different controllers to declare same-named data objects |
| 39 | + without risk of collisions |
| 40 | +* Support controllers with screens that require communication from HID to |
| 41 | + separate Bulk USB devices (Traktor S4 MK3). |
| 42 | +* Build a data model foundation for users with multiple instances of the same |
| 43 | + controller (CDJ-2000). |
| 44 | +* Ensure that the API could support the features we may wish to add in the |
| 45 | + future, such as global namespace, without breaking controller mappings that |
| 46 | + use the API defined here. |
| 47 | + |
| 48 | +### Audience |
| 49 | + |
| 50 | +Users of modern controllers or multiple controllers will appreciate this work. |
| 51 | +Specifically, this work is required to fully support the Traktor S4 MK3, which |
| 52 | +has separate USB interfaces for the controller and the two screens (two total USB |
| 53 | +interfaces). |
| 54 | + |
| 55 | +## Non-Goals |
| 56 | + |
| 57 | +* We do not intend to fully support the multi-device scenario yet, that needs |
| 58 | + further design to associate specific devices with specific controller |
| 59 | + configurations. |
| 60 | +* We do not intend to immediately support "global" objects accessible across |
| 61 | + namespaces, like "universal shift". |
| 62 | + |
| 63 | +## "Universal Shift" |
| 64 | + |
| 65 | +"Universal Shift" refers to the idea that a shift button pressed on one |
| 66 | +controller can be detected by any and all other controllers. This may be a |
| 67 | +useful use-case but creates a lot of difficulties, so for now Universal Shift is |
| 68 | +out of scope for this first implementation. |
| 69 | + |
| 70 | +## How |
| 71 | + |
| 72 | +### Data Object |
| 73 | + |
| 74 | +We will create a central object inside Mixxx that contains a triple-keyed map: |
| 75 | + |
| 76 | +* Namespace (string) |
| 77 | + * Entity (string) |
| 78 | + * Key (string) |
| 79 | + * Value ("SafeData") |
| 80 | + |
| 81 | +#### Namespace |
| 82 | + |
| 83 | +`Namespace` is a string that is unique to each controller **mapping |
| 84 | +definition**. All connected controllers of the same model will share the same |
| 85 | +namespace. e.g. Two CDJ-2000's will both have a namespace like `CDJ_2000`. All |
| 86 | +hardware mappings must have distinct namespaces. |
| 87 | + |
| 88 | +#### Entity |
| 89 | + |
| 90 | +`Entity` is a logical value defined by the controller mapping definition or |
| 91 | +during the initialization of the mapping script (e.g. for readout of a serial |
| 92 | +number using MIDI commands). It can be like a Mixxx-style group ("`[Channel1]`") |
| 93 | +but it can be any arbitrary string. Many controllers will want to define |
| 94 | +something like `deck1` to refer to a device that can itself be assigned to |
| 95 | +multiple mixxx channels. For the case of multiple devices of the same type, it |
| 96 | +is intended to implement functionality to gather a unique device identifier in a |
| 97 | +later step. These will than be used as Entity to distinguish the identical |
| 98 | +devices. These identifiers include, but are not limited to: |
| 99 | + |
| 100 | +* USB device serial number (only works for USB and not each manufacturer use |
| 101 | + unique serial numbers) |
| 102 | +* Operating system provided device identifiers like Container-ID on Windows or |
| 103 | + Location-ID on macOS |
| 104 | + |
| 105 | +The controller mapping decides how these entities behave and Mixxx does no |
| 106 | +enforcement of them. To reiterate: even if an "entity" "looks like" a Mixxx |
| 107 | +group, it is not. |
| 108 | + |
| 109 | +Here is partial suggestion for entity definition, which could be extended over |
| 110 | +time. |
| 111 | + |
| 112 | +```typescript |
| 113 | +declare Entities { |
| 114 | +type Mixer = 'mixer'; |
| 115 | +type Main = 'main'; |
| 116 | +type Library = 'library'; |
| 117 | +type Decks = 'deck1' | 'deck2' | 'deck3' | 'deck4'; |
| 118 | +type Channels = 'channel1' | 'channel2' | 'channel3' | 'channel4'; |
| 119 | +type Controller = 'controller'; |
| 120 | +} |
| 121 | +type Entity = Entities.Mixer | Entities.Main | Entities.Library | Entities.Decks | Entities.Channels | Entities.Controller; |
| 122 | +``` |
| 123 | + |
| 124 | +Numbered entity names could also be validated with a regular expression such as |
| 125 | +`deck[0-9]+`. |
| 126 | + |
| 127 | +#### Key |
| 128 | + |
| 129 | +`Key` is a logical value defined by the controller mapping definition. It could |
| 130 | +refer to a button, light, knob, or abstract name. |
| 131 | + |
| 132 | +The controller mapping decides how these keys behave and Mixxx does no |
| 133 | +enforcement of them. Similar to "entity", keys bear no relation to equivalent |
| 134 | +Mixxx keys. |
| 135 | + |
| 136 | +#### Value |
| 137 | + |
| 138 | +In the engine, the `value` is stored as a QVariant, however we want to only |
| 139 | +support a limited set of types in Javascript / Typescript. For the first |
| 140 | +implementation, we will support bool, number, and string, and Arrays of those: |
| 141 | + |
| 142 | +```typescript |
| 143 | +type SafePrimitive = string | number | boolean | null; |
| 144 | + |
| 145 | +type SafeData = |
| 146 | + | SafePrimitive |
| 147 | + | SafePrimitive[]; |
| 148 | +``` |
| 149 | + |
| 150 | +The list of allowable types can be expanded as needed, but we want to be sure |
| 151 | +that the shared data system does not become a "bag of bytes" message bus for |
| 152 | +large pieces of data like bitmaps or code, nor should it be used to circumvent |
| 153 | +intentional limitations or gaps in the overall javascript framework. |
| 154 | + |
| 155 | +#### Example |
| 156 | + |
| 157 | +The shift button on the left side of a Traktor S4MK3 would be stored this way: |
| 158 | + |
| 159 | +pseudocode -- not final naming: |
| 160 | + |
| 161 | +`m_shared_data["S4MK3"]["deck1"]["shift"] = true` |
| 162 | + |
| 163 | +### API |
| 164 | + |
| 165 | +The shared data API should be roughly the same across Controllers, QML, and C++. |
| 166 | +The primary difference is that C++ will have access to the namespace value at |
| 167 | +all times, whereas controllers and QML will have that value elided. |
| 168 | + |
| 169 | +The controller javascript has access to the shared data object through three |
| 170 | +functions: |
| 171 | + |
| 172 | +#### Get |
| 173 | + |
| 174 | +excuse the pseudo-js: |
| 175 | + |
| 176 | +`engine.getSharedValue(entity: Entity|string, key: string): SafeData?` |
| 177 | + |
| 178 | +`namespace` is set automatically by the engine code, so controllers can't get |
| 179 | +that wrong. |
| 180 | + |
| 181 | +This function returns error if the value is not found. |
| 182 | + |
| 183 | +#### Set |
| 184 | + |
| 185 | +`engine.setSharedData(entity: Entity, key: string, value: SafeData): void` |
| 186 | + |
| 187 | +`namespace` is set automatically by the engine code, so controllers can't get |
| 188 | +that wrong. |
| 189 | + |
| 190 | +Calling "set" triggers "updated" signals to all subscribers across the engine, |
| 191 | +QML (e.g. controller screen code), and controllers. |
| 192 | + |
| 193 | +Controllers do not get notified about updates they initiated themselves, to |
| 194 | +prevent circular signal loops. |
| 195 | + |
| 196 | +#### Updated |
| 197 | + |
| 198 | +Controllers can subscribe to notifications about data updates via a method |
| 199 | +similar to how they subscribe to engine Control Object updates: |
| 200 | + |
| 201 | +(not final naming) |
| 202 | + |
| 203 | +`function makeSharedDataConnection(entity: Entity, name: string, callback: CoCallback): ScriptConnection | undefined;` |
| 204 | + |
| 205 | +In this first implementation, controllers can only subscribe to updates for |
| 206 | +their own namespace. |
| 207 | + |
| 208 | +### Possible future directions |
| 209 | + |
| 210 | +The following are possible future extensions to this proposal that are currently |
| 211 | +out of scope and will not be implemented in the first version, but we want to |
| 212 | +make sure to leave room in case we add them in the future: |
| 213 | + |
| 214 | +#### Cross-device communication / subscription |
| 215 | + |
| 216 | +There is a possibility controller authors may want access to signals sent from |
| 217 | +other controllers, for instance a "universal shift" button. For this purpose we |
| 218 | +may choose to allow controller to "subscribe" to updates from other namespaces, |
| 219 | +or all namespaces, in a read-only fashion. In this scenario, controllers would |
| 220 | +not have the ability to write updates to namespaces outside their own. This may |
| 221 | +be brittle because controllers would need to know about all possible valid |
| 222 | +namespaces, so this feature would need more care to make it maintainable. |
| 223 | + |
| 224 | +#### "Global" namespace |
| 225 | + |
| 226 | +Another possibility is that we may want a "global" namespaces that all |
| 227 | +controllers can read and write to. This would be another way to support a |
| 228 | +"universal shift" button. This would have to be carefully managed to prevent |
| 229 | +collisions between controller configs. One way to do this would be to "bless" |
| 230 | +specific entities and keys for the global namespace, and controller authors |
| 231 | +would have to add their requested global entity/key to Mixxx. |
| 232 | + |
| 233 | +## Alternatives |
| 234 | + |
| 235 | +The original implementation did not have entities and keys and instead had a |
| 236 | +single namespaced data blob that controllers had to manage themselves. This |
| 237 | +approach requires a lot more work on the part of the controller author to merge |
| 238 | +and manage the data object. |
| 239 | + |
| 240 | +## Action Plan |
| 241 | + |
| 242 | +1. Build on the existing PR to implement the desired API |
| 243 | +2. Rewrite the Traktor S4 MK3 mapping to support the new API. |
0 commit comments