@soundworks/core 3.7.0 → 4.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +48 -28
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +74 -112
- package/{common → src/common}/SharedStatePrivate.js +56 -62
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +114 -88
- package/{server → src/server}/Sockets.js +73 -60
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -193
- package/client/PluginManager.js +0 -279
- package/client/Socket.js +0 -346
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/logger.js +0 -162
- package/common/shared-state-utils.js +0 -89
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -214
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -273
- package/server/Server.js +0 -785
- package/server/index.js +0 -49
- package/server.js +0 -28
|
@@ -1,50 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
|
+
|
|
3
|
+
import clonedeep from 'lodash.clonedeep';
|
|
4
|
+
|
|
5
|
+
import BaseStateManager from '../common/BaseStateManager.js';
|
|
6
|
+
import ParameterBag from '../common/ParameterBag.js';
|
|
7
|
+
import SharedStatePrivate from '../common/SharedStatePrivate.js';
|
|
8
|
+
import {
|
|
9
|
+
SERVER_ID,
|
|
10
|
+
CREATE_REQUEST,
|
|
11
|
+
CREATE_RESPONSE,
|
|
12
|
+
CREATE_ERROR,
|
|
13
|
+
DELETE_NOTIFICATION,
|
|
14
|
+
ATTACH_REQUEST,
|
|
15
|
+
ATTACH_RESPONSE,
|
|
16
|
+
ATTACH_ERROR,
|
|
17
|
+
OBSERVE_REQUEST,
|
|
18
|
+
OBSERVE_RESPONSE,
|
|
19
|
+
OBSERVE_NOTIFICATION,
|
|
20
|
+
UNOBSERVE_NOTIFICATION,
|
|
21
|
+
DELETE_SCHEMA,
|
|
22
|
+
PRIVATE_STATES,
|
|
23
|
+
} from '../common/constants.js';
|
|
24
|
+
import { idGenerator } from '../common/utils.js';
|
|
25
|
+
|
|
26
|
+
const generateStateId = idGenerator();
|
|
27
|
+
const generateRemoteId = idGenerator();
|
|
2
28
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
|
|
11
|
-
|
|
12
|
-
var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
|
|
13
|
-
|
|
14
|
-
var _SharedStatePrivate = _interopRequireDefault(require("./SharedStatePrivate.js"));
|
|
15
|
-
|
|
16
|
-
var _SharedStateManagerClient = _interopRequireDefault(require("./SharedStateManagerClient.js"));
|
|
17
|
-
|
|
18
|
-
var _sharedStateUtils = require("./shared-state-utils.js");
|
|
19
|
-
|
|
20
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
/**
|
|
30
|
+
* @callback server.StateManager~ObserveCallback
|
|
31
|
+
* @async
|
|
32
|
+
* @param {String} schemaName - name of the schema
|
|
33
|
+
* @param {Number} stateId - id of the state
|
|
34
|
+
* @param {Number} nodeId - id of the node that created the state
|
|
35
|
+
*/
|
|
21
36
|
|
|
22
|
-
const generateStateId = (0, _sharedStateUtils.idGenerator)();
|
|
23
|
-
const generateRemoteId = (0, _sharedStateUtils.idGenerator)();
|
|
24
37
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
38
|
+
* The `StateManager` allows to create new {@link server.SharedState}s, or attach
|
|
39
|
+
* to {@link server.SharedState}s created by other nodes (clients or server). It
|
|
40
|
+
* can also track all the {@link server.SharedState}s created by other nodes.
|
|
28
41
|
*
|
|
29
|
-
* An instance of `
|
|
30
|
-
*
|
|
42
|
+
* An instance of `StateManager` is automatically created by the `soundworks.Server`
|
|
43
|
+
* at initialization (cf. {@link server.Server#stateManager}).
|
|
31
44
|
*
|
|
32
|
-
*
|
|
45
|
+
* Compared to the {@link client.StateManager}, the `server.StateManager` can also
|
|
46
|
+
* create and delete schemas, as well as register update hook that are executed when
|
|
47
|
+
* a state is updated.
|
|
33
48
|
*
|
|
34
|
-
* @
|
|
35
|
-
*
|
|
49
|
+
* See {@link server.Server#stateManager}
|
|
50
|
+
*
|
|
51
|
+
* Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
|
|
52
|
+
*
|
|
53
|
+
* ```
|
|
54
|
+
* // server-side
|
|
55
|
+
* import { Server } from '@soundworks/server/index.js';
|
|
36
56
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
57
|
+
* const server = new Server(config);
|
|
58
|
+
* // declare and register the schema of a shared state.
|
|
59
|
+
* server.stateManager.registerSchema('some-global-state', {
|
|
60
|
+
* myRandom: {
|
|
61
|
+
* type: 'float',
|
|
62
|
+
* default: 0,
|
|
63
|
+
* }
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* await server.start();
|
|
67
|
+
*
|
|
68
|
+
* // create a global state server-side
|
|
69
|
+
* const globalState = await server.stateManager.create('some-global-state');
|
|
70
|
+
* // listen and react to the changes made by the clients
|
|
71
|
+
* globalState.onUpdate(updates => console.log(updates));
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* ```
|
|
75
|
+
* // client-side
|
|
76
|
+
* import { Client } from '@soundworks/client.index.js';
|
|
77
|
+
*
|
|
78
|
+
* const client = new Client(config);
|
|
79
|
+
* await client.start();
|
|
80
|
+
*
|
|
81
|
+
* // attach to the global state created by the server
|
|
82
|
+
* const globalState = await client.stateManager.attach('some-global-state');
|
|
83
|
+
*
|
|
84
|
+
* // update the value of a `myRandom` parameter every seconds
|
|
85
|
+
* setInterval(() => {
|
|
86
|
+
* globalState.set({ myRandom: Math.random() });
|
|
87
|
+
* }, 1000);
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @memberof server
|
|
91
|
+
* @extends BaseStateManager
|
|
92
|
+
* @inheritdoc
|
|
93
|
+
* @hideconstructor
|
|
40
94
|
*/
|
|
41
|
-
|
|
42
|
-
class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
95
|
+
class StateManager extends BaseStateManager {
|
|
43
96
|
constructor() {
|
|
44
97
|
// acts as a client of itself locally
|
|
45
|
-
const localClientId =
|
|
46
|
-
const localTransport = new
|
|
98
|
+
const localClientId = SERVER_ID;
|
|
99
|
+
const localTransport = new EventEmitter();
|
|
100
|
+
|
|
47
101
|
super(localClientId, localTransport);
|
|
102
|
+
|
|
48
103
|
this._clientByNodeId = new Map();
|
|
49
104
|
this._serverStatesById = new Map();
|
|
50
105
|
this._schemas = new Map();
|
|
@@ -53,67 +108,61 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
53
108
|
|
|
54
109
|
this.addClient(localClientId, localTransport);
|
|
55
110
|
}
|
|
111
|
+
|
|
56
112
|
/**
|
|
57
|
-
* Add a client
|
|
58
|
-
*
|
|
113
|
+
* Add a client to the manager.
|
|
114
|
+
*
|
|
115
|
+
* This is automatically handled by the {@link server.Server} when a client connects.
|
|
59
116
|
*
|
|
60
117
|
* @param {Number} nodeId - Id of the client node, as given in
|
|
61
|
-
* {@link client.
|
|
118
|
+
* {@link client.StateManager}
|
|
62
119
|
* @param {Object} transport - Tranpsort mecanism to communicate with the
|
|
63
120
|
* client. Should implement a basic EventEmitter API.
|
|
64
121
|
*
|
|
65
122
|
* @private
|
|
66
123
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
124
|
addClient(nodeId, transport) {
|
|
70
|
-
const client = {
|
|
71
|
-
|
|
72
|
-
transport
|
|
73
|
-
};
|
|
125
|
+
const client = { id: nodeId, transport };
|
|
126
|
+
this._clientByNodeId.set(nodeId, client);
|
|
74
127
|
|
|
75
|
-
|
|
128
|
+
// ---------------------------------------------
|
|
76
129
|
// CREATE
|
|
77
130
|
// ---------------------------------------------
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
client.transport.addListener(_sharedStateUtils.CREATE_REQUEST, (reqId, schemaName, requireSchema, initValues = {}) => {
|
|
131
|
+
client.transport.addListener(CREATE_REQUEST, (reqId, schemaName, requireSchema, initValues = {}) => {
|
|
81
132
|
if (this._schemas.has(schemaName)) {
|
|
82
133
|
try {
|
|
83
134
|
const schema = this._schemas.get(schemaName);
|
|
84
|
-
|
|
85
135
|
const stateId = generateStateId.next().value;
|
|
86
|
-
const remoteId = generateRemoteId.next().value;
|
|
87
|
-
|
|
88
|
-
const state = new
|
|
136
|
+
const remoteId = generateRemoteId.next().value;
|
|
137
|
+
// id, schemaName, schema, manager, initValues = {}
|
|
138
|
+
const state = new SharedStatePrivate(stateId, schemaName, schema, this, initValues);
|
|
89
139
|
|
|
90
140
|
state._attachClient(remoteId, client, true); // attach client to the state
|
|
91
|
-
|
|
92
|
-
|
|
93
141
|
this._serverStatesById.set(stateId, state);
|
|
94
142
|
|
|
95
143
|
const currentValues = state._parameters.getValues();
|
|
96
|
-
|
|
97
144
|
const sendedSchema = requireSchema ? schema : null;
|
|
98
|
-
|
|
145
|
+
|
|
146
|
+
client.transport.emit(CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
|
|
99
147
|
|
|
100
148
|
this._observers.forEach(observer => {
|
|
101
|
-
observer.transport.emit(
|
|
149
|
+
observer.transport.emit(OBSERVE_NOTIFICATION, [schemaName, stateId, nodeId]);
|
|
102
150
|
});
|
|
103
|
-
} catch
|
|
104
|
-
client.transport.emit(
|
|
151
|
+
} catch(err) {
|
|
152
|
+
client.transport.emit(CREATE_ERROR, reqId, err.message);
|
|
105
153
|
console.error(err.message);
|
|
106
154
|
}
|
|
107
155
|
} else {
|
|
108
156
|
const msg = `Cannot create state "${schemaName}", schema does not exists`;
|
|
109
157
|
console.error(msg);
|
|
110
|
-
client.transport.emit(
|
|
158
|
+
client.transport.emit(CREATE_ERROR, reqId, msg);
|
|
111
159
|
}
|
|
112
|
-
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------
|
|
113
163
|
// ATTACH (when creator, is attached by default)
|
|
114
164
|
// ---------------------------------------------
|
|
115
|
-
|
|
116
|
-
client.transport.addListener(_sharedStateUtils.ATTACH_REQUEST, (reqId, schemaName, stateId = null, requireSchema = true) => {
|
|
165
|
+
client.transport.addListener(ATTACH_REQUEST, (reqId, schemaName, stateId = null, requireSchema = true) => {
|
|
117
166
|
if (this._schemas.has(schemaName)) {
|
|
118
167
|
let state = null;
|
|
119
168
|
|
|
@@ -137,63 +186,68 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
137
186
|
// several times to the same state.
|
|
138
187
|
// i.e. same state -> several remote attach on the same node
|
|
139
188
|
const remoteId = generateRemoteId.next().value;
|
|
140
|
-
|
|
141
189
|
const schema = this._schemas.get(schemaName);
|
|
142
|
-
|
|
143
190
|
const currentValues = state._parameters.getValues();
|
|
144
191
|
|
|
145
|
-
state._attachClient(remoteId, client, false);
|
|
146
|
-
|
|
147
|
-
|
|
192
|
+
state._attachClient(remoteId, client, false);
|
|
193
|
+
// send schema for client-side instanciation
|
|
148
194
|
const sendedSchema = requireSchema ? schema : null;
|
|
149
|
-
client.transport.emit(
|
|
195
|
+
client.transport.emit(ATTACH_RESPONSE, reqId, state.id, remoteId, schemaName, sendedSchema, currentValues);
|
|
150
196
|
} else {
|
|
151
197
|
const msg = `Cannot attach, no existing state for schema "${schemaName}" with stateId: "${stateId}"`;
|
|
152
|
-
client.transport.emit(
|
|
198
|
+
client.transport.emit(ATTACH_ERROR, reqId, msg);
|
|
153
199
|
console.error(msg);
|
|
154
200
|
}
|
|
155
201
|
} else {
|
|
156
202
|
const msg = `Cannot attach, schema "${schemaName}" does not exists`;
|
|
157
|
-
client.transport.emit(
|
|
203
|
+
client.transport.emit(ATTACH_ERROR, reqId, msg);
|
|
158
204
|
console.error(msg);
|
|
159
205
|
}
|
|
160
|
-
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------
|
|
161
209
|
// OBSERVE PEERS (be notified when a state is created, lazy)
|
|
162
210
|
// ---------------------------------------------
|
|
163
|
-
|
|
164
|
-
client.transport.addListener(_sharedStateUtils.OBSERVE_REQUEST, reqId => {
|
|
211
|
+
client.transport.addListener(OBSERVE_REQUEST, reqId => {
|
|
165
212
|
const statesInfos = [];
|
|
166
213
|
|
|
167
214
|
this._serverStatesById.forEach(state => {
|
|
168
|
-
const {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
215
|
+
const { schemaName, id, _creatorId } = state;
|
|
216
|
+
// only track application states
|
|
217
|
+
// (e.g. do not propagate infos about audit state)
|
|
218
|
+
if (!PRIVATE_STATES.includes(schemaName)) {
|
|
219
|
+
statesInfos.push([schemaName, id, _creatorId]);
|
|
220
|
+
}
|
|
174
221
|
});
|
|
175
222
|
|
|
176
|
-
client
|
|
177
|
-
|
|
223
|
+
// add client to observers first because if some (sync) server side
|
|
224
|
+
// callback throws, the client would never be added to the list
|
|
178
225
|
this._observers.add(client);
|
|
226
|
+
|
|
227
|
+
client.transport.emit(OBSERVE_RESPONSE, reqId, ...statesInfos);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
client.transport.addListener(UNOBSERVE_NOTIFICATION, () => {
|
|
231
|
+
this._observers.delete(client);
|
|
179
232
|
});
|
|
180
233
|
}
|
|
234
|
+
|
|
181
235
|
/**
|
|
182
|
-
* Remove a client
|
|
183
|
-
*
|
|
236
|
+
* Remove a client from the manager. Clean all created or attached states.
|
|
237
|
+
*
|
|
238
|
+
* This is automatically handled by the {@link server.Server} when a client disconnects.
|
|
184
239
|
*
|
|
185
240
|
* @param {Number} nodeId - Id of the client node, as given in
|
|
186
|
-
* {@link client.
|
|
241
|
+
* {@link client.StateManager}
|
|
187
242
|
*
|
|
188
243
|
* @private
|
|
189
244
|
*/
|
|
190
|
-
|
|
191
|
-
|
|
192
245
|
removeClient(nodeId) {
|
|
193
246
|
for (let [_id, state] of this._serverStatesById.entries()) {
|
|
194
|
-
let deleteState = false;
|
|
195
|
-
// everybody must delete it
|
|
247
|
+
let deleteState = false;
|
|
196
248
|
|
|
249
|
+
// define if the client is the creator of the state, in which case
|
|
250
|
+
// everybody must delete it
|
|
197
251
|
for (let [remoteId, attachedClient] of state._attachedClients.entries()) {
|
|
198
252
|
if (nodeId === attachedClient.id && remoteId === state._creatorRemoteId) {
|
|
199
253
|
deleteState = true;
|
|
@@ -208,31 +262,30 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
208
262
|
if (deleteState) {
|
|
209
263
|
if (remoteId !== state._creatorRemoteId) {
|
|
210
264
|
// send notification to other attached nodes
|
|
211
|
-
attachedClient.transport.emit(`${
|
|
265
|
+
attachedClient.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
|
|
212
266
|
}
|
|
213
267
|
|
|
214
268
|
this._serverStatesById.delete(state.id);
|
|
215
269
|
}
|
|
216
270
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
271
|
+
}
|
|
219
272
|
|
|
273
|
+
// if is an observer, delete it
|
|
220
274
|
const client = this._clientByNodeId.get(nodeId);
|
|
221
|
-
|
|
222
275
|
this._observers.delete(client);
|
|
223
|
-
|
|
224
276
|
this._clientByNodeId.delete(nodeId);
|
|
225
277
|
}
|
|
278
|
+
|
|
226
279
|
/**
|
|
227
280
|
* Register a schema from which shared states (cf. {@link common.SharedState})
|
|
228
281
|
* can be instanciated.
|
|
229
282
|
*
|
|
230
283
|
* @param {String} schemaName - Name of the schema.
|
|
231
|
-
* @param {server.
|
|
284
|
+
* @param {server.StateManager~schema} schema - Data structure
|
|
232
285
|
* describing the states that will be created from this schema.
|
|
233
286
|
*
|
|
234
|
-
* @see {@link server.
|
|
235
|
-
* @see {@link client.
|
|
287
|
+
* @see {@link server.StateManager#create}
|
|
288
|
+
* @see {@link client.StateManager#create}
|
|
236
289
|
*
|
|
237
290
|
* @example
|
|
238
291
|
* server.stateManager.registerSchema('my-schema', {
|
|
@@ -248,20 +301,18 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
248
301
|
* }
|
|
249
302
|
* })
|
|
250
303
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
253
304
|
registerSchema(schemaName, schema) {
|
|
254
305
|
if (this._schemas.has(schemaName)) {
|
|
255
306
|
throw new Error(`[stateManager.registerSchema] cannot register schema with name: "${schemaName}", schema name already exists`);
|
|
256
307
|
}
|
|
257
308
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this._schemas.set(schemaName, (0, _lodash.default)(schema)); // create hooks list
|
|
261
|
-
|
|
309
|
+
ParameterBag.validateSchema(schema);
|
|
262
310
|
|
|
311
|
+
this._schemas.set(schemaName, clonedeep(schema));
|
|
312
|
+
// create hooks list
|
|
263
313
|
this._hooksBySchemaName.set(schemaName, new Set());
|
|
264
314
|
}
|
|
315
|
+
|
|
265
316
|
/**
|
|
266
317
|
* Delete a schema and all associated states.
|
|
267
318
|
* When a schema is deleted, all states created from this schema are deleted
|
|
@@ -270,40 +321,39 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
270
321
|
*
|
|
271
322
|
* @param {String} schemaName - Name of the schema.
|
|
272
323
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
324
|
deleteSchema(schemaName) {
|
|
276
325
|
// @note: deleting schema
|
|
277
326
|
for (let [_id, state] of this._serverStatesById.entries()) {
|
|
278
327
|
if (state.schemaName === schemaName) {
|
|
279
328
|
for (let [remoteId, attached] of state._attachedClients.entries()) {
|
|
280
329
|
state._detachClient(remoteId, attached);
|
|
281
|
-
|
|
282
|
-
attached.transport.emit(`${_sharedStateUtils.DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
|
|
330
|
+
attached.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
|
|
283
331
|
}
|
|
284
332
|
|
|
285
333
|
this._serverStatesById.delete(this.id);
|
|
286
334
|
}
|
|
287
|
-
}
|
|
288
|
-
|
|
335
|
+
}
|
|
289
336
|
|
|
337
|
+
// clear schema cache of all connected clients
|
|
290
338
|
for (let client of this._clientByNodeId.values()) {
|
|
291
|
-
client.transport.emit(`${
|
|
339
|
+
client.transport.emit(`${DELETE_SCHEMA}`, schemaName);
|
|
292
340
|
}
|
|
293
341
|
|
|
294
|
-
this._schemas.delete(schemaName);
|
|
295
|
-
|
|
296
|
-
|
|
342
|
+
this._schemas.delete(schemaName);
|
|
343
|
+
// delete registered hooks
|
|
297
344
|
this._hooksBySchemaName.delete(schemaName);
|
|
298
345
|
}
|
|
346
|
+
|
|
299
347
|
/**
|
|
300
|
-
* @callback server.
|
|
348
|
+
* @callback server.StateManager~updateHook
|
|
349
|
+
* @async
|
|
301
350
|
*
|
|
302
351
|
* @param {Object} updates - Update object as given on a set callback, or
|
|
303
352
|
* result of the previous hook
|
|
304
353
|
* @param {Object} currentValues - Current values of the state.
|
|
305
354
|
* @param {Object} [context=null] - Optionnal context passed by the creator
|
|
306
355
|
* of the update.
|
|
356
|
+
*
|
|
307
357
|
* @return {Object} The "real" updates to be applied on the state.
|
|
308
358
|
*/
|
|
309
359
|
|
|
@@ -317,11 +367,20 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
317
367
|
* The hook is associated to every state of its kind (i.e. schemaName) and
|
|
318
368
|
* executed on every update (call of `set`). Note that the hooks are executed
|
|
319
369
|
* server-side regarless the node on which `set` has been called and before
|
|
320
|
-
* the "actual" update of the state (e.g. before the call of `
|
|
370
|
+
* the "actual" update of the state (e.g. before the call of `onUpdate`).
|
|
371
|
+
*
|
|
372
|
+
* @param {String} schemaName - Kind of states on which applying the hook.
|
|
373
|
+
* @param {server.StateManager~updateHook} updateHook - Function
|
|
374
|
+
* called between the `set` call and the actual update.
|
|
375
|
+
*
|
|
376
|
+
* @returns {Fuction} deleteHook - Handler that deletes the hook when executed.
|
|
321
377
|
*
|
|
322
378
|
* @example
|
|
323
|
-
* server.stateManager.registerSchema('hooked',
|
|
324
|
-
*
|
|
379
|
+
* server.stateManager.registerSchema('hooked', {
|
|
380
|
+
* name: { type: 'string', default: null, nullable: true },
|
|
381
|
+
* name: { numUpdates: 'integer', default: 0 },
|
|
382
|
+
* });
|
|
383
|
+
* server.stateManager.registerUpdateHook('hooked', updates => {
|
|
325
384
|
* return {
|
|
326
385
|
* ...updates
|
|
327
386
|
* numUpdates: currentValues.numUpdates + 1,
|
|
@@ -331,15 +390,9 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
331
390
|
* const state = await server.stateManager.create('hooked');
|
|
332
391
|
*
|
|
333
392
|
* await state.set({ name: 'test' });
|
|
334
|
-
* state.getValues();
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
* @param {String} schemaName - Kind of states on which applying the hook.
|
|
338
|
-
* @param {server.SharedStateManagerServer~updateHook} updateHook - Function
|
|
339
|
-
* called between the `set` call and the actual update.
|
|
393
|
+
* const values = state.getValues();
|
|
394
|
+
* assert.deepEqual(result, { name: 'test', numUpdates: 1 });
|
|
340
395
|
*/
|
|
341
|
-
|
|
342
|
-
|
|
343
396
|
registerUpdateHook(schemaName, updateHook) {
|
|
344
397
|
// throw error if schemaName has not been registered
|
|
345
398
|
if (!this._schemas.has(schemaName)) {
|
|
@@ -347,12 +400,11 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
347
400
|
}
|
|
348
401
|
|
|
349
402
|
const hooks = this._hooksBySchemaName.get(schemaName);
|
|
350
|
-
|
|
351
403
|
hooks.add(updateHook);
|
|
404
|
+
|
|
352
405
|
return () => hooks.delete(updateHook);
|
|
353
406
|
}
|
|
354
407
|
|
|
355
408
|
}
|
|
356
409
|
|
|
357
|
-
|
|
358
|
-
exports.default = _default;
|
|
410
|
+
export default StateManager;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
|
|
3
|
+
import { getTime } from '@ircam/sc-gettime';
|
|
4
|
+
|
|
5
|
+
let stack = [];
|
|
6
|
+
let averageLatencyWindow = 5;
|
|
7
|
+
let averageLatencyPeriod = 2;
|
|
8
|
+
|
|
9
|
+
let intervalId = null;
|
|
10
|
+
let meanLatency = 0;
|
|
11
|
+
|
|
12
|
+
parentPort.on('message', msg => {
|
|
13
|
+
switch (msg.type) {
|
|
14
|
+
case 'config': {
|
|
15
|
+
averageLatencyWindow = msg.value.averageLatencyWindow;
|
|
16
|
+
averageLatencyPeriod = msg.value.averageLatencyPeriod;
|
|
17
|
+
|
|
18
|
+
// launch compute in its own loop so that the number of computation is
|
|
19
|
+
// decoupled from the number of connected clients
|
|
20
|
+
clearInterval(intervalId);
|
|
21
|
+
intervalId = setInterval(computeAverageLatency, averageLatencyPeriod * 1000);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case 'add-measurement': {
|
|
25
|
+
// compute latency considering ping and pong time are equal
|
|
26
|
+
const entry = msg.value;
|
|
27
|
+
entry.latency = (entry.pong - entry.ping) / 2;
|
|
28
|
+
// add new measure to the stack
|
|
29
|
+
stack.push(entry);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function computeAverageLatency() {
|
|
35
|
+
const now = getTime();
|
|
36
|
+
// remove old measurements from stack
|
|
37
|
+
stack = stack.filter(entry => entry.pong >= now - averageLatencyWindow);
|
|
38
|
+
|
|
39
|
+
// prevent dividing by zero, just return last computed value
|
|
40
|
+
if (stack.length === 0) {
|
|
41
|
+
parentPort.postMessage({
|
|
42
|
+
type: 'computed-average-latency',
|
|
43
|
+
value: meanLatency,
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
// compute mean
|
|
47
|
+
let sum = 0;
|
|
48
|
+
stack.forEach(entry => sum += entry.latency);
|
|
49
|
+
|
|
50
|
+
meanLatency = sum / stack.length;
|
|
51
|
+
// send result to main thread
|
|
52
|
+
parentPort.postMessage({
|
|
53
|
+
type: 'computed-average-latency',
|
|
54
|
+
value: meanLatency,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal schema used to audit the application.
|
|
3
|
+
*/
|
|
4
|
+
export default {
|
|
5
|
+
/**
|
|
6
|
+
* Number of connected clients by role.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* {
|
|
10
|
+
* player: 12,
|
|
11
|
+
* controller: 1,
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
numClients: {
|
|
15
|
+
type: 'any',
|
|
16
|
+
default: {},
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Average latency in seconds computed from ping/pong.
|
|
21
|
+
*/
|
|
22
|
+
averageNetworkLatency: {
|
|
23
|
+
type: 'float',
|
|
24
|
+
default: 0,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Time window in second used to compute the average latency. Defaults to 5
|
|
29
|
+
*/
|
|
30
|
+
averageNetworkLatencyWindow: {
|
|
31
|
+
type: 'float',
|
|
32
|
+
default: 5,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Period in second at which the average latency is computed. Defaults to 2
|
|
37
|
+
*/
|
|
38
|
+
averageNetworkLatencyPeriod: {
|
|
39
|
+
type: 'float',
|
|
40
|
+
default: 2,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration object for the server.
|
|
3
|
+
*
|
|
4
|
+
* @typedef ServerConfig
|
|
5
|
+
* @memberof server
|
|
6
|
+
* @type {object}
|
|
7
|
+
* @property {object} [app] - Application configration object.
|
|
8
|
+
* @property {object} app.clients - Definition of the application clients.
|
|
9
|
+
* @property {string} [app.name=''] - Name of the application.
|
|
10
|
+
* @property {string} [app.author=''] - Name of the author.
|
|
11
|
+
* @property {object} [env] - Environment configration object.
|
|
12
|
+
* @property {boolean} env.port - Port on which the server is listening.
|
|
13
|
+
* @property {boolean} env.useHttps - Define is the server run in http or in https.
|
|
14
|
+
* @property {boolean} [env.httpsInfos={}] - Path to cert files for https.
|
|
15
|
+
* @property {boolean} env.serverAddress - Domain name or IP of the server.
|
|
16
|
+
* Mandatory if node clients are defined
|
|
17
|
+
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
18
|
+
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Server-side part of the *soundworks* framework.
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* import '@soundworks/helpers/polyfills.js';
|
|
26
|
+
* import { Server } from '@soundworks/core/server.js';
|
|
27
|
+
* import { loadConfig } from '../utils/load-config.js';
|
|
28
|
+
*
|
|
29
|
+
* // - General documentation: https://soundworks.dev/
|
|
30
|
+
* // - API documentation: https://soundworks.dev/api
|
|
31
|
+
* // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
|
|
32
|
+
* // - Wizard & Tools: `npx soundworks`
|
|
33
|
+
*
|
|
34
|
+
* const config = loadConfig(process.env.ENV, import.meta.url);
|
|
35
|
+
*
|
|
36
|
+
* const server = new Server(config);
|
|
37
|
+
* server.setDefaultTemplateConfig();
|
|
38
|
+
*
|
|
39
|
+
* await server.start();
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @namespace server
|
|
43
|
+
*/
|
|
44
|
+
export { default as Server } from './Server.js';
|
|
45
|
+
export { default as Context } from './Context.js';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["src/**/*.js"],
|
|
3
|
+
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
// Tells TypeScript to read JS files, as
|
|
6
|
+
// normally they are ignored as source files
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
// Generate d.ts files
|
|
9
|
+
"declaration": true,
|
|
10
|
+
// This compiler run should
|
|
11
|
+
// only output d.ts files
|
|
12
|
+
"emitDeclarationOnly": true,
|
|
13
|
+
// Types should go into this directory.
|
|
14
|
+
// Removing this would place the .d.ts files
|
|
15
|
+
// next to the .js files
|
|
16
|
+
"outDir": "types",
|
|
17
|
+
// go to js file when using IDE functions like
|
|
18
|
+
// "Go to Definition" in VSCode
|
|
19
|
+
"declarationMap": true
|
|
20
|
+
}
|
|
21
|
+
}
|