Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/proxyClient.js
4128 views
1
/**
2
* @license
3
* Copyright 2013 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
/*
8
* Proxy events/work to/from an emscripen worker built
9
* with PROXY_TO_WORKER. This code runs on the main
10
* thread and is not part of the main emscripten output
11
* file.
12
*/
13
14
#if !PROXY_TO_WORKER
15
#error "proxyClient.js should only be included in PROXY_TO_WORKER mode"
16
#endif
17
18
#if ENVIRONMENT_MAY_BE_NODE
19
var ENVIRONMENT_IS_NODE = {{{ nodeDetectionCode() }}};
20
if (ENVIRONMENT_IS_NODE) {
21
var NodeWorker = require('worker_threads').Worker;
22
global.Worker = function(url, options) {
23
// Special handling for `data:` URL argument, to match the behaviour
24
// of the Web API.
25
if (typeof url == 'string' && url.startsWith('data:')) {
26
#if EXPORT_ES6
27
// worker_threads always assume data URLs are ES6 modules
28
url = new URL(url);
29
#else
30
// For class modules we decode the data URL and use `eval: true`.
31
url = Buffer.from(url.split(",")[1], 'base64').toString();
32
options ||= {}
33
options.eval = true;
34
#endif
35
}
36
return new NodeWorker(url, options);
37
}
38
var Module = Module || {}
39
} else
40
#endif
41
if (typeof Module == 'undefined') {
42
console.warn('no Module object defined - cannot proxy canvas rendering and input events, etc.');
43
Module = {
44
canvas: {
45
addEventListener: () => {},
46
getBoundingClientRect: () => ({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0 }),
47
},
48
};
49
}
50
51
if (!Module.hasOwnProperty('print')) {
52
Module['print'] = (x) => console.log(x);
53
}
54
55
if (!Module.hasOwnProperty('printErr')) {
56
Module['printErr'] = (x) => console.error(x);
57
}
58
59
// utils
60
61
function FPSTracker(text) {
62
var last = 0;
63
var mean = 0;
64
var counter = 0;
65
this.tick = () => {
66
var now = Date.now();
67
if (last > 0) {
68
var diff = now - last;
69
mean = 0.99*mean + 0.01*diff;
70
if (counter++ === 60) {
71
counter = 0;
72
dump(text + ' fps: ' + (1000/mean).toFixed(2) + '\n');
73
}
74
}
75
last = now;
76
};
77
}
78
79
/*
80
function GenericTracker(text) {
81
var mean = 0;
82
var counter = 0;
83
this.tick = (value) => {
84
mean = 0.99*mean + 0.01*value;
85
if (counter++ === 60) {
86
counter = 0;
87
dump(text + ': ' + (mean).toFixed(2) + '\n');
88
}
89
};
90
}
91
*/
92
93
// render
94
95
var renderFrameData = null;
96
97
function renderFrame() {
98
var dst = Module.canvasData.data;
99
if (dst.set) {
100
dst.set(renderFrameData);
101
} else {
102
for (var i = 0; i < renderFrameData.length; i++) {
103
dst[i] = renderFrameData[i];
104
}
105
}
106
Module['ctx'].putImageData(Module.canvasData, 0, 0);
107
renderFrameData = null;
108
}
109
110
/*
111
(() => {
112
var trueRAF = window.requestAnimationFrame;
113
var tracker = new FPSTracker('client');
114
window.requestAnimationFrame = (func) => {
115
trueRAF(() => {
116
tracker.tick();
117
func();
118
});
119
}
120
})();
121
*/
122
123
// end render
124
125
// IDBStore
126
127
#include "IDBStore.js"
128
129
// Frame throttling
130
131
var frameId = 0;
132
133
// Worker
134
135
var filename;
136
filename ||= '<<< filename >>>';
137
138
var worker = new Worker(filename);
139
140
#if ENVIRONMENT_MAY_BE_NODE
141
if (ENVIRONMENT_IS_NODE) {
142
worker.postMessage({target: 'worker-init'});
143
} else {
144
#endif
145
WebGLClient.prefetch();
146
147
setTimeout(() => {
148
worker.postMessage({
149
target: 'worker-init',
150
width: Module['canvas'].width,
151
height: Module['canvas'].height,
152
boundingClientRect: cloneObject(Module['canvas'].getBoundingClientRect()),
153
URL: document.URL,
154
currentScriptUrl: filename,
155
preMain: true });
156
}, 0); // delay til next frame, to make sure html is ready
157
#if ENVIRONMENT_MAY_BE_NODE
158
}
159
#endif
160
161
var workerResponded = false;
162
163
worker.onmessage = (event) => {
164
//dump('\nclient got ' + JSON.stringify(event.data).slice(0, 150) + '\n');
165
if (!workerResponded) {
166
workerResponded = true;
167
Module.setStatus?.('');
168
}
169
170
var data = event.data;
171
switch (data.target) {
172
case 'stdout': {
173
Module['print'](data.content);
174
break;
175
}
176
case 'stderr': {
177
Module['printErr'](data.content);
178
break;
179
}
180
case 'window': {
181
window[data.method]();
182
break;
183
}
184
case 'canvas': {
185
switch (data.op) {
186
case 'getContext': {
187
Module['ctx'] = Module['canvas'].getContext(data.type, data.attributes);
188
if (data.type !== '2d') {
189
// possible GL_DEBUG entry point: Module['ctx'] = wrapDebugGL(Module['ctx']);
190
Module.glClient = new WebGLClient();
191
}
192
break;
193
}
194
case 'resize': {
195
Module['canvas'].width = data.width;
196
Module['canvas'].height = data.height;
197
if (Module['ctx']?.getImageData) Module.canvasData = Module['ctx'].getImageData(0, 0, data.width, data.height);
198
worker.postMessage({ target: 'canvas', boundingClientRect: cloneObject(Module['canvas'].getBoundingClientRect()) });
199
break;
200
}
201
case 'render': {
202
if (renderFrameData) {
203
// previous image was not rendered yet, just update image
204
renderFrameData = data.image.data;
205
} else {
206
// previous image was rendered so update image and request another frame
207
renderFrameData = data.image.data;
208
window.requestAnimationFrame(renderFrame);
209
}
210
break;
211
}
212
case 'setObjectProperty': {
213
Module['canvas'][data.object][data.property] = data.value;
214
break;
215
}
216
default: throw 'eh?';
217
}
218
break;
219
}
220
case 'gl': {
221
Module.glClient.onmessage(data);
222
break;
223
}
224
case 'tick': {
225
frameId = data.id;
226
worker.postMessage({ target: 'tock', id: frameId });
227
break;
228
}
229
case 'Image': {
230
assert(data.method === 'src');
231
var img = new Image();
232
img.onload = () => {
233
assert(img.complete);
234
var canvas = document.createElement('canvas');
235
canvas.width = img.width;
236
canvas.height = img.height;
237
var ctx = canvas.getContext('2d');
238
ctx.drawImage(img, 0, 0);
239
var imageData = ctx.getImageData(0, 0, img.width, img.height);
240
worker.postMessage({ target: 'Image', method: 'onload', id: data.id, width: img.width, height: img.height, data: imageData.data, preMain: true });
241
};
242
img.onerror = () => {
243
worker.postMessage({ target: 'Image', method: 'onerror', id: data.id, preMain: true });
244
};
245
img.src = data.src;
246
break;
247
}
248
case 'IDBStore': {
249
switch (data.method) {
250
case 'loadBlob': {
251
IDBStore.getFile(data.db, data.id, (error, blob) => {
252
worker.postMessage({
253
target: 'IDBStore',
254
method: 'response',
255
blob: error ? null : blob
256
});
257
});
258
break;
259
}
260
case 'storeBlob': {
261
IDBStore.setFile(data.db, data.id, data.blob, (error) => {
262
worker.postMessage({
263
target: 'IDBStore',
264
method: 'response',
265
error: !!error
266
});
267
});
268
break;
269
}
270
}
271
break;
272
}
273
case 'custom': {
274
if (Module['onCustomMessage']) {
275
Module['onCustomMessage'](event);
276
} else {
277
throw 'Custom message received but client Module.onCustomMessage not implemented.';
278
}
279
break;
280
}
281
case 'setimmediate': {
282
worker.postMessage({target: 'setimmediate'});
283
break;
284
}
285
default: throw 'what? ' + data.target;
286
}
287
};
288
289
function postCustomMessage(data, options = {}) {
290
worker.postMessage({ target: 'custom', userData: data, preMain: options.preMain });
291
}
292
293
function cloneObject(event) {
294
var ret = {};
295
for (var x in event) {
296
if (x == x.toUpperCase()) continue;
297
var prop = event[x];
298
if (typeof prop == 'number' || typeof prop == 'string') ret[x] = prop;
299
}
300
return ret;
301
};
302
303
#if ENVIRONMENT_MAY_BE_NODE
304
if (!ENVIRONMENT_IS_NODE) {
305
#endif
306
307
// Only prevent default on backspace/tab because we don't want unexpected navigation.
308
// Do not prevent default on the rest as we need the keypress event.
309
function shouldPreventDefault(event) {
310
if (event.type === 'keydown' && event.key != 'Backspace' && event.key != 'Tab') {
311
return false; // keypress, back navigation
312
} else {
313
return true; // NO keypress, NO back navigation
314
}
315
};
316
317
318
['keydown', 'keyup', 'keypress', 'blur', 'visibilitychange'].forEach((event) => {
319
document.addEventListener(event, (event) => {
320
worker.postMessage({ target: 'document', event: cloneObject(event) });
321
322
if (shouldPreventDefault(event)) {
323
event.preventDefault();
324
}
325
});
326
});
327
328
['unload'].forEach((event) => {
329
window.addEventListener(event, (event) => {
330
worker.postMessage({ target: 'window', event: cloneObject(event) });
331
});
332
});
333
334
['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach((event) => {
335
Module['canvas'].addEventListener(event, (event) => {
336
worker.postMessage({ target: 'canvas', event: cloneObject(event) });
337
event.preventDefault();
338
}, true);
339
});
340
341
#if ENVIRONMENT_MAY_BE_NODE
342
}
343
#endif
344
345