-
Notifications
You must be signed in to change notification settings - Fork 5
/
worker-builder.html
336 lines (292 loc) · 10.8 KB
/
worker-builder.html
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
328
329
330
331
332
333
334
335
<!--
CUSTOM ELEMENT: worker-builder
BY: DM
Use it like this:
<script is='worker-builder' type='javascript/worker' id="worker_factory">
"use strict"
var n=0;
function do_it(v){
exec_main('done_it', v + n, v+1000+n);
n++;
}
function do_it_other(a, b, c_name, arrays){
var c = arrays[c_name];
for(var kk=0; kk<did; kk++)
c[kk] = Math.pow(kk, a) + b;
exec_main_b("done_it_other", 'arr_result', "does that look good?", {arr_result: c});
// note that c is "std::move"d off the worker thread
// i.e. if you wanted to keep it, you need to send a copy, rather
// than the original. Google tansferable objects for details.
}
</script>
<script>
var worker_factory = document.getElementById('worker_factory');
var Widget = function(){
this.worker = worker_factory.create_for(this);
return this;
}
Widget.prototype.done_it = function(a, b){
console.log("worker says a=" + a + ", b=" + b);
}
Widget.prototype.try_it = function(v){
this.worker.do_it(v);
}
Widget.prototype.try_it_other = function(a, b, c){
this.worker.do_it_other(a, b, 'arr_c', {arr_c: c});
}
Worker.prototype.done_it_other = function(arr_name, msg, arrays){
var arr = arrays[arr_name];
console.log(msg);
console.dir(arr);
}
var my_widget = new Widget();
var my_second_widget = new Widget();
my_widget.exec('try_it', 41); // async console log: worker says a=41, b=1041
my_widget.exec('try_it', 41); // async console log: worker says a=42, b=1042
my_second_widget.exec('try_it', 41); // async console log: worker says a=41, b=1041
my_widget.exec('try_it', 41); // async console log: worker says a=43, b=1043
</script>
Note that you *must* provide the type='javascript/worker', otherwise the content will be treated
as a normal script. See http://www.html5rocks.com/en/tutorials/workers/.
Also, note that the code is compiled once, but you can run multiple copies (i.e. multiple threads),
each one will report back to it's registered Widget.
For sending typedarrays/buffers to/from threads use the exec_b rather than exec functions.
The last argument should be a "flat" object that maps from array "names" to arrays.
This object will be reconstruced on the other end, i.e. the object will have its names
and exact array types reinstated, and it will be provided as the last argument to the function
on the other end, thus the recipient can lookup arrays by name in the object.
When defining a worker within a dom-module, use the following syntax...
<dom-module id="my-widget">
<template> </template>
<script is='worker-builder' type='javascript/worker'>
// worker code goes here
</script>
<script>
Polymer({
is: 'my-widget',
ready: function(){
var worker_factory = document._currentScript.previousElementSibling; // drop the underscore if not using webcomponents polyfill
this.worker = worker_factory.create_for(this);
}
})
</script>
</dom-module>
If you don't want to use previousElementSibling, you could give the script elemenet and id
and then use Polymer.DomModule.import('my-widget','#your_chosen_id') to access it.
-->
<link rel="import" href="bower_components/polymer/polymer.html">
<script id="worker_extra" type="javascript/worker">
// issues requests from main thread to functions on worker thread
self.onmessage = function(e){
if(e.data.foo && self[e.data.foo]){
if(e.data.bufs){
var buffers = {};
for(var ii=0; ii<e.data.bufs.length; ii++){
var item = e.data.bufs[ii];
buffers[item.name] = item.constructor === "ArrayBuffer" ? item.buffer : new self[item.constructor](item.buffer);
}
for(var ii=0; ii<e.data.undefineds.length; ii++){
buffers[e.data.undefineds[ii]] = undefined;
}
e.data.args.push(buffers);
}
self[e.data.foo].apply(null, e.data.args);
} else if (e.data.foo === '_ports'){
var a = {};
for(var ii=0; ii< e.ports.length; ii++){
a[e.data.args[ii]] = e.ports[ii];
}
self.got_ports(a);
}else {
throw "worker '" + self._title + "' got bad message: " + (e.data && e.data.foo ? e.data.foo : JSON.stringify(e.data));
}
};
// Send back to main
var exec_main = function(/*arguments*/){
self.postMessage({
foo: arguments[0],
args: Array.prototype.slice.call(arguments, 1)
});
};
// Send back to main with buffers
var exec_main_b = function(/*arguments*/){
var msg = {
foo: arguments[0],
args: Array.prototype.slice.call(arguments,1, arguments.length-1),
bufs: []
};
var x = arguments[arguments.length-1] || {};
var buffers = [];
// x is a map from labels to typedarrays
for(var k in x)if(x.hasOwnProperty(k)){
buffers.push(x[k].buffer);
msg.bufs.push({
buffer: x[k].buffer,
name: k,
constructor: x[k].constructor.name
});
}
self.postMessage(msg, buffers);
};
// Add basic console support
var console = {
log: function(){
self.postMessage({
foo:'console.log',
args: Array.prototype.slice.call(arguments, 0)
});
}
};
// typed array debug function, from worker thread, use as:
// set_array_on_main_window(my_int16_array, "thing")
// This will set window.thing as a copy of my_int16_array.
// Works for any array with a .constructor property which can be found
// by name on the window in the main thread.
// see also exec_b
var set_array_on_main_window = function(x, name){
var buffer = x.buffer.slice(0);
self.postMessage({
foo: 'set_array_on_main_window',
args: [buffer, name, x.constructor.name, [buffer]]
});
};
// Add setImmediate and clearImmediate.
// see https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
// and https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
// this version doesn't bother accepting args.
(function(){
var last_task_h=0;
var tasks = {};
self._immediate_channel = new MessageChannel();
_immediate_channel.port1.onmessage = function(e) {
var h = e.data;
try {
tasks[h] && tasks[h]();
} finally {
clearImmediate(h);
}
}
self.setImmediate = function(cb) {
tasks[++last_task_h] = cb;
_immediate_channel.port2.postMessage(last_task_h);
return last_task_h;
}
self.clearImmediate = function(h){
delete tasks[h];
}
})();
// often helpful to have when doing compute stuff
var endian = function(){
var b = new ArrayBuffer(2);
(new DataView(b)).setInt16(0,256,true);
return (new Int16Array(b))[0] == 256? 'L' : 'B';
}();
// call created user funciton if it exists
self.created && self.created();
</script>
<script>
"use strict";
var worker_extra = document.currentScript.previousElementSibling.textContent; // note polyfilled as _currentScript in webcomponents
var BuiltWorker = function(url, title, listener){
this._worker = new Worker(url);
this._listener = listener;
this._title = title;
this._worker.onmessage = this._onmessage.bind(this);
return this;
}
BuiltWorker.prototype.exec = function(/*arguments*/){
this._worker.postMessage({
foo: arguments[0],
args: Array.prototype.slice.call(arguments, 1)
})
}
BuiltWorker.prototype.exec_b = function(/*arguments*/){
var msg = {
foo: arguments[0],
args: Array.prototype.slice.call(arguments, 1, arguments.length-1),
bufs: [],
undefineds: []
};
var x = arguments[arguments.length-1] || {};
var buffers = [];
// x is a map from labels to typedarrays
for(var k in x)if(x.hasOwnProperty(k)){
if(x[k] && x[k].buffer){
buffers.push(x[k].buffer);
msg.bufs.push({
buffer: x[k].buffer,
name: k,
constructor: x[k].constructor.name
});
} else {
msg.undefineds.push(k);
}
}
this._worker.postMessage(msg, buffers);
}
BuiltWorker.prototype._onmessage = function(e){
if(!e.data || !e.data.foo)
throw "bad message recieved on main thread from built worker '" + this._title + "'."
if(e.data.bufs){
var buffers = {};
for(var ii=0; ii<e.data.bufs.length; ii++){
var item = e.data.bufs[ii];
buffers[item.name] = new window[item.constructor](item.buffer);
}
e.data.args.push(buffers);
}
var foo = e.data.foo;
try{
this._listener[foo].apply(this._listener, e.data.args);
}catch(err){
if(foo === "console.log"){
console.log.apply(console, e.data.args);
} else if(foo === "set_array_on_main_window"){
window[e.data.args[1]] = new window[e.data.args[2]](e.data.args[0]);
} else {
throw err;
}
}
}
BuiltWorker.prototype.send_ports = function(a){
// a is a map from port name to port,
// i.e. analgous to the last arg in exec_b.
var ports = [];
var names = [];
for(var k in a){
ports.push(a[k]);
names.push(k);
}
this._worker.postMessage({
foo: '_ports',
args: names,
}, ports);
}
Polymer({
is:'worker-builder',
extends: 'script',
properties: {
title: {
type: String,
value: "Untitled"
},
url: {
type: String,
value: ""
},
},
observers: [ ],
ready: function(){
var user_code = this.root.textContent;
var full_code = "/* ===================\n\n" + this.title + " (worker constructed with worker-builder)" +
"\n\n =================== */\n\n\n" + user_code +
"\n\n/* ======================================================== */\n" +
"self._title='" + this.title + "';\n\n" + worker_extra;
this.set('url', window.URL.createObjectURL(new Blob([full_code],
{type:'text/javascript'})));
},
create_for: function(that){
return new BuiltWorker(this.url, this.title, that);
},
});
</script>