@soundworks/core 4.0.0-alpha.16 → 4.0.0-alpha.17
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 +4 -0
- package/src/common/BaseSharedStateCollection.js +2 -3
- package/src/common/PromiseStore.js +16 -9
- package/src/server/Sockets.js +8 -4
- 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) {
|
|
@@ -320,6 +320,10 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
320
320
|
* const updates = await state.set({ myParam: Math.random() });
|
|
321
321
|
*/
|
|
322
322
|
async set(updates, context = null) {
|
|
323
|
+
if (this._detached) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
323
327
|
if (!isPlainObject(updates)) {
|
|
324
328
|
throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as first parameter`);
|
|
325
329
|
}
|
|
@@ -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,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
|
package/src/server/Sockets.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
+
// import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
5
|
import querystring from 'querystring';
|
|
6
6
|
import { WebSocketServer } from 'ws';
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
8
|
|
|
9
9
|
import Socket from './Socket.js';
|
|
10
|
+
import networkLatencyWorker from './audit-network-latency.worker.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
// this crashes when bundling server to cjs module for Max, fallback to
|
|
13
|
+
// string worker for now
|
|
14
|
+
// const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Manager all {@link server.Socket} instances.
|
|
@@ -45,8 +48,9 @@ class Sockets {
|
|
|
45
48
|
*/
|
|
46
49
|
async start(server, config, onConnectionCallback) {
|
|
47
50
|
// init audit stuff for network latency estimation
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
// @see note about __dirname
|
|
52
|
+
// const workerPathname = path.join(__dirname, 'audit-network-latency.worker.js');
|
|
53
|
+
this._latencyStatsWorker = new Worker(networkLatencyWorker, { eval: true });
|
|
50
54
|
this._auditState = await server.getAuditState();
|
|
51
55
|
|
|
52
56
|
this._auditState.onUpdate(updates => {
|
|
@@ -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
|
+
`;
|