@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 CHANGED
@@ -1,6 +1,10 @@
1
+ ## v4.0.0-alpha.17 - 16/12/2023
2
+
3
+ - Support bundling the server to cjs for Max externals
4
+
1
5
  ## v4.0.0-alpha.16 - 15/12/2023
2
6
 
3
- - improve StateCollection performances
7
+ - Improve StateCollection performances
4
8
 
5
9
  ## v4.0.0-alpha.15 - 13/12/2023
6
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soundworks/core",
3
- "version": "4.0.0-alpha.16",
3
+ "version": "4.0.0-alpha.17",
4
4
  "description": "Open-source creative coding framework for distributed applications based on Web technologies",
5
5
  "authors": [
6
6
  "Benjamin Matuszewski",
@@ -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
- this._promiseStore.resolve(reqId);
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
- this._promiseStore.reject(reqId, msg);
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
- this._promiseStore.resolve(reqId);
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
- this._promiseStore.reject(reqId, msg);
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 = this._promiseStore.add(resolve, reject, 'enter-context');
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 = this._promiseStore.add(resolve, reject, 'exit-context');
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
- const { resolve } = this.store.get(reqId);
20
- this.store.delete(reqId);
18
+ if (this.store.has(reqId)) {
19
+ const { resolve } = this.store.get(reqId);
20
+ this.store.delete(reqId);
21
21
 
22
- resolve(data);
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
- const { reject } = this.store.get(reqId);
27
- this.store.delete(reqId);
29
+ if (this.store.has(reqId)) {
30
+ const { reject } = this.store.get(reqId);
31
+ this.store.delete(reqId);
28
32
 
29
- reject(new Error(msg));
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
@@ -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
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
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
- const workerPathname = path.join(__dirname, 'audit-network-latency.worker.js');
49
- this._latencyStatsWorker = new Worker(workerPathname);
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
- import { parentPort } from 'node:worker_threads';
2
-
3
- import { getTime } from '@ircam/sc-utils';
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
- parentPort.on('message', msg => {
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
+ `;