Skip to content

Commit 41ecdb0

Browse files
committedAug 11, 2021
Tests for webworker
1 parent 40015c1 commit 41ecdb0

10 files changed

+195
-91
lines changed
 

‎.github/workflows/nodejs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99

1010
strategy:
1111
matrix:
12-
node-version: [10.x, 12.x]
12+
node-version: [12.x, 14.x, 16.x]
1313

1414
steps:
1515
- uses: actions/checkout@v2

‎babel.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
}
1212
]
1313
],
14-
"plugins": ["babel-plugin-add-import-extension"]
14+
"plugins": ["babel-plugin-add-import-extension", "babel-plugin-transform-import-meta"]
1515
}

‎package-lock.json

+22-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"babel-jest": "^26.6.3",
3737
"babel-loader": "^8.2.2",
3838
"babel-plugin-add-import-extension": "^1.6.0",
39+
"babel-plugin-transform-import-meta": "^2.0.0",
3940
"clean-webpack-plugin": "^2.0.2",
4041
"css-loader": "^6.2.0",
4142
"expose-loader": "^3.0.0",
@@ -47,7 +48,7 @@
4748
"webpack-cli": "^4.7.2"
4849
},
4950
"dependencies": {
50-
"3vl": "^0.3.5",
51+
"3vl": "^1.0.0",
5152
"babel-polyfill": "^6.26.0",
5253
"dagre": "^0.8.5",
5354
"elkjs": "^0.7.1",
@@ -58,7 +59,8 @@
5859
"jquery-ui": "^1.12.1",
5960
"regenerator-runtime": "^0.13.9",
6061
"resize-observer-polyfill": "^1.5.1",
61-
"wavecanvas": "^0.2.5"
62+
"wavecanvas": "^1.0.0",
63+
"web-worker": "^1.0.0"
6264
},
6365
"homepage": "https://github.com/tilk/digitaljs",
6466
"repository": {

‎src/circuit.mjs

+22-11
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ export function getCellType(tp) {
6666
}
6767

6868
export class HeadlessCircuit {
69-
constructor(data, {cellsNamespace = {}, engine = SynchEngine} = {}) {
69+
constructor(data, {cellsNamespace = {}, engine = SynchEngine, engineOptions = {}} = {}) {
7070
this._cells = Object.assign(cells, cellsNamespace);
7171
this._display3vl = new Display3vl();
7272
this._display3vl.addDisplay(new help.Display3vlASCII());
7373
this._graph = this._makeGraph(data, data.subcircuits);
74-
this._engine = new engine(this._graph, this._cells);
74+
engineOptions.cells = this._cells;
75+
this._engine = new engine(this._graph, engineOptions);
7576
this.makeLabelIndex();
7677
this.listenTo(this._engine, 'postUpdateGates', (tick, count) => {
7778
this.trigger('postUpdateGates', tick, count);
@@ -170,11 +171,14 @@ export class HeadlessCircuit {
170171
if (laid_out) graph.set('laid_out', true);
171172
return graph;
172173
}
173-
updateGatesNext() {
174-
this._engine.updateGatesNext();
174+
updateGatesNext(opts) {
175+
return this._engine.updateGatesNext(opts);
175176
}
176-
updateGates() {
177-
this._engine.updateGates();
177+
updateGates(opts) {
178+
return this._engine.updateGates(opts);
179+
}
180+
synchronize() {
181+
return this._engine.synchronize();
178182
}
179183
get hasPendingEvents() {
180184
return this._engine.hasPendingEvents;
@@ -185,15 +189,15 @@ export class HeadlessCircuit {
185189
start() {
186190
if (this.hasWarnings())
187191
return; //todo: print/show error
188-
this._engine.start();
192+
return this._engine.start();
189193
}
190194
startFast() {
191195
if (this.hasWarnings())
192196
return; //todo: print/show error
193-
this._engine.startFast();
197+
return this._engine.startFast();
194198
}
195-
stop() {
196-
this._engine.stop();
199+
stop(opts) {
200+
return this._engine.stop(opts);
197201
}
198202
get interval() {
199203
return this._engine.interval;
@@ -229,7 +233,8 @@ export class HeadlessCircuit {
229233
devices: {},
230234
subcircuits: {},
231235
inputs: {},
232-
outputs: {}
236+
outputs: {},
237+
graph
233238
};
234239
for (const elem of graph.getElements()) {
235240
if (elem.has('net')) {
@@ -311,6 +316,12 @@ export class HeadlessCircuit {
311316
unmonitor(monitorId) {
312317
this._engine.unmonitor(monitorId);
313318
}
319+
observeGraph(path = []) {
320+
this._engine.observeGraph(this.getLabelIndex(path).graph);
321+
}
322+
unobserveGraph(path = []) {
323+
this._engine.observeGraph(this.getLabelIndex(path).graph);
324+
}
314325
};
315326

316327
_.extend(HeadlessCircuit.prototype, Backbone.Events);

‎src/engines/browsersynch.mjs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import { SynchEngine } from './synch.mjs';
33

44
export class BrowserSynchEngine extends SynchEngine {
5-
constructor(graph, cells) {
6-
super(graph, cells);
5+
constructor(graph, opts) {
6+
super(graph, opts);
77
this._interval_ms = 10;
88
this._interval = null;
99
this._idle = null;
@@ -37,6 +37,7 @@ export class BrowserSynchEngine extends SynchEngine {
3737
this._idle = null;
3838
}
3939
this.trigger('changeRunning');
40+
return Promise.resolve();
4041
}
4142
get interval() {
4243
return this._interval_ms;

‎src/engines/synch.mjs

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as help from '../help.mjs';
55
import { BaseEngine } from './base.mjs';
66

77
export class SynchEngine extends BaseEngine {
8-
constructor(graph, cells) {
8+
constructor(graph, {cells}) {
99
super(graph);
1010
this._queue = new Map();
1111
this._pq = new FastPriorityQueue();
@@ -102,24 +102,28 @@ export class SynchEngine extends BaseEngine {
102102
this._queue.delete(k);
103103
this._tick = (k + 1) | 0;
104104
this.trigger('postUpdateGates', k, count);
105-
return count;
105+
return Promise.resolve(count);
106106
}
107107
updateGates() {
108108
if (this._pq.peek() == this._tick) return this.updateGatesNext();
109109
else {
110110
const k = this._tick | 0;
111111
this._tick = (k + 1) | 0;
112112
this.trigger('postUpdateGates', k, 0);
113-
return 0;
113+
return Promise.resolve(0);
114114
}
115115
}
116+
synchronize() {
117+
return Promise.resolve();
118+
}
116119
start() {
117120
throw new Error("start() not supported");
118121
}
119122
startFast() {
120123
throw new Error("startFast() not supported");
121124
}
122125
stop() {
126+
return Promise.resolve()
123127
}
124128
get interval() {
125129
throw new Error("interval not supported");

‎src/engines/worker-worker.mjs

+36-10
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,30 @@ class WorkerEngineWorker {
162162
interval(ms) {
163163
this._interval = ms;
164164
}
165-
updateGates() {
166-
if (this._pq.peek() == this._tick) this.updateGatesNext();
165+
updateGates(reqid, sendUpdates) {
166+
const count = this._updateGates();
167+
if (sendUpdates) this._sendUpdates();
168+
this._sendAck(reqid, count);
169+
}
170+
_updateGates() {
171+
if (this._pq.peek() == this._tick) return this._updateGatesNext();
167172
else {
168173
const k = this._tick | 0;
169174
this._tick = (k + 1) | 0;
175+
return 0;
170176
}
171177
}
172-
updateGatesNext() {
178+
updateGatesNext(reqid, sendUpdates) {
179+
const count = this._updateGatesNext();
180+
if (sendUpdates) this._sendUpdates();
181+
this._sendAck(reqid, count);
182+
}
183+
_updateGatesNext() {
173184
const k = this._pq.poll() | 0;
174185
console.assert(k >= this._tick);
175186
this._tick = k;
176187
const q = this._queue.get(k);
188+
let count = 0;
177189
while (q.size) {
178190
const [gate, args] = q.entries().next().value;
179191
q.delete(gate);
@@ -186,25 +198,36 @@ class WorkerEngineWorker {
186198
this._enqueue(gate);
187199
}
188200
this._setGateOutputSignals(gate, newOutputs);
201+
count++;
189202
}
190203
this._queue.delete(k);
191204
this._tick = (k + 1) | 0;
205+
return count;
206+
}
207+
ping(reqid, sendUpdates) {
208+
if (sendUpdates) this._sendUpdates();
209+
this._sendAck(reqid);
192210
}
193211
start() {
194-
this.stop();
212+
this._stop();
195213
this._updater = setInterval(() => {
196-
this.updateGates();
214+
this._updateGates();
197215
}, this._interval);
198216
}
199217
startFast() {
200-
this.stop();
218+
this._stop();
201219
this._updater = setInterval(() => {
202220
const startTime = Date.now();
203221
while (Date.now() - startTime < 10 && this._hasPendingEvents() && this._updater)
204-
this.updateGatesNext();
222+
this._updateGatesNext();
205223
}, 10);
206224
}
207-
stop() {
225+
stop(reqid, sendUpdates) {
226+
this._stop();
227+
if (sendUpdates) this._sendUpdates();
228+
this._sendAck(reqid);
229+
}
230+
_stop() {
208231
if (this._updater) {
209232
clearInterval(this._updater);
210233
this._updater = null;
@@ -326,7 +349,7 @@ class WorkerEngineWorker {
326349
triggered = triggerValues.some((triggerValue) => sig.eq(triggerValue));
327350
if (triggered) {
328351
postMessage({ type: 'monitorValue', args: [monitorId, this._tick, sig, stopOnTrigger] });
329-
if (stopOnTrigger) this.stop();
352+
if (stopOnTrigger) this._stop();
330353
}
331354
}
332355
}
@@ -394,14 +417,17 @@ class WorkerEngineWorker {
394417
this._toUpdateParam = new Map();
395418
}
396419
}
420+
_sendAck(reqid, response) {
421+
postMessage({ type: 'ack', args: [reqid, response] });
422+
}
397423
_hasPendingEvents() {
398424
return this._queue.size > 0;
399425
}
400426
};
401427

402428
const worker = new WorkerEngineWorker();
403429

404-
onmessage = (e) => {
430+
self.onmessage = (e) => {
405431
const msg = e.data;
406432
if ('arg' in msg)
407433
worker[msg.type](msg.arg);

‎src/engines/worker.mjs

+36-8
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@ import _ from 'lodash';
33
import { BaseEngine } from './base.mjs';
44
import { Vector3vl } from '3vl';
55
import * as cells from '../cells.mjs';
6+
import Worker from 'web-worker';
67

78
export class WorkerEngine extends BaseEngine {
8-
constructor(graph) {
9+
constructor(graph, { workerURL }) {
910
super(graph);
1011
this._running = false;
1112
this._tickCache = 0;
1213
this._pendingEventsCache = false;
1314
this._observers = Object.create(null);
1415
this._graphs = Object.create(null);
1516
this._monitors = Object.create(null);
17+
this._promises = Object.create(null);
1618
this._uniqueCounter = 0;
17-
this._worker = new Worker(new URL('./worker-worker.mjs', import.meta.url));
19+
this._worker = workerURL ? new Worker(workerURL) : new Worker(new URL('./worker-worker.mjs', import.meta.url));
1820
this._worker.onmessage = (e) => this._handleMessage(e.data);
1921
this.interval = 10;
2022
this._addGraph(this._graph);
@@ -103,15 +105,24 @@ export class WorkerEngine extends BaseEngine {
103105
shutdown() {
104106
this._worker.terminate();
105107
}
106-
updateGatesNext() {
108+
synchronize() {
109+
const [reqid, promise] = this._generatePromise();
110+
this._worker.postMessage({ type: 'ping', args: [reqid, true] });
111+
return promise;
112+
}
113+
updateGatesNext({ synchronous = false } = {}) {
107114
if (this._running)
108115
throw new Error("updateGatesNext while running");
109-
this._worker.postMessage({ type: 'updateGatesNext' });
116+
const [reqid, promise] = this._generatePromise();
117+
this._worker.postMessage({ type: 'updateGatesNext', args: [reqid, synchronous] });
118+
return promise;
110119
}
111-
updateGates() {
120+
updateGates({ synchronous = false } = {}) {
112121
if (this._running)
113122
throw new Error("updateGates while running");
114-
this._worker.postMessage({ type: 'updateGates' });
123+
const [reqid, promise] = this._generatePromise();
124+
this._worker.postMessage({ type: 'updateGates', args: [reqid, synchronous] });
125+
return promise;
115126
}
116127
start() {
117128
if (this.running)
@@ -127,11 +138,13 @@ export class WorkerEngine extends BaseEngine {
127138
this._running = 'fast';
128139
this.trigger('changeRunning');
129140
}
130-
stop() {
141+
stop({ synchronous = false } = {}) {
131142
if (!this._running) return;
132-
this._worker.postMessage({ type: 'stop' });
143+
const [reqid, promise] = this._generatePromise();
144+
this._worker.postMessage({ type: 'stop', args: [reqid, synchronous] });
133145
this._running = false;
134146
this.trigger('changeRunning');
147+
return promise;
135148
}
136149
get interval() {
137150
return this._interval;
@@ -219,6 +232,9 @@ export class WorkerEngine extends BaseEngine {
219232
this._worker.postMessage({ type: this._running == 'fast' ? 'startFast' : 'start' });
220233
}
221234
}
235+
_handle_ack(reqid, response) {
236+
this._resolvePromise(reqid, response);
237+
}
222238
_findGateByIds(graphId, gateId) {
223239
const graph = this._graphs[graphId];
224240
if (graph === undefined) return undefined;
@@ -227,5 +243,17 @@ export class WorkerEngine extends BaseEngine {
227243
_generateUniqueId() {
228244
return this._uniqueCounter++;
229245
}
246+
_generatePromise() {
247+
const reqid = this._generateUniqueId();
248+
return [reqid, new Promise((resolve) => { this._promises[reqid] = resolve; })];
249+
}
250+
_resolvePromise(reqid, value) {
251+
if (!this._promises[reqid]) {
252+
console.warn("Missing promise", reqid);
253+
return;
254+
}
255+
this._promises[reqid](value);
256+
delete this._promises[reqid];
257+
}
230258
};
231259

‎tests/index.test.mjs

+63-46
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.