-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWorkerFactory.js
More file actions
327 lines (296 loc) · 10.6 KB
/
WorkerFactory.js
File metadata and controls
327 lines (296 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/**
* Generalized postMessage interface
* It enables us to send messages to scripts executing in the same or a different thread
*/
class MessageInterface {
constructor() {
}
/**
* send a message to the object
* @param {Object} _
*/
postMessage(_) {
throw new Error("Interface Messageinterface not implemented");
}
/**
* set function to receive messages from the object
* @param {function(MessageEvent):void} _
*/
setOnMessage(_) {
throw new Error("Interface MessageInterface not implemented");
}
}
/**
* Wraps a Worker object to implement the generalized message interface
* it will allow communication with the wrapped web worker
* Works in tandem with SelfMessageInterface which provides communication in the opposite direction
*/
class WorkerMessageInterface extends MessageInterface {
/**
* @param {Worker} worker the worker object to wrap
*/
constructor(worker) {
super();
this.worker = worker;
}
/**
* @param {Object} message
*/
postMessage(message) {
this.worker.postMessage(message);
}
/**
* @param {function(MessageEvent):void} func
*/
setOnMessage(func) {
this.worker.onmessage = func;
}
}
/**
* Wraps the global self object and implements the generalized send message interface
* Used to communicate from the worker thread with the main thread
* Works together with WorkerMessageInterface to provide communication in the other direction
*/
class SelfMessageInterface extends MessageInterface {
constructor() {
super();
}
/**
* @param {Object} message
*/
postMessage(message) {
self.postMessage(message);
}
/**
* @param {function(MessageEvent):void} func
*/
setOnMessage(func) {
self.onmessage = func;
}
}
/**
* implmenets the generalized post message protocol for dynamicly loaded scripts
* used for communication between the main thread and the script
* works with FactoryMessageInterface for communication in the opposite direction
* Requires a FactoryMessageInterface to function completely, initialize with a call to setExternalMessageInterface
*/
class DynamicScriptMessageInterface extends MessageInterface {
constructor() {
super();
/**
* stores messages that are send before a FactoryMessageInterface has been set
* @type {Array<Object>}
*/
this.sendMessageBuffer = [];
/**
* stores messages that are received before a receive function has been set
* @type {Array<MessageEvent>}
*/
this.receivedMessageEventBuffer = [];
/**
* callback that will receive messages
* @type {(function(MessageEvent):void)|null}
*/
this.onMessageFunc = null;
/**
* reference to the FactoryMessageInterface linked to this one for communication with the script
* it's a weak reference to prevent cyclic reference counting causing a memory leak
* the main thread owns the dynamicly loaded script not the otherway around
* @type {WeakRef<FactoryMessageInterface>|null}
*/
this.externalMessageInterface = null;
}
/**
* @param {Object} message
*/
postMessage(message) {
if (this.externalMessageInterface) {
const localExternalMessageInterface = this.externalMessageInterface.deref();
if (!localExternalMessageInterface) {
throw new Error('externalMessageInterface could not be dereferenced');
}
const messageEvent = new MessageEvent('message', {data: message});
localExternalMessageInterface.onMessage(messageEvent);
} else {
this.sendMessageBuffer.push(message);
}
}
/**
* @param {function(MessageEvent):void} func
*/
setOnMessage(func) {
if (this.onMessageFunc) console.warn("Changing onMessage callback");
this.onMessageFunc = func;
// if there are messages stored in the buffer (messages send before configuring this call back) send them now (fifo)
for (let bufferedMessageEvent of this.receivedMessageEventBuffer) {
func(bufferedMessageEvent);
}
this.receivedMessageEventBuffer = [];
}
/**
* @param {MessageEvent} messageEvent
*/
onMessage(messageEvent) {
if (this.onMessageFunc) {
this.onMessageFunc(messageEvent);
} else {
this.receivedMessageEventBuffer.push(messageEvent);
}
}
/**
* @param {FactoryMessageInterface} messageInterface the other half of this communicationchannel
*/
setExternalMessageInterface(messageInterface) {
if (this.externalMessageInterface) console.warn("Changing externalMessageInterface");
this.externalMessageInterface = new WeakRef(messageInterface);
// if we have send/saved messages before this configuration was complete, send them now (fifo)
for (let bufferedMessage of this.sendMessageBuffer) {
const bufferedMessageEvent = new MessageEvent('message', {data: bufferedMessage});
messageInterface.onMessage(bufferedMessageEvent);
}
this.sendMessageBuffer = [];
}
}
/**
* communication interface for communicating from the dynamicly loaded script to the main thread
* before construction the following global variables should be set:
* self.dynamicScriptFactoryName
* self.dynamicScriptId
* we use these to pass information to the loaded script, this works since we load scripts synchroneously and they get executed before returning from the initiated load call
* after script loading is done these variables should not be used, since they will be reused for the next script we will load dynamicaly
*/
class FactoryMessageInterface extends MessageInterface {
/**
* @param {DynamicScriptFactory} factory
*/
constructor(factory) {
super();
/**
* @type {DynamicScriptMessageInterface|undefined}
*/
this.externalMessageInterface = factory.getMessageInterfaceById(factory.lastId);
if (!this.externalMessageInterface) {
throw new Error(`Factory doesn't have a DynamicScriptMessageInterface with id: ${factory.lastId}`);
}
// connect both classes to establish communication
this.externalMessageInterface.setExternalMessageInterface(this);
/**
* buffer for temporary storage in case messages are received while the callback isn't configured
* @type {Array<MessageEvent>}
*/
this.receivedMessageEventBuffer = [];
/**
* mesage received callback
* @type {(function(MessageEvent):void)|null};
*/
this.onMessageFunc = null;
}
/**
* @param {Object} message
*/
postMessage(message) {
const messageEvent = new MessageEvent('message', {data: message});
if (!this.externalMessageInterface) {
throw new Error('No externalMessageInterface');
}
this.externalMessageInterface.onMessage(messageEvent);
}
/**
* @param {function(MessageEvent):void} func
*/
setOnMessage(func) {
if (this.onMessageFunc) console.warn("Changing onMessage callback");
this.onMessageFunc = func;
// if there are stored messages send them (fifo)
for (let bufferedMessageEvent of this.receivedMessageEventBuffer) {
func(bufferedMessageEvent);
}
}
/**
* if we receive a mesage but there is no callback store the message for later
* @param {MessageEvent} messageEvent
*/
onMessage(messageEvent) {
if (this.onMessageFunc) {
this.onMessageFunc(messageEvent);
} else {
this.receivedMessageEventBuffer.push(messageEvent);
}
}
}
/**
* Factory class that loads scripts using either WebWorker or dynamic script loading strategies
*/
class WorkerFactory {
constructor() {
}
/**
* Creates a new script and returns a communication interface through which messages can be send
* @param {string} _scriptPath path to the script
* @param {boolean} _isModule wether the loaded script is a module or normal javascript
* @returns {MessageInterface} communication interface
*/
createWorker(_scriptPath, _isModule) {
throw new Error("Interface WorkerFactory not implemented");
}
}
/**
* WebWorker strategy for the worker factory
*/
class WebWorkerFactory extends WorkerFactory {
constructor() {
super();
}
/**
* @param {string} scriptPath path to the script
* @param {boolean} isModule wether the loaded script is a module or normal javascript
* @returns {MessageInterface} communication interface
*/
createWorker(scriptPath, isModule) {
const webWorker = new Worker(scriptPath, {type: isModule ? "module" : "classic"});
return new WorkerMessageInterface(webWorker);
}
}
/**
* dynamic script loading strategy for the WorkerFactory
*/
class DynamicScriptFactory extends WorkerFactory {
constructor() {
super();
/**
* @type {Map<number, DynamicScriptMessageInterface>}
*/
this.workers = new Map();
this.nextId = 1;
this.lastId = 0;
}
/**
* @param {string} scriptPath path to the script
* @param {boolean} isModule wether the loaded script is a module or normal javascript
* @returns {MessageInterface} communication interface
*/
createWorker(scriptPath, isModule) {
const scriptElem = document.createElement('script');
if (isModule) {
scriptElem.setAttribute('type', 'module');
}
scriptElem.setAttribute('src', scriptPath);
// set global variables to communicate connection point to scriptside FactoryMessageInterface
this.lastId = this.nextId;
const messageInterface = new DynamicScriptMessageInterface();
this.workers.set(this.nextId, messageInterface);
this.nextId++;
// start script execution
document.body.appendChild(scriptElem);
return messageInterface;
}
/**
* used by FactoryMessageInterface to find the main thread's DynamicScriptMessageInterface using the given id
* @param {number} id dynamicScriptId/unique number identifying the dynamically loaded script
* @returns {DynamicScriptMessageInterface | undefined}
*/
getMessageInterfaceById(id) {
return this.workers.get(id);
}
}
export {MessageInterface, WorkerMessageInterface, SelfMessageInterface, DynamicScriptMessageInterface, FactoryMessageInterface, WorkerFactory, WebWorkerFactory, DynamicScriptFactory};