@soundworks/core 4.0.0-alpha.16 → 4.0.0-alpha.18
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/CHANGELOG.md +5 -1
- package/package.json +1 -1
- package/src/client/Context.js +9 -9
- package/src/common/BaseSharedState.js +13 -2
- package/src/common/BaseSharedStateCollection.js +2 -3
- package/src/common/BaseStateManager.js +4 -2
- package/src/common/BatchedTransport.js +66 -0
- package/src/common/PromiseStore.js +16 -9
- package/src/common/SharedStatePrivate.js +12 -23
- package/src/common/constants.js +3 -0
- package/src/server/Sockets.js +7 -8
- package/src/server/StateManager.js +8 -5
- package/src/server/audit-network-latency.worker.js +16 -4
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/client/Context.js
CHANGED
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
CONTEXT_EXIT_ERROR,
|
|
10
10
|
} from '../common/constants.js';
|
|
11
11
|
|
|
12
|
+
// share between all context, as channels are common to all contexts
|
|
13
|
+
const promiseStore = new PromiseStore('Context');
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
16
|
* Base class to extend in order to implement a new Context.
|
|
14
17
|
*
|
|
@@ -77,15 +80,12 @@ class Context {
|
|
|
77
80
|
*/
|
|
78
81
|
this.status = 'idle';
|
|
79
82
|
|
|
80
|
-
/** @private */
|
|
81
|
-
this._promiseStore = new PromiseStore(this.constructor.name);
|
|
82
|
-
|
|
83
83
|
this.client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {
|
|
84
84
|
if (contextName !== this.name) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
promiseStore.resolve(reqId);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
this.client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
|
|
@@ -93,7 +93,7 @@ class Context {
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
promiseStore.reject(reqId, msg);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
this.client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
|
|
@@ -101,7 +101,7 @@ class Context {
|
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
promiseStore.resolve(reqId);
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
this.client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
|
|
@@ -109,7 +109,7 @@ class Context {
|
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
promiseStore.reject(reqId, msg);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
this.client.contextManager.register(this);
|
|
@@ -205,7 +205,7 @@ class Context {
|
|
|
205
205
|
// we need the try/catch block to change the promise rejection into proper error
|
|
206
206
|
try {
|
|
207
207
|
await new Promise((resolve, reject) => {
|
|
208
|
-
const reqId =
|
|
208
|
+
const reqId = promiseStore.add(resolve, reject, 'enter-context');
|
|
209
209
|
this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
|
|
210
210
|
});
|
|
211
211
|
} catch (err) {
|
|
@@ -239,7 +239,7 @@ class Context {
|
|
|
239
239
|
// we need the try/catch block to change the promise rejection into proper error
|
|
240
240
|
try {
|
|
241
241
|
await new Promise((resolve, reject) => {
|
|
242
|
-
const reqId =
|
|
242
|
+
const reqId = promiseStore.add(resolve, reject, 'exit-context');
|
|
243
243
|
this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
|
|
244
244
|
});
|
|
245
245
|
} catch (err) {
|
|
@@ -223,9 +223,16 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
223
223
|
// remove listeners
|
|
224
224
|
this._client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
|
|
225
225
|
this._client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
|
|
226
|
-
this._client.transport.removeAllListeners(`${
|
|
226
|
+
this._client.transport.removeAllListeners(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`);
|
|
227
227
|
this._client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
|
|
228
|
-
|
|
228
|
+
|
|
229
|
+
if (this._isOwner) {
|
|
230
|
+
this._client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
|
|
231
|
+
this._client.transport.removeAllListeners(`${DELETE_ERROR}-${this.id}-${this.remoteId}`);
|
|
232
|
+
} else {
|
|
233
|
+
this._client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
|
|
234
|
+
this._client.transport.removeAllListeners(`${DETACH_ERROR}-${this.id}-${this.remoteId}`);
|
|
235
|
+
}
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
/** @private */
|
|
@@ -320,6 +327,10 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
320
327
|
* const updates = await state.set({ myParam: Math.random() });
|
|
321
328
|
*/
|
|
322
329
|
async set(updates, context = null) {
|
|
330
|
+
if (this._detached) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
323
334
|
if (!isPlainObject(updates)) {
|
|
324
335
|
throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as first parameter`);
|
|
325
336
|
}
|
|
@@ -3,9 +3,7 @@ import {
|
|
|
3
3
|
kAttachInCollection,
|
|
4
4
|
} from './BaseStateManager.js';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* @private
|
|
8
|
-
*/
|
|
6
|
+
/** @private */
|
|
9
7
|
class BaseSharedStateCollection {
|
|
10
8
|
constructor(stateManager, schemaName, options = {}) {
|
|
11
9
|
this._stateManager = stateManager;
|
|
@@ -21,6 +19,7 @@ class BaseSharedStateCollection {
|
|
|
21
19
|
this._unobserve = null;
|
|
22
20
|
}
|
|
23
21
|
|
|
22
|
+
/** @private */
|
|
24
23
|
async _init() {
|
|
25
24
|
this._controller = await this._stateManager[kCreateCollectionController](this._schemaName);
|
|
26
25
|
this._controller.onUpdate(async (updates, context) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isString, isFunction, isPlainObject } from '@ircam/sc-utils';
|
|
2
2
|
import SharedState from './BaseSharedState.js';
|
|
3
3
|
import SharedStateCollection from './BaseSharedStateCollection.js';
|
|
4
|
+
import BatchedTransport from './BatchedTransport.js';
|
|
4
5
|
import PromiseStore from './PromiseStore.js';
|
|
5
6
|
import {
|
|
6
7
|
CREATE_REQUEST,
|
|
@@ -30,7 +31,8 @@ class BaseStateManager {
|
|
|
30
31
|
* Must implement a basic EventEmitter API.
|
|
31
32
|
*/
|
|
32
33
|
constructor(id, transport) {
|
|
33
|
-
|
|
34
|
+
// proxy transport with BatchedTransport;
|
|
35
|
+
this.client = { id, transport: new BatchedTransport(transport) };
|
|
34
36
|
|
|
35
37
|
this._statesById = new Map(); // <id, state>
|
|
36
38
|
this._cachedSchemas = new Map(); // <shemaName, definition>
|
|
@@ -388,7 +390,7 @@ class BaseStateManager {
|
|
|
388
390
|
try {
|
|
389
391
|
await collection._init();
|
|
390
392
|
} catch (err) {
|
|
391
|
-
console.log(err.message)
|
|
393
|
+
console.log(err.message);
|
|
392
394
|
throw new Error(`Cannot create collection, schema "${schemaName}" does not exists`);
|
|
393
395
|
}
|
|
394
396
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BATCHED_TRANSPORT_CHANNEL } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This class proxies transports given the SharedStateManager to batch messages
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
class BatchedTransport {
|
|
8
|
+
constructor(transport) {
|
|
9
|
+
this._transport = transport;
|
|
10
|
+
this._stack = [];
|
|
11
|
+
this._listeners = new Map();
|
|
12
|
+
this._sending = false;
|
|
13
|
+
|
|
14
|
+
this._transport.addListener(BATCHED_TRANSPORT_CHANNEL, stack => {
|
|
15
|
+
stack.forEach(entry => {
|
|
16
|
+
const [channel, args] = entry;
|
|
17
|
+
// server side the transport is the same EventEmitter instance
|
|
18
|
+
// for both state manager server and client, so channel might not exist
|
|
19
|
+
// one side or the other.
|
|
20
|
+
if (this._listeners.has(channel)) {
|
|
21
|
+
const callbacks = this._listeners.get(channel);
|
|
22
|
+
callbacks.forEach(callback => callback(...args));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
addListener(channel, callback) {
|
|
29
|
+
if (!this._listeners.has(channel)) {
|
|
30
|
+
this._listeners.set(channel, new Set());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const callbacks = this._listeners.get(channel);
|
|
34
|
+
callbacks.add(callback);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async emit(channel, ...args) {
|
|
38
|
+
this._stack.push([channel, args]);
|
|
39
|
+
|
|
40
|
+
if (!this._sending) {
|
|
41
|
+
this._sending = true;
|
|
42
|
+
this._sending = await false;
|
|
43
|
+
const stack = this._stack;
|
|
44
|
+
this._stack = [];
|
|
45
|
+
this._transport.emit(BATCHED_TRANSPORT_CHANNEL, stack);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
removeListener(channel, callback) {
|
|
50
|
+
if (!this._listeners.has(channel)) {
|
|
51
|
+
const callbacks = this._listeners.get(channel);
|
|
52
|
+
callbacks.delete(callback);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeAllListeners(channel = null) {
|
|
57
|
+
if (channel === null) {
|
|
58
|
+
this._listeners.clear();
|
|
59
|
+
} else if (this._listeners.has(channel)) {
|
|
60
|
+
const callbacks = this._listeners.get(channel);
|
|
61
|
+
callbacks.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default BatchedTransport;
|
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
import { idGenerator } from '@ircam/sc-utils';
|
|
2
2
|
|
|
3
|
-
const generateRequestId = idGenerator();
|
|
4
|
-
|
|
5
3
|
export default class PromiseStore {
|
|
6
4
|
constructor(name) {
|
|
7
5
|
this.name = name;
|
|
8
6
|
this.store = new Map();
|
|
7
|
+
this.generateRequestId = idGenerator();
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
add(resolve, reject, type) {
|
|
12
|
-
const reqId = generateRequestId.next().value;
|
|
11
|
+
const reqId = this.generateRequestId.next().value;
|
|
13
12
|
this.store.set(reqId, { resolve, reject, type });
|
|
14
13
|
|
|
15
14
|
return reqId;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
resolve(reqId, data) {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (this.store.has(reqId)) {
|
|
19
|
+
const { resolve } = this.store.get(reqId);
|
|
20
|
+
this.store.delete(reqId);
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
resolve(data);
|
|
23
|
+
} else {
|
|
24
|
+
throw new Error(`[${this.name}] cannot resolve request id (${reqId}), id does not exist`);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
reject(reqId, msg) {
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
if (this.store.has(reqId)) {
|
|
30
|
+
const { reject } = this.store.get(reqId);
|
|
31
|
+
this.store.delete(reqId);
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
reject(new Error(msg));
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error(`[${this.name}] cannot reject request id (${reqId}), id does not exist`);
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
// reject all pendeing request
|
|
@@ -70,16 +70,7 @@ class SharedStatePrivate {
|
|
|
70
70
|
let hasUpdates = false;
|
|
71
71
|
|
|
72
72
|
for (let name in updates) {
|
|
73
|
-
// from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal'
|
|
74
|
-
// cf. https://github.com/epoberezkin/fast-deep-equal
|
|
75
|
-
// therefore unchanged objects are not considered changed
|
|
76
|
-
// nor propagated anymore.
|
|
77
|
-
// until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider
|
|
78
|
-
// objects as dirty, because if the state is attached locally, we
|
|
79
|
-
// compare the Object instances instead of their values.
|
|
80
|
-
// @note - this should be made more robust but how?
|
|
81
73
|
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
82
|
-
|
|
83
74
|
// if `filterChange` is set to `false` we don't check if the value
|
|
84
75
|
// has been changed or not, it is always propagated to client states
|
|
85
76
|
const { event, filterChange } = this._parameters.getSchema(name);
|
|
@@ -96,25 +87,23 @@ class SharedStatePrivate {
|
|
|
96
87
|
}
|
|
97
88
|
|
|
98
89
|
if (hasUpdates) {
|
|
99
|
-
|
|
90
|
+
|
|
91
|
+
// Collection controller logic
|
|
100
92
|
if (isOwner && inCollection) {
|
|
101
|
-
// notify the
|
|
102
|
-
// no need to check for attached clients as it is not observable
|
|
93
|
+
// notify the collection controller back
|
|
103
94
|
client.transport.emit(
|
|
104
95
|
`${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
|
|
105
|
-
reqId, filteredUpdates, context
|
|
96
|
+
reqId, filteredUpdates, context,
|
|
106
97
|
);
|
|
107
98
|
|
|
108
99
|
// loop through all private states
|
|
109
|
-
for (let [
|
|
100
|
+
for (let [_stateId, state] of this._manager._sharedStatePrivateById) {
|
|
110
101
|
// pick all states with same schema name and not this
|
|
111
102
|
if (state !== this && this.schemaName === state.schemaName) {
|
|
112
103
|
// notify all attached clients except those who belong to a collection
|
|
113
104
|
// i.e. !isOwner && inCollection, they will be notified by their own
|
|
114
105
|
// collection controller
|
|
115
106
|
for (let [remoteId, clientInfos] of state._attachedClients) {
|
|
116
|
-
const { client, isOwner, inCollection } = clientInfos;
|
|
117
|
-
|
|
118
107
|
if (!clientInfos.isOwner && clientInfos.inCollection) {
|
|
119
108
|
continue;
|
|
120
109
|
} else {
|
|
@@ -122,7 +111,7 @@ class SharedStatePrivate {
|
|
|
122
111
|
|
|
123
112
|
peer.transport.emit(
|
|
124
113
|
`${UPDATE_NOTIFICATION}-${state.id}-${remoteId}`,
|
|
125
|
-
filteredUpdates, context
|
|
114
|
+
filteredUpdates, context,
|
|
126
115
|
);
|
|
127
116
|
}
|
|
128
117
|
}
|
|
@@ -131,7 +120,7 @@ class SharedStatePrivate {
|
|
|
131
120
|
} else {
|
|
132
121
|
|
|
133
122
|
// Normal case
|
|
134
|
-
|
|
123
|
+
//
|
|
135
124
|
// We need to handle cases where:
|
|
136
125
|
// - client state (client.id: 2) sends a request
|
|
137
126
|
// - server attached state (client.id: -1) spot a problem and overrides the value
|
|
@@ -146,14 +135,14 @@ class SharedStatePrivate {
|
|
|
146
135
|
// is synchronous it can break ordering if a subscription function makes
|
|
147
136
|
// itself an update in reaction to an update. Propagating to server last
|
|
148
137
|
// alllows to maintain network messages order consistent.
|
|
149
|
-
|
|
138
|
+
//
|
|
150
139
|
// @note - remoteId correspond to unique remote state id
|
|
151
140
|
|
|
152
141
|
// propagate RESPONSE to the client that originates the request if not the server
|
|
153
142
|
if (client.id !== -1) {
|
|
154
143
|
client.transport.emit(
|
|
155
144
|
`${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
|
|
156
|
-
reqId, filteredUpdates, context
|
|
145
|
+
reqId, filteredUpdates, context,
|
|
157
146
|
);
|
|
158
147
|
}
|
|
159
148
|
|
|
@@ -164,7 +153,7 @@ class SharedStatePrivate {
|
|
|
164
153
|
if (remoteId !== peerRemoteId && peer.id !== -1) {
|
|
165
154
|
peer.transport.emit(
|
|
166
155
|
`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
|
|
167
|
-
filteredUpdates, context
|
|
156
|
+
filteredUpdates, context,
|
|
168
157
|
);
|
|
169
158
|
}
|
|
170
159
|
}
|
|
@@ -173,7 +162,7 @@ class SharedStatePrivate {
|
|
|
173
162
|
if (client.id === -1) {
|
|
174
163
|
client.transport.emit(
|
|
175
164
|
`${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
|
|
176
|
-
reqId, filteredUpdates, context
|
|
165
|
+
reqId, filteredUpdates, context,
|
|
177
166
|
);
|
|
178
167
|
}
|
|
179
168
|
|
|
@@ -184,7 +173,7 @@ class SharedStatePrivate {
|
|
|
184
173
|
if (remoteId !== peerRemoteId && peer.id === -1) {
|
|
185
174
|
peer.transport.emit(
|
|
186
175
|
`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
|
|
187
|
-
filteredUpdates, context
|
|
176
|
+
filteredUpdates, context,
|
|
188
177
|
);
|
|
189
178
|
}
|
|
190
179
|
}
|
package/src/common/constants.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// id of the server when owner of a state
|
|
2
2
|
export const SERVER_ID = -1;
|
|
3
3
|
|
|
4
|
+
// batched transport channel
|
|
5
|
+
export const BATCHED_TRANSPORT_CHANNEL = 'b:t';
|
|
6
|
+
|
|
4
7
|
// shared states channels
|
|
5
8
|
export const CREATE_REQUEST = 's:c:req';
|
|
6
9
|
export const CREATE_RESPONSE = 's:c:res';
|
package/src/server/Sockets.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
2
|
import querystring from 'querystring';
|
|
6
|
-
import { WebSocketServer } from 'ws';
|
|
7
|
-
import WebSocket from 'ws';
|
|
3
|
+
import { default as WebSocket, WebSocketServer } from 'ws';
|
|
8
4
|
|
|
9
5
|
import Socket from './Socket.js';
|
|
6
|
+
import networkLatencyWorker from './audit-network-latency.worker.js';
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
// this crashes when bundling server to cjs module for Max, fallback to cjs worker
|
|
9
|
+
// const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Manager all {@link server.Socket} instances.
|
|
@@ -45,8 +43,9 @@ class Sockets {
|
|
|
45
43
|
*/
|
|
46
44
|
async start(server, config, onConnectionCallback) {
|
|
47
45
|
// init audit stuff for network latency estimation
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// @see note about __dirname
|
|
47
|
+
// const workerPathname = path.join(__dirname, 'audit-network-latency.worker.js');
|
|
48
|
+
this._latencyStatsWorker = new Worker(networkLatencyWorker, { eval: true });
|
|
50
49
|
this._auditState = await server.getAuditState();
|
|
51
50
|
|
|
52
51
|
this._auditState.onUpdate(updates => {
|
|
@@ -4,6 +4,7 @@ import { idGenerator, isString, isPlainObject } from '@ircam/sc-utils';
|
|
|
4
4
|
import clonedeep from 'lodash/cloneDeep.js';
|
|
5
5
|
|
|
6
6
|
import BaseStateManager from '../common/BaseStateManager.js';
|
|
7
|
+
import BatchedTransport from '../common/BatchedTransport.js';
|
|
7
8
|
import ParameterBag from '../common/ParameterBag.js';
|
|
8
9
|
import SharedStatePrivate from '../common/SharedStatePrivate.js';
|
|
9
10
|
import {
|
|
@@ -344,6 +345,8 @@ class StateManager extends BaseStateManager {
|
|
|
344
345
|
* @private
|
|
345
346
|
*/
|
|
346
347
|
addClient(nodeId, transport) {
|
|
348
|
+
transport = new BatchedTransport(transport);
|
|
349
|
+
|
|
347
350
|
const client = { id: nodeId, transport };
|
|
348
351
|
this._clientByNodeId.set(nodeId, client);
|
|
349
352
|
|
|
@@ -360,7 +363,7 @@ class StateManager extends BaseStateManager {
|
|
|
360
363
|
const remoteId = generateRemoteId.next().value;
|
|
361
364
|
const state = new SharedStatePrivate(stateId, schemaName, schema, this, initValues);
|
|
362
365
|
|
|
363
|
-
|
|
366
|
+
// attach client to the state as owner
|
|
364
367
|
const isOwner = true;
|
|
365
368
|
state._attachClient(remoteId, client, isOwner, inCollection);
|
|
366
369
|
|
|
@@ -371,7 +374,7 @@ class StateManager extends BaseStateManager {
|
|
|
371
374
|
|
|
372
375
|
client.transport.emit(
|
|
373
376
|
CREATE_RESPONSE,
|
|
374
|
-
reqId, stateId, remoteId, schemaName, schemaOption, currentValues
|
|
377
|
+
reqId, stateId, remoteId, schemaName, schemaOption, currentValues,
|
|
375
378
|
);
|
|
376
379
|
|
|
377
380
|
const isObservable = this[kIsObservableState](state);
|
|
@@ -393,7 +396,7 @@ class StateManager extends BaseStateManager {
|
|
|
393
396
|
|
|
394
397
|
client.transport.emit(CREATE_ERROR, reqId, msg);
|
|
395
398
|
}
|
|
396
|
-
}
|
|
399
|
+
},
|
|
397
400
|
);
|
|
398
401
|
|
|
399
402
|
// ---------------------------------------------
|
|
@@ -434,7 +437,7 @@ class StateManager extends BaseStateManager {
|
|
|
434
437
|
|
|
435
438
|
client.transport.emit(
|
|
436
439
|
ATTACH_RESPONSE,
|
|
437
|
-
reqId, state.id, remoteId, schemaName, schemaOption, currentValues
|
|
440
|
+
reqId, state.id, remoteId, schemaName, schemaOption, currentValues,
|
|
438
441
|
);
|
|
439
442
|
|
|
440
443
|
} else {
|
|
@@ -449,7 +452,7 @@ class StateManager extends BaseStateManager {
|
|
|
449
452
|
|
|
450
453
|
client.transport.emit(ATTACH_ERROR, reqId, msg);
|
|
451
454
|
}
|
|
452
|
-
}
|
|
455
|
+
},
|
|
453
456
|
);
|
|
454
457
|
|
|
455
458
|
// ---------------------------------------------
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// NOTICE: we use this syntax so that the server can be bundled to cjs
|
|
2
|
+
// with esbuild, so we can ship a cjs server bundle into Max.
|
|
3
|
+
// Should move back to regular esm module once Max has fixed its loader
|
|
4
|
+
export default `
|
|
5
|
+
const { parentPort } = require('node:worker_threads');
|
|
4
6
|
|
|
5
7
|
let stack = [];
|
|
6
8
|
let averageLatencyWindow = 5;
|
|
@@ -9,12 +11,21 @@ let averageLatencyPeriod = 2;
|
|
|
9
11
|
let intervalId = null;
|
|
10
12
|
let meanLatency = 0;
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
let getTime;
|
|
15
|
+
let inited = new Promise(async (resolve) => {
|
|
16
|
+
module = await import('@ircam/sc-utils');
|
|
17
|
+
getTime = module.getTime;
|
|
18
|
+
resolve(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
parentPort.on('message', async msg => {
|
|
13
22
|
switch (msg.type) {
|
|
14
23
|
case 'config': {
|
|
15
24
|
averageLatencyWindow = msg.value.averageLatencyWindow;
|
|
16
25
|
averageLatencyPeriod = msg.value.averageLatencyPeriod;
|
|
17
26
|
|
|
27
|
+
await inited;
|
|
28
|
+
|
|
18
29
|
// launch compute in its own loop so that the number of computation is
|
|
19
30
|
// decoupled from the number of connected clients
|
|
20
31
|
clearInterval(intervalId);
|
|
@@ -55,3 +66,4 @@ function computeAverageLatency() {
|
|
|
55
66
|
});
|
|
56
67
|
}
|
|
57
68
|
}
|
|
69
|
+
`;
|