blob: 8987bb6cfea6c66c86938582bf69228c1d4a33cc [file] [log] [blame] [edit]
/**
* @license
* Copyright 2013 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
/*
* Proxy events/work to/from an emscripen worker built
* with PROXY_TO_WORKER. This code runs on the main
* thread and is not part of the main emscripten output
* file.
*/
#if !PROXY_TO_WORKER
#error "proxyClient.js should only be included in PROXY_TO_WORKER mode"
#endif
#if ENVIRONMENT_MAY_BE_NODE
var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string';
if (ENVIRONMENT_IS_NODE) {
global.Worker = require('worker_threads').Worker;
var Module = Module || {}
} else
#endif
if (typeof Module == 'undefined') {
console.warn('no Module object defined - cannot proxy canvas rendering and input events, etc.');
Module = {
canvas: {
addEventListener: () => {},
getBoundingClientRect: () => ({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0 }),
},
};
}
if (!Module.hasOwnProperty('print')) {
Module['print'] = (x) => console.log(x);
}
if (!Module.hasOwnProperty('printErr')) {
Module['printErr'] = (x) => console.error(x);
}
// utils
function FPSTracker(text) {
var last = 0;
var mean = 0;
var counter = 0;
this.tick = () => {
var now = Date.now();
if (last > 0) {
var diff = now - last;
mean = 0.99*mean + 0.01*diff;
if (counter++ === 60) {
counter = 0;
dump(text + ' fps: ' + (1000/mean).toFixed(2) + '\n');
}
}
last = now;
};
}
/*
function GenericTracker(text) {
var mean = 0;
var counter = 0;
this.tick = (value) => {
mean = 0.99*mean + 0.01*value;
if (counter++ === 60) {
counter = 0;
dump(text + ': ' + (mean).toFixed(2) + '\n');
}
};
}
*/
// render
var renderFrameData = null;
function renderFrame() {
var dst = Module.canvasData.data;
if (dst.set) {
dst.set(renderFrameData);
} else {
for (var i = 0; i < renderFrameData.length; i++) {
dst[i] = renderFrameData[i];
}
}
Module.ctx.putImageData(Module.canvasData, 0, 0);
renderFrameData = null;
}
if (typeof window != 'undefined') {
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
renderFrame;
}
/*
(() => {
var trueRAF = window.requestAnimationFrame;
var tracker = new FPSTracker('client');
window.requestAnimationFrame = (func) => {
trueRAF(() => {
tracker.tick();
func();
});
}
})();
*/
// end render
// IDBStore
#include "IDBStore.js"
// Frame throttling
var frameId = 0;
// Worker
var filename;
filename ||= '<<< filename >>>';
var worker = new Worker(filename);
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
worker.postMessage({target: 'worker-init'});
} else {
#endif
WebGLClient.prefetch();
setTimeout(() => {
worker.postMessage({
target: 'worker-init',
width: Module.canvas.width,
height: Module.canvas.height,
boundingClientRect: cloneObject(Module.canvas.getBoundingClientRect()),
URL: document.URL,
currentScriptUrl: filename,
preMain: true });
}, 0); // delay til next frame, to make sure html is ready
#if ENVIRONMENT_MAY_BE_NODE
}
#endif
var workerResponded = false;
worker.onmessage = (event) => {
//dump('\nclient got ' + JSON.stringify(event.data).substr(0, 150) + '\n');
if (!workerResponded) {
workerResponded = true;
Module.setStatus?.('');
}
var data = event.data;
switch (data.target) {
case 'stdout': {
Module['print'](data.content);
break;
}
case 'stderr': {
Module['printErr'](data.content);
break;
}
case 'window': {
window[data.method]();
break;
}
case 'canvas': {
switch (data.op) {
case 'getContext': {
Module.ctx = Module.canvas.getContext(data.type, data.attributes);
if (data.type !== '2d') {
// possible GL_DEBUG entry point: Module.ctx = wrapDebugGL(Module.ctx);
Module.glClient = new WebGLClient();
}
break;
}
case 'resize': {
Module.canvas.width = data.width;
Module.canvas.height = data.height;
if (Module.ctx?.getImageData) Module.canvasData = Module.ctx.getImageData(0, 0, data.width, data.height);
worker.postMessage({ target: 'canvas', boundingClientRect: cloneObject(Module.canvas.getBoundingClientRect()) });
break;
}
case 'render': {
if (renderFrameData) {
// previous image was not rendered yet, just update image
renderFrameData = data.image.data;
} else {
// previous image was rendered so update image and request another frame
renderFrameData = data.image.data;
window.requestAnimationFrame(renderFrame);
}
break;
}
case 'setObjectProperty': {
Module.canvas[data.object][data.property] = data.value;
break;
}
default: throw 'eh?';
}
break;
}
case 'gl': {
Module.glClient.onmessage(data);
break;
}
case 'tick': {
frameId = data.id;
worker.postMessage({ target: 'tock', id: frameId });
break;
}
case 'Image': {
assert(data.method === 'src');
var img = new Image();
img.onload = () => {
assert(img.complete);
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var imageData = ctx.getImageData(0, 0, img.width, img.height);
worker.postMessage({ target: 'Image', method: 'onload', id: data.id, width: img.width, height: img.height, data: imageData.data, preMain: true });
};
img.onerror = () => {
worker.postMessage({ target: 'Image', method: 'onerror', id: data.id, preMain: true });
};
img.src = data.src;
break;
}
case 'IDBStore': {
switch (data.method) {
case 'loadBlob': {
IDBStore.getFile(data.db, data.id, (error, blob) => {
worker.postMessage({
target: 'IDBStore',
method: 'response',
blob: error ? null : blob
});
});
break;
}
case 'storeBlob': {
IDBStore.setFile(data.db, data.id, data.blob, (error) => {
worker.postMessage({
target: 'IDBStore',
method: 'response',
error: !!error
});
});
break;
}
}
break;
}
case 'custom': {
if (Module['onCustomMessage']) {
Module['onCustomMessage'](event);
} else {
throw 'Custom message received but client Module.onCustomMessage not implemented.';
}
break;
}
case 'setimmediate': {
worker.postMessage({target: 'setimmediate'});
break;
}
default: throw 'what? ' + data.target;
}
};
function postCustomMessage(data, options = {}) {
worker.postMessage({ target: 'custom', userData: data, preMain: options.preMain });
}
function cloneObject(event) {
var ret = {};
for (var x in event) {
if (x == x.toUpperCase()) continue;
var prop = event[x];
if (typeof prop == 'number' || typeof prop == 'string') ret[x] = prop;
}
return ret;
};
#if ENVIRONMENT_MAY_BE_NODE
if (!ENVIRONMENT_IS_NODE) {
#endif
// Only prevent default on backspace/tab because we don't want unexpected navigation.
// Do not prevent default on the rest as we need the keypress event.
function shouldPreventDefault(event) {
if (event.type === 'keydown' && event.keyCode !== 8 /* backspace */ && event.keyCode !== 9 /* tab */) {
return false; // keypress, back navigation
} else {
return true; // NO keypress, NO back navigation
}
};
['keydown', 'keyup', 'keypress', 'blur', 'visibilitychange'].forEach((event) => {
document.addEventListener(event, (event) => {
worker.postMessage({ target: 'document', event: cloneObject(event) });
if (shouldPreventDefault(event)) {
event.preventDefault();
}
});
});
['unload'].forEach((event) => {
window.addEventListener(event, (event) => {
worker.postMessage({ target: 'window', event: cloneObject(event) });
});
});
['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach((event) => {
Module.canvas.addEventListener(event, (event) => {
worker.postMessage({ target: 'canvas', event: cloneObject(event) });
event.preventDefault();
}, true);
});
#if ENVIRONMENT_MAY_BE_NODE
}
#endif