mirror of https://github.com/pyodide/pyodide.git
1519 lines
59 KiB
JavaScript
1519 lines
59 KiB
JavaScript
//"use strict";
|
|
|
|
// Utilities for browser environments
|
|
var LibraryBrowser = {
|
|
$Browser__deps: ['emscripten_set_main_loop', 'emscripten_set_main_loop_timing'],
|
|
$Browser__postset: 'Module["requestFullScreen"] = function Module_requestFullScreen(lockPointer, resizeCanvas, vrDevice) { Module.printErr("Module.requestFullScreen is deprecated. Please call Module.requestFullscreen instead."); Module["requestFullScreen"] = Module["requestFullscreen"]; Browser.requestFullScreen(lockPointer, resizeCanvas, vrDevice) };\n' + // exports
|
|
'Module["requestFullscreen"] = function Module_requestFullscreen(lockPointer, resizeCanvas, vrDevice) { Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice) };\n' + // exports
|
|
'Module["requestAnimationFrame"] = function Module_requestAnimationFrame(func) { Browser.requestAnimationFrame(func) };\n' +
|
|
'Module["setCanvasSize"] = function Module_setCanvasSize(width, height, noUpdates) { Browser.setCanvasSize(width, height, noUpdates) };\n' +
|
|
'Module["pauseMainLoop"] = function Module_pauseMainLoop() { Browser.mainLoop.pause() };\n' +
|
|
'Module["resumeMainLoop"] = function Module_resumeMainLoop() { Browser.mainLoop.resume() };\n' +
|
|
'Module["getUserMedia"] = function Module_getUserMedia() { Browser.getUserMedia() }\n' +
|
|
'Module["createContext"] = function Module_createContext(canvas, useWebGL, setInModule, webGLContextAttributes) { return Browser.createContext(canvas, useWebGL, setInModule, webGLContextAttributes) }',
|
|
$Browser: {
|
|
mainLoop: {
|
|
scheduler: null,
|
|
method: '',
|
|
// Each main loop is numbered with a ID in sequence order. Only one main loop can run at a time. This variable stores the ordinal number of the main loop that is currently
|
|
// allowed to run. All previous main loops will quit themselves. This is incremented whenever a new main loop is created.
|
|
currentlyRunningMainloop: 0,
|
|
func: null, // The main loop tick function that will be called at each iteration.
|
|
arg: 0, // The argument that will be passed to the main loop. (of type void*)
|
|
timingMode: 0,
|
|
timingValue: 0,
|
|
currentFrameNumber: 0,
|
|
queue: [],
|
|
pause: function() {
|
|
Browser.mainLoop.scheduler = null;
|
|
Browser.mainLoop.currentlyRunningMainloop++; // Incrementing this signals the previous main loop that it's now become old, and it must return.
|
|
},
|
|
resume: function() {
|
|
Browser.mainLoop.currentlyRunningMainloop++;
|
|
var timingMode = Browser.mainLoop.timingMode;
|
|
var timingValue = Browser.mainLoop.timingValue;
|
|
var func = Browser.mainLoop.func;
|
|
Browser.mainLoop.func = null;
|
|
_emscripten_set_main_loop(func, 0, false, Browser.mainLoop.arg, true /* do not set timing and call scheduler, we will do it on the next lines */);
|
|
_emscripten_set_main_loop_timing(timingMode, timingValue);
|
|
Browser.mainLoop.scheduler();
|
|
},
|
|
updateStatus: function() {
|
|
if (Module['setStatus']) {
|
|
var message = Module['statusMessage'] || 'Please wait...';
|
|
var remaining = Browser.mainLoop.remainingBlockers;
|
|
var expected = Browser.mainLoop.expectedBlockers;
|
|
if (remaining) {
|
|
if (remaining < expected) {
|
|
Module['setStatus'](message + ' (' + (expected - remaining) + '/' + expected + ')');
|
|
} else {
|
|
Module['setStatus'](message);
|
|
}
|
|
} else {
|
|
Module['setStatus']('');
|
|
}
|
|
}
|
|
},
|
|
runIter: function(func) {
|
|
if (ABORT) return;
|
|
if (Module['preMainLoop']) {
|
|
var preRet = Module['preMainLoop']();
|
|
if (preRet === false) {
|
|
return; // |return false| skips a frame
|
|
}
|
|
}
|
|
try {
|
|
func();
|
|
} catch (e) {
|
|
if (e instanceof ExitStatus) {
|
|
return;
|
|
} else {
|
|
if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]);
|
|
throw e;
|
|
}
|
|
}
|
|
if (Module['postMainLoop']) Module['postMainLoop']();
|
|
}
|
|
},
|
|
isFullscreen: false,
|
|
pointerLock: false,
|
|
moduleContextCreatedCallbacks: [],
|
|
workers: [],
|
|
|
|
init: function() {
|
|
if (!Module["preloadPlugins"]) Module["preloadPlugins"] = []; // needs to exist even in workers
|
|
|
|
if (Browser.initted) return;
|
|
Browser.initted = true;
|
|
|
|
try {
|
|
new Blob();
|
|
Browser.hasBlobConstructor = true;
|
|
} catch(e) {
|
|
Browser.hasBlobConstructor = false;
|
|
console.log("warning: no blob constructor, cannot create blobs with mimetypes");
|
|
}
|
|
Browser.BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : (typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : (!Browser.hasBlobConstructor ? console.log("warning: no BlobBuilder") : null));
|
|
Browser.URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : undefined;
|
|
if (!Module.noImageDecoding && typeof Browser.URLObject === 'undefined') {
|
|
console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.");
|
|
Module.noImageDecoding = true;
|
|
}
|
|
|
|
// Support for plugins that can process preloaded files. You can add more of these to
|
|
// your app by creating and appending to Module.preloadPlugins.
|
|
//
|
|
// Each plugin is asked if it can handle a file based on the file's name. If it can,
|
|
// it is given the file's raw data. When it is done, it calls a callback with the file's
|
|
// (possibly modified) data. For example, a plugin might decompress a file, or it
|
|
// might create some side data structure for use later (like an Image element, etc.).
|
|
|
|
var imagePlugin = {};
|
|
imagePlugin['canHandle'] = function imagePlugin_canHandle(name) {
|
|
return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name);
|
|
};
|
|
imagePlugin['handle'] = function imagePlugin_handle(byteArray, name, onload, onerror) {
|
|
var b = null;
|
|
if (Browser.hasBlobConstructor) {
|
|
try {
|
|
b = new Blob([byteArray], { type: Browser.getMimetype(name) });
|
|
if (b.size !== byteArray.length) { // Safari bug #118630
|
|
// Safari's Blob can only take an ArrayBuffer
|
|
b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) });
|
|
}
|
|
} catch(e) {
|
|
warnOnce('Blob constructor present but fails: ' + e + '; falling back to blob builder');
|
|
}
|
|
}
|
|
if (!b) {
|
|
var bb = new Browser.BlobBuilder();
|
|
bb.append((new Uint8Array(byteArray)).buffer); // we need to pass a buffer, and must copy the array to get the right data range
|
|
b = bb.getBlob();
|
|
}
|
|
var url = Browser.URLObject.createObjectURL(b);
|
|
#if ASSERTIONS
|
|
assert(typeof url == 'string', 'createObjectURL must return a url as a string');
|
|
#endif
|
|
var img = new Image();
|
|
img.onload = function img_onload() {
|
|
assert(img.complete, 'Image ' + name + ' could not be decoded');
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.drawImage(img, 0, 0);
|
|
Module["preloadedImages"][name] = canvas;
|
|
Browser.URLObject.revokeObjectURL(url);
|
|
if (onload) onload(byteArray);
|
|
};
|
|
img.onerror = function img_onerror(event) {
|
|
console.log('Image ' + url + ' could not be decoded');
|
|
if (onerror) onerror();
|
|
};
|
|
img.src = url;
|
|
};
|
|
Module['preloadPlugins'].push(imagePlugin);
|
|
|
|
var audioPlugin = {};
|
|
audioPlugin['canHandle'] = function audioPlugin_canHandle(name) {
|
|
return !Module.noAudioDecoding && name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 };
|
|
};
|
|
audioPlugin['handle'] = function audioPlugin_handle(byteArray, name, onload, onerror) {
|
|
var done = false;
|
|
function finish(audio) {
|
|
if (done) return;
|
|
done = true;
|
|
Module["preloadedAudios"][name] = audio;
|
|
if (onload) onload(byteArray);
|
|
}
|
|
function fail() {
|
|
if (done) return;
|
|
done = true;
|
|
Module["preloadedAudios"][name] = new Audio(); // empty shim
|
|
if (onerror) onerror();
|
|
}
|
|
if (Browser.hasBlobConstructor) {
|
|
try {
|
|
var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
|
|
} catch(e) {
|
|
return fail();
|
|
}
|
|
var url = Browser.URLObject.createObjectURL(b); // XXX we never revoke this!
|
|
#if ASSERTIONS
|
|
assert(typeof url == 'string', 'createObjectURL must return a url as a string');
|
|
#endif
|
|
var audio = new Audio();
|
|
audio.addEventListener('canplaythrough', function() { finish(audio) }, false); // use addEventListener due to chromium bug 124926
|
|
audio.onerror = function audio_onerror(event) {
|
|
if (done) return;
|
|
console.log('warning: browser could not fully decode audio ' + name + ', trying slower base64 approach');
|
|
function encode64(data) {
|
|
var BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
var PAD = '=';
|
|
var ret = '';
|
|
var leftchar = 0;
|
|
var leftbits = 0;
|
|
for (var i = 0; i < data.length; i++) {
|
|
leftchar = (leftchar << 8) | data[i];
|
|
leftbits += 8;
|
|
while (leftbits >= 6) {
|
|
var curr = (leftchar >> (leftbits-6)) & 0x3f;
|
|
leftbits -= 6;
|
|
ret += BASE[curr];
|
|
}
|
|
}
|
|
if (leftbits == 2) {
|
|
ret += BASE[(leftchar&3) << 4];
|
|
ret += PAD + PAD;
|
|
} else if (leftbits == 4) {
|
|
ret += BASE[(leftchar&0xf) << 2];
|
|
ret += PAD;
|
|
}
|
|
return ret;
|
|
}
|
|
audio.src = 'data:audio/x-' + name.substr(-3) + ';base64,' + encode64(byteArray);
|
|
finish(audio); // we don't wait for confirmation this worked - but it's worth trying
|
|
};
|
|
audio.src = url;
|
|
// workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror
|
|
Browser.safeSetTimeout(function() {
|
|
finish(audio); // try to use it even though it is not necessarily ready to play
|
|
}, 10000);
|
|
} else {
|
|
return fail();
|
|
}
|
|
};
|
|
Module['preloadPlugins'].push(audioPlugin);
|
|
|
|
#if (WASM != 0) && (MAIN_MODULE != 0)
|
|
var wasmPlugin = {};
|
|
wasmPlugin['asyncWasmLoadPromise'] = new Promise(
|
|
function(resolve, reject) { return resolve(); });
|
|
wasmPlugin['canHandle'] = function(name) {
|
|
return !Module.noWasmDecoding && (name.endsWith('.so') || name.endsWith('.wasm'));
|
|
};
|
|
wasmPlugin['handle'] = function(byteArray, name, onload, onerror) {
|
|
// loadWebAssemblyModule can not load modules out-of-order, so rather
|
|
// than just running the promises in parallel, this makes a chain of
|
|
// promises to run in series.
|
|
this.asyncWasmLoadPromise = this.asyncWasmLoadPromise.then(
|
|
function() {
|
|
return Module.loadWebAssemblyModule(byteArray, true)
|
|
}).then(
|
|
function(module) {
|
|
Module.preloadedWasm[name] = module;
|
|
onload();
|
|
},
|
|
function(err) {
|
|
console.warn("Couldn't instantiate wasm: " + name + " '" + err + "'");
|
|
onerror();
|
|
});
|
|
};
|
|
Module['preloadPlugins'].push(wasmPlugin);
|
|
#endif
|
|
|
|
// Canvas event setup
|
|
|
|
function pointerLockChange() {
|
|
Browser.pointerLock = document['pointerLockElement'] === Module['canvas'] ||
|
|
document['mozPointerLockElement'] === Module['canvas'] ||
|
|
document['webkitPointerLockElement'] === Module['canvas'] ||
|
|
document['msPointerLockElement'] === Module['canvas'];
|
|
}
|
|
var canvas = Module['canvas'];
|
|
if (canvas) {
|
|
// forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module
|
|
// Module['forcedAspectRatio'] = 4 / 3;
|
|
|
|
canvas.requestPointerLock = canvas['requestPointerLock'] ||
|
|
canvas['mozRequestPointerLock'] ||
|
|
canvas['webkitRequestPointerLock'] ||
|
|
canvas['msRequestPointerLock'] ||
|
|
function(){};
|
|
canvas.exitPointerLock = document['exitPointerLock'] ||
|
|
document['mozExitPointerLock'] ||
|
|
document['webkitExitPointerLock'] ||
|
|
document['msExitPointerLock'] ||
|
|
function(){}; // no-op if function does not exist
|
|
canvas.exitPointerLock = canvas.exitPointerLock.bind(document);
|
|
|
|
document.addEventListener('pointerlockchange', pointerLockChange, false);
|
|
document.addEventListener('mozpointerlockchange', pointerLockChange, false);
|
|
document.addEventListener('webkitpointerlockchange', pointerLockChange, false);
|
|
document.addEventListener('mspointerlockchange', pointerLockChange, false);
|
|
|
|
if (Module['elementPointerLock']) {
|
|
canvas.addEventListener("click", function(ev) {
|
|
if (!Browser.pointerLock && Module['canvas'].requestPointerLock) {
|
|
Module['canvas'].requestPointerLock();
|
|
ev.preventDefault();
|
|
}
|
|
}, false);
|
|
}
|
|
}
|
|
},
|
|
|
|
createContext: function(canvas, useWebGL, setInModule, webGLContextAttributes) {
|
|
if (useWebGL && Module.ctx && canvas == Module.canvas) return Module.ctx; // no need to recreate GL context if it's already been created for this canvas.
|
|
|
|
var ctx;
|
|
var contextHandle;
|
|
if (useWebGL) {
|
|
// For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults.
|
|
var contextAttributes = {
|
|
antialias: false,
|
|
alpha: false
|
|
};
|
|
|
|
if (webGLContextAttributes) {
|
|
for (var attribute in webGLContextAttributes) {
|
|
contextAttributes[attribute] = webGLContextAttributes[attribute];
|
|
}
|
|
}
|
|
|
|
contextHandle = GL.createContext(canvas, contextAttributes);
|
|
if (contextHandle) {
|
|
ctx = GL.getContext(contextHandle).GLctx;
|
|
}
|
|
} else {
|
|
ctx = canvas.getContext('2d');
|
|
}
|
|
|
|
if (!ctx) return null;
|
|
|
|
if (setInModule) {
|
|
if (!useWebGL) assert(typeof GLctx === 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it');
|
|
|
|
Module.ctx = ctx;
|
|
if (useWebGL) GL.makeContextCurrent(contextHandle);
|
|
Module.useWebGL = useWebGL;
|
|
Browser.moduleContextCreatedCallbacks.forEach(function(callback) { callback() });
|
|
Browser.init();
|
|
}
|
|
return ctx;
|
|
},
|
|
|
|
destroyContext: function(canvas, useWebGL, setInModule) {},
|
|
|
|
fullscreenHandlersInstalled: false,
|
|
lockPointer: undefined,
|
|
resizeCanvas: undefined,
|
|
requestFullscreen: function(lockPointer, resizeCanvas, vrDevice) {
|
|
Browser.lockPointer = lockPointer;
|
|
Browser.resizeCanvas = resizeCanvas;
|
|
Browser.vrDevice = vrDevice;
|
|
if (typeof Browser.lockPointer === 'undefined') Browser.lockPointer = true;
|
|
if (typeof Browser.resizeCanvas === 'undefined') Browser.resizeCanvas = false;
|
|
if (typeof Browser.vrDevice === 'undefined') Browser.vrDevice = null;
|
|
|
|
var canvas = Module['canvas'];
|
|
function fullscreenChange() {
|
|
Browser.isFullscreen = false;
|
|
var canvasContainer = canvas.parentNode;
|
|
if ((document['fullscreenElement'] || document['mozFullScreenElement'] ||
|
|
document['msFullscreenElement'] || document['webkitFullscreenElement'] ||
|
|
document['webkitCurrentFullScreenElement']) === canvasContainer) {
|
|
canvas.exitFullscreen = document['exitFullscreen'] ||
|
|
document['cancelFullScreen'] ||
|
|
document['mozCancelFullScreen'] ||
|
|
document['msExitFullscreen'] ||
|
|
document['webkitCancelFullScreen'] ||
|
|
function() {};
|
|
canvas.exitFullscreen = canvas.exitFullscreen.bind(document);
|
|
if (Browser.lockPointer) canvas.requestPointerLock();
|
|
Browser.isFullscreen = true;
|
|
if (Browser.resizeCanvas) {
|
|
Browser.setFullscreenCanvasSize();
|
|
} else {
|
|
Browser.updateCanvasDimensions(canvas);
|
|
}
|
|
} else {
|
|
// remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen
|
|
canvasContainer.parentNode.insertBefore(canvas, canvasContainer);
|
|
canvasContainer.parentNode.removeChild(canvasContainer);
|
|
|
|
if (Browser.resizeCanvas) {
|
|
Browser.setWindowedCanvasSize();
|
|
} else {
|
|
Browser.updateCanvasDimensions(canvas);
|
|
}
|
|
}
|
|
if (Module['onFullScreen']) Module['onFullScreen'](Browser.isFullscreen);
|
|
if (Module['onFullscreen']) Module['onFullscreen'](Browser.isFullscreen);
|
|
}
|
|
|
|
if (!Browser.fullscreenHandlersInstalled) {
|
|
Browser.fullscreenHandlersInstalled = true;
|
|
document.addEventListener('fullscreenchange', fullscreenChange, false);
|
|
document.addEventListener('mozfullscreenchange', fullscreenChange, false);
|
|
document.addEventListener('webkitfullscreenchange', fullscreenChange, false);
|
|
document.addEventListener('MSFullscreenChange', fullscreenChange, false);
|
|
}
|
|
|
|
// create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root
|
|
var canvasContainer = document.createElement("div");
|
|
canvas.parentNode.insertBefore(canvasContainer, canvas);
|
|
canvasContainer.appendChild(canvas);
|
|
|
|
// use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size)
|
|
canvasContainer.requestFullscreen = canvasContainer['requestFullscreen'] ||
|
|
canvasContainer['mozRequestFullScreen'] ||
|
|
canvasContainer['msRequestFullscreen'] ||
|
|
(canvasContainer['webkitRequestFullscreen'] ? function() { canvasContainer['webkitRequestFullscreen'](Element['ALLOW_KEYBOARD_INPUT']) } : null) ||
|
|
(canvasContainer['webkitRequestFullScreen'] ? function() { canvasContainer['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) } : null);
|
|
|
|
if (vrDevice) {
|
|
canvasContainer.requestFullscreen({ vrDisplay: vrDevice });
|
|
} else {
|
|
canvasContainer.requestFullscreen();
|
|
}
|
|
},
|
|
|
|
requestFullScreen: function(lockPointer, resizeCanvas, vrDevice) {
|
|
Module.printErr('Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead.');
|
|
Browser.requestFullScreen = function(lockPointer, resizeCanvas, vrDevice) {
|
|
return Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice);
|
|
}
|
|
return Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice);
|
|
},
|
|
|
|
nextRAF: 0,
|
|
|
|
fakeRequestAnimationFrame: function(func) {
|
|
// try to keep 60fps between calls to here
|
|
var now = Date.now();
|
|
if (Browser.nextRAF === 0) {
|
|
Browser.nextRAF = now + 1000/60;
|
|
} else {
|
|
while (now + 2 >= Browser.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0
|
|
Browser.nextRAF += 1000/60;
|
|
}
|
|
}
|
|
var delay = Math.max(Browser.nextRAF - now, 0);
|
|
setTimeout(func, delay);
|
|
},
|
|
|
|
requestAnimationFrame: function requestAnimationFrame(func) {
|
|
if (typeof window === 'undefined') { // Provide fallback to setTimeout if window is undefined (e.g. in Node.js)
|
|
Browser.fakeRequestAnimationFrame(func);
|
|
} else {
|
|
if (!window.requestAnimationFrame) {
|
|
window.requestAnimationFrame = window['requestAnimationFrame'] ||
|
|
window['mozRequestAnimationFrame'] ||
|
|
window['webkitRequestAnimationFrame'] ||
|
|
window['msRequestAnimationFrame'] ||
|
|
window['oRequestAnimationFrame'] ||
|
|
Browser.fakeRequestAnimationFrame;
|
|
}
|
|
window.requestAnimationFrame(func);
|
|
}
|
|
},
|
|
|
|
// generic abort-aware wrapper for an async callback
|
|
safeCallback: function(func) {
|
|
return function() {
|
|
if (!ABORT) return func.apply(null, arguments);
|
|
};
|
|
},
|
|
|
|
// abort and pause-aware versions TODO: build main loop on top of this?
|
|
|
|
allowAsyncCallbacks: true,
|
|
queuedAsyncCallbacks: [],
|
|
|
|
pauseAsyncCallbacks: function() {
|
|
Browser.allowAsyncCallbacks = false;
|
|
},
|
|
resumeAsyncCallbacks: function() { // marks future callbacks as ok to execute, and synchronously runs any remaining ones right now
|
|
Browser.allowAsyncCallbacks = true;
|
|
if (Browser.queuedAsyncCallbacks.length > 0) {
|
|
var callbacks = Browser.queuedAsyncCallbacks;
|
|
Browser.queuedAsyncCallbacks = [];
|
|
callbacks.forEach(function(func) {
|
|
func();
|
|
});
|
|
}
|
|
},
|
|
|
|
safeRequestAnimationFrame: function(func) {
|
|
return Browser.requestAnimationFrame(function() {
|
|
if (ABORT) return;
|
|
if (Browser.allowAsyncCallbacks) {
|
|
func();
|
|
} else {
|
|
Browser.queuedAsyncCallbacks.push(func);
|
|
}
|
|
});
|
|
},
|
|
safeSetTimeout: function(func, timeout) {
|
|
Module['noExitRuntime'] = true;
|
|
return setTimeout(function() {
|
|
if (ABORT) return;
|
|
if (Browser.allowAsyncCallbacks) {
|
|
func();
|
|
} else {
|
|
Browser.queuedAsyncCallbacks.push(func);
|
|
}
|
|
}, timeout);
|
|
},
|
|
safeSetInterval: function(func, timeout) {
|
|
Module['noExitRuntime'] = true;
|
|
return setInterval(function() {
|
|
if (ABORT) return;
|
|
if (Browser.allowAsyncCallbacks) {
|
|
func();
|
|
} // drop it on the floor otherwise, next interval will kick in
|
|
}, timeout);
|
|
},
|
|
|
|
getMimetype: function(name) {
|
|
return {
|
|
'jpg': 'image/jpeg',
|
|
'jpeg': 'image/jpeg',
|
|
'png': 'image/png',
|
|
'bmp': 'image/bmp',
|
|
'ogg': 'audio/ogg',
|
|
'wav': 'audio/wav',
|
|
'mp3': 'audio/mpeg'
|
|
}[name.substr(name.lastIndexOf('.')+1)];
|
|
},
|
|
|
|
getUserMedia: function(func) {
|
|
if(!window.getUserMedia) {
|
|
window.getUserMedia = navigator['getUserMedia'] ||
|
|
navigator['mozGetUserMedia'];
|
|
}
|
|
window.getUserMedia(func);
|
|
},
|
|
|
|
|
|
getMovementX: function(event) {
|
|
return event['movementX'] ||
|
|
event['mozMovementX'] ||
|
|
event['webkitMovementX'] ||
|
|
0;
|
|
},
|
|
|
|
getMovementY: function(event) {
|
|
return event['movementY'] ||
|
|
event['mozMovementY'] ||
|
|
event['webkitMovementY'] ||
|
|
0;
|
|
},
|
|
|
|
// Browsers specify wheel direction according to the page CSS pixel Y direction:
|
|
// Scrolling mouse wheel down (==towards user/away from screen) on Windows/Linux (and OSX without 'natural scroll' enabled)
|
|
// is the positive wheel direction. Scrolling mouse wheel up (towards the screen) is the negative wheel direction.
|
|
// This function returns the wheel direction in the browser page coordinate system (+: down, -: up). Note that this is often the
|
|
// opposite of native code: In native APIs the positive scroll direction is to scroll up (away from the user).
|
|
// NOTE: The mouse wheel delta is a decimal number, and can be a fractional value within -1 and 1. If you need to represent
|
|
// this as an integer, don't simply cast to int, or you may receive scroll events for wheel delta == 0.
|
|
getMouseWheelDelta: function(event) {
|
|
var delta = 0;
|
|
switch (event.type) {
|
|
case 'DOMMouseScroll':
|
|
delta = event.detail;
|
|
break;
|
|
case 'mousewheel':
|
|
delta = event.wheelDelta;
|
|
break;
|
|
case 'wheel':
|
|
delta = event['deltaY'];
|
|
break;
|
|
default:
|
|
throw 'unrecognized mouse wheel event: ' + event.type;
|
|
}
|
|
return delta;
|
|
},
|
|
|
|
mouseX: 0,
|
|
mouseY: 0,
|
|
mouseMovementX: 0,
|
|
mouseMovementY: 0,
|
|
touches: {},
|
|
lastTouches: {},
|
|
|
|
calculateMouseEvent: function(event) { // event should be mousemove, mousedown or mouseup
|
|
if (Browser.pointerLock) {
|
|
// When the pointer is locked, calculate the coordinates
|
|
// based on the movement of the mouse.
|
|
// Workaround for Firefox bug 764498
|
|
if (event.type != 'mousemove' &&
|
|
('mozMovementX' in event)) {
|
|
Browser.mouseMovementX = Browser.mouseMovementY = 0;
|
|
} else {
|
|
Browser.mouseMovementX = Browser.getMovementX(event);
|
|
Browser.mouseMovementY = Browser.getMovementY(event);
|
|
}
|
|
|
|
// check if SDL is available
|
|
if (typeof SDL != "undefined") {
|
|
Browser.mouseX = SDL.mouseX + Browser.mouseMovementX;
|
|
Browser.mouseY = SDL.mouseY + Browser.mouseMovementY;
|
|
} else {
|
|
// just add the mouse delta to the current absolut mouse position
|
|
// FIXME: ideally this should be clamped against the canvas size and zero
|
|
Browser.mouseX += Browser.mouseMovementX;
|
|
Browser.mouseY += Browser.mouseMovementY;
|
|
}
|
|
} else {
|
|
// Otherwise, calculate the movement based on the changes
|
|
// in the coordinates.
|
|
var rect = Module["canvas"].getBoundingClientRect();
|
|
var cw = Module["canvas"].width;
|
|
var ch = Module["canvas"].height;
|
|
|
|
// Neither .scrollX or .pageXOffset are defined in a spec, but
|
|
// we prefer .scrollX because it is currently in a spec draft.
|
|
// (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/)
|
|
var scrollX = ((typeof window.scrollX !== 'undefined') ? window.scrollX : window.pageXOffset);
|
|
var scrollY = ((typeof window.scrollY !== 'undefined') ? window.scrollY : window.pageYOffset);
|
|
#if ASSERTIONS
|
|
// If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset
|
|
// and we have no viable fallback.
|
|
assert((typeof scrollX !== 'undefined') && (typeof scrollY !== 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.');
|
|
#endif
|
|
|
|
if (event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchmove') {
|
|
var touch = event.touch;
|
|
if (touch === undefined) {
|
|
return; // the "touch" property is only defined in SDL
|
|
|
|
}
|
|
var adjustedX = touch.pageX - (scrollX + rect.left);
|
|
var adjustedY = touch.pageY - (scrollY + rect.top);
|
|
|
|
adjustedX = adjustedX * (cw / rect.width);
|
|
adjustedY = adjustedY * (ch / rect.height);
|
|
|
|
var coords = { x: adjustedX, y: adjustedY };
|
|
|
|
if (event.type === 'touchstart') {
|
|
Browser.lastTouches[touch.identifier] = coords;
|
|
Browser.touches[touch.identifier] = coords;
|
|
} else if (event.type === 'touchend' || event.type === 'touchmove') {
|
|
var last = Browser.touches[touch.identifier];
|
|
if (!last) last = coords;
|
|
Browser.lastTouches[touch.identifier] = last;
|
|
Browser.touches[touch.identifier] = coords;
|
|
}
|
|
return;
|
|
}
|
|
|
|
var x = event.pageX - (scrollX + rect.left);
|
|
var y = event.pageY - (scrollY + rect.top);
|
|
|
|
// the canvas might be CSS-scaled compared to its backbuffer;
|
|
// SDL-using content will want mouse coordinates in terms
|
|
// of backbuffer units.
|
|
x = x * (cw / rect.width);
|
|
y = y * (ch / rect.height);
|
|
|
|
Browser.mouseMovementX = x - Browser.mouseX;
|
|
Browser.mouseMovementY = y - Browser.mouseY;
|
|
Browser.mouseX = x;
|
|
Browser.mouseY = y;
|
|
}
|
|
},
|
|
|
|
asyncLoad: function(url, onload, onerror, noRunDep) {
|
|
var dep = !noRunDep ? getUniqueRunDependency('al ' + url) : '';
|
|
Module['readAsync'](url, function(arrayBuffer) {
|
|
assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).');
|
|
onload(new Uint8Array(arrayBuffer));
|
|
if (dep) removeRunDependency(dep);
|
|
}, function(event) {
|
|
if (onerror) {
|
|
onerror();
|
|
} else {
|
|
throw 'Loading data file "' + url + '" failed.';
|
|
}
|
|
});
|
|
if (dep) addRunDependency(dep);
|
|
},
|
|
|
|
resizeListeners: [],
|
|
|
|
updateResizeListeners: function() {
|
|
var canvas = Module['canvas'];
|
|
Browser.resizeListeners.forEach(function(listener) {
|
|
listener(canvas.width, canvas.height);
|
|
});
|
|
},
|
|
|
|
setCanvasSize: function(width, height, noUpdates) {
|
|
var canvas = Module['canvas'];
|
|
Browser.updateCanvasDimensions(canvas, width, height);
|
|
if (!noUpdates) Browser.updateResizeListeners();
|
|
},
|
|
|
|
windowedWidth: 0,
|
|
windowedHeight: 0,
|
|
setFullscreenCanvasSize: function() {
|
|
// check if SDL is available
|
|
if (typeof SDL != "undefined") {
|
|
var flags = {{{ makeGetValue('SDL.screen', '0', 'i32', 0, 1) }}};
|
|
flags = flags | 0x00800000; // set SDL_FULLSCREEN flag
|
|
{{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}}
|
|
}
|
|
Browser.updateCanvasDimensions(Module['canvas']);
|
|
Browser.updateResizeListeners();
|
|
},
|
|
|
|
setWindowedCanvasSize: function() {
|
|
// check if SDL is available
|
|
if (typeof SDL != "undefined") {
|
|
var flags = {{{ makeGetValue('SDL.screen', '0', 'i32', 0, 1) }}};
|
|
flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag
|
|
{{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}}
|
|
}
|
|
Browser.updateCanvasDimensions(Module['canvas']);
|
|
Browser.updateResizeListeners();
|
|
},
|
|
|
|
updateCanvasDimensions : function(canvas, wNative, hNative) {
|
|
if (wNative && hNative) {
|
|
canvas.widthNative = wNative;
|
|
canvas.heightNative = hNative;
|
|
} else {
|
|
wNative = canvas.widthNative;
|
|
hNative = canvas.heightNative;
|
|
}
|
|
var w = wNative;
|
|
var h = hNative;
|
|
if (Module['forcedAspectRatio'] && Module['forcedAspectRatio'] > 0) {
|
|
if (w/h < Module['forcedAspectRatio']) {
|
|
w = Math.round(h * Module['forcedAspectRatio']);
|
|
} else {
|
|
h = Math.round(w / Module['forcedAspectRatio']);
|
|
}
|
|
}
|
|
if (((document['fullscreenElement'] || document['mozFullScreenElement'] ||
|
|
document['msFullscreenElement'] || document['webkitFullscreenElement'] ||
|
|
document['webkitCurrentFullScreenElement']) === canvas.parentNode) && (typeof screen != 'undefined')) {
|
|
var factor = Math.min(screen.width / w, screen.height / h);
|
|
w = Math.round(w * factor);
|
|
h = Math.round(h * factor);
|
|
}
|
|
if (Browser.resizeCanvas) {
|
|
if (canvas.width != w) canvas.width = w;
|
|
if (canvas.height != h) canvas.height = h;
|
|
if (typeof canvas.style != 'undefined') {
|
|
canvas.style.removeProperty( "width");
|
|
canvas.style.removeProperty("height");
|
|
}
|
|
} else {
|
|
if (canvas.width != wNative) canvas.width = wNative;
|
|
if (canvas.height != hNative) canvas.height = hNative;
|
|
if (typeof canvas.style != 'undefined') {
|
|
if (w != wNative || h != hNative) {
|
|
canvas.style.setProperty( "width", w + "px", "important");
|
|
canvas.style.setProperty("height", h + "px", "important");
|
|
} else {
|
|
canvas.style.removeProperty( "width");
|
|
canvas.style.removeProperty("height");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
wgetRequests: {},
|
|
nextWgetRequestHandle: 0,
|
|
|
|
getNextWgetRequestHandle: function() {
|
|
var handle = Browser.nextWgetRequestHandle;
|
|
Browser.nextWgetRequestHandle++;
|
|
return handle;
|
|
}
|
|
},
|
|
|
|
emscripten_async_wget__deps: ['$PATH'],
|
|
emscripten_async_wget__proxy: 'sync',
|
|
emscripten_async_wget__sig: 'viiii',
|
|
emscripten_async_wget: function(url, file, onload, onerror) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
var _url = Pointer_stringify(url);
|
|
var _file = Pointer_stringify(file);
|
|
_file = PATH.resolve(FS.cwd(), _file);
|
|
function doCallback(callback) {
|
|
if (callback) {
|
|
var stack = stackSave();
|
|
Module['dynCall_vi'](callback, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK));
|
|
stackRestore(stack);
|
|
}
|
|
}
|
|
var destinationDirectory = PATH.dirname(_file);
|
|
FS.createPreloadedFile(
|
|
destinationDirectory,
|
|
PATH.basename(_file),
|
|
_url, true, true,
|
|
function() {
|
|
doCallback(onload);
|
|
},
|
|
function() {
|
|
doCallback(onerror);
|
|
},
|
|
false, // dontCreateFile
|
|
false, // canOwn
|
|
function() { // preFinish
|
|
// if a file exists there, we overwrite it
|
|
try {
|
|
FS.unlink(_file);
|
|
} catch (e) {}
|
|
// if the destination directory does not yet exist, create it
|
|
FS.mkdirTree(destinationDirectory);
|
|
}
|
|
);
|
|
},
|
|
|
|
emscripten_async_wget_data__proxy: 'sync',
|
|
emscripten_async_wget_data__sig: 'viiii',
|
|
emscripten_async_wget_data: function(url, arg, onload, onerror) {
|
|
Browser.asyncLoad(Pointer_stringify(url), function(byteArray) {
|
|
var buffer = _malloc(byteArray.length);
|
|
HEAPU8.set(byteArray, buffer);
|
|
Module['dynCall_viii'](onload, arg, buffer, byteArray.length);
|
|
_free(buffer);
|
|
}, function() {
|
|
if (onerror) Module['dynCall_vi'](onerror, arg);
|
|
}, true /* no need for run dependency, this is async but will not do any prepare etc. step */ );
|
|
},
|
|
|
|
emscripten_async_wget2__proxy: 'sync',
|
|
emscripten_async_wget2__sig: 'iiiiiiiii',
|
|
emscripten_async_wget2: function(url, file, request, param, arg, onload, onerror, onprogress) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
var _url = Pointer_stringify(url);
|
|
var _file = Pointer_stringify(file);
|
|
_file = PATH.resolve(FS.cwd(), _file);
|
|
var _request = Pointer_stringify(request);
|
|
var _param = Pointer_stringify(param);
|
|
var index = _file.lastIndexOf('/');
|
|
|
|
var http = new XMLHttpRequest();
|
|
http.open(_request, _url, true);
|
|
http.responseType = 'arraybuffer';
|
|
|
|
var handle = Browser.getNextWgetRequestHandle();
|
|
|
|
var destinationDirectory = PATH.dirname(_file);
|
|
|
|
// LOAD
|
|
http.onload = function http_onload(e) {
|
|
if (http.status == 200) {
|
|
// if a file exists there, we overwrite it
|
|
try {
|
|
FS.unlink(_file);
|
|
} catch (e) {}
|
|
// if the destination directory does not yet exist, create it
|
|
FS.mkdirTree(destinationDirectory);
|
|
|
|
FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true, false);
|
|
if (onload) {
|
|
var stack = stackSave();
|
|
Module['dynCall_viii'](onload, handle, arg, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK));
|
|
stackRestore(stack);
|
|
}
|
|
} else {
|
|
if (onerror) Module['dynCall_viii'](onerror, handle, arg, http.status);
|
|
}
|
|
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
// ERROR
|
|
http.onerror = function http_onerror(e) {
|
|
if (onerror) Module['dynCall_viii'](onerror, handle, arg, http.status);
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
// PROGRESS
|
|
http.onprogress = function http_onprogress(e) {
|
|
if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) {
|
|
var percentComplete = (e.loaded / e.total)*100;
|
|
if (onprogress) Module['dynCall_viii'](onprogress, handle, arg, percentComplete);
|
|
}
|
|
};
|
|
|
|
// ABORT
|
|
http.onabort = function http_onabort(e) {
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
if (_request == "POST") {
|
|
//Send the proper header information along with the request
|
|
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
http.send(_param);
|
|
} else {
|
|
http.send(null);
|
|
}
|
|
|
|
Browser.wgetRequests[handle] = http;
|
|
|
|
return handle;
|
|
},
|
|
|
|
emscripten_async_wget2_data__proxy: 'sync',
|
|
emscripten_async_wget2_data__sig: 'iiiiiiiii',
|
|
emscripten_async_wget2_data: function(url, request, param, arg, free, onload, onerror, onprogress) {
|
|
var _url = Pointer_stringify(url);
|
|
var _request = Pointer_stringify(request);
|
|
var _param = Pointer_stringify(param);
|
|
|
|
var http = new XMLHttpRequest();
|
|
http.open(_request, _url, true);
|
|
http.responseType = 'arraybuffer';
|
|
|
|
var handle = Browser.getNextWgetRequestHandle();
|
|
|
|
// LOAD
|
|
http.onload = function http_onload(e) {
|
|
if (http.status == 200 || _url.substr(0,4).toLowerCase() != "http") {
|
|
var byteArray = new Uint8Array(http.response);
|
|
var buffer = _malloc(byteArray.length);
|
|
HEAPU8.set(byteArray, buffer);
|
|
if (onload) Module['dynCall_viiii'](onload, handle, arg, buffer, byteArray.length);
|
|
if (free) _free(buffer);
|
|
} else {
|
|
if (onerror) Module['dynCall_viiii'](onerror, handle, arg, http.status, http.statusText);
|
|
}
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
// ERROR
|
|
http.onerror = function http_onerror(e) {
|
|
if (onerror) {
|
|
Module['dynCall_viiii'](onerror, handle, arg, http.status, http.statusText);
|
|
}
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
// PROGRESS
|
|
http.onprogress = function http_onprogress(e) {
|
|
if (onprogress) Module['dynCall_viiii'](onprogress, handle, arg, e.loaded, e.lengthComputable || e.lengthComputable === undefined ? e.total : 0);
|
|
};
|
|
|
|
// ABORT
|
|
http.onabort = function http_onabort(e) {
|
|
delete Browser.wgetRequests[handle];
|
|
};
|
|
|
|
if (_request == "POST") {
|
|
//Send the proper header information along with the request
|
|
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
|
http.send(_param);
|
|
} else {
|
|
http.send(null);
|
|
}
|
|
|
|
Browser.wgetRequests[handle] = http;
|
|
|
|
return handle;
|
|
},
|
|
|
|
emscripten_async_wget2_abort__proxy: 'sync',
|
|
emscripten_async_wget2_abort__sig: 'vi',
|
|
emscripten_async_wget2_abort: function(handle) {
|
|
var http = Browser.wgetRequests[handle];
|
|
if (http) {
|
|
http.abort();
|
|
}
|
|
},
|
|
|
|
emscripten_run_preload_plugins__deps: ['$PATH'],
|
|
emscripten_run_preload_plugins__proxy: 'sync',
|
|
emscripten_run_preload_plugins__sig: 'iiii',
|
|
emscripten_run_preload_plugins: function(file, onload, onerror) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
var _file = Pointer_stringify(file);
|
|
var data = FS.analyzePath(_file);
|
|
if (!data.exists) return -1;
|
|
FS.createPreloadedFile(
|
|
PATH.dirname(_file),
|
|
PATH.basename(_file),
|
|
new Uint8Array(data.object.contents), true, true,
|
|
function() {
|
|
if (onload) Module['dynCall_vi'](onload, file);
|
|
},
|
|
function() {
|
|
if (onerror) Module['dynCall_vi'](onerror, file);
|
|
},
|
|
true // don'tCreateFile - it's already there
|
|
);
|
|
return 0;
|
|
},
|
|
|
|
emscripten_run_preload_plugins_data__proxy: 'sync',
|
|
emscripten_run_preload_plugins_data__sig: 'viiiiii',
|
|
emscripten_run_preload_plugins_data: function(data, size, suffix, arg, onload, onerror) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
var _suffix = Pointer_stringify(suffix);
|
|
if (!Browser.asyncPrepareDataCounter) Browser.asyncPrepareDataCounter = 0;
|
|
var name = 'prepare_data_' + (Browser.asyncPrepareDataCounter++) + '.' + _suffix;
|
|
var lengthAsUTF8 = lengthBytesUTF8(name);
|
|
var cname = _malloc(lengthAsUTF8+1);
|
|
stringToUTF8(name, cname, lengthAsUTF8+1);
|
|
FS.createPreloadedFile(
|
|
'/',
|
|
name,
|
|
{{{ makeHEAPView('U8', 'data', 'data + size') }}},
|
|
true, true,
|
|
function() {
|
|
if (onload) Module['dynCall_vii'](onload, arg, cname);
|
|
},
|
|
function() {
|
|
if (onerror) Module['dynCall_vi'](onerror, arg);
|
|
},
|
|
true // don'tCreateFile - it's already there
|
|
);
|
|
},
|
|
|
|
// Callable from pthread, executes in pthread context.
|
|
emscripten_async_run_script__deps: ['emscripten_run_script'],
|
|
emscripten_async_run_script: function(script, millis) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
// TODO: cache these to avoid generating garbage
|
|
Browser.safeSetTimeout(function() {
|
|
_emscripten_run_script(script);
|
|
}, millis);
|
|
},
|
|
|
|
// TODO: currently not callable from a pthread, but immediately calls onerror() if not on main thread.
|
|
emscripten_async_load_script: function(url, onload, onerror) {
|
|
onload = getFuncWrapper(onload, 'v');
|
|
onerror = getFuncWrapper(onerror, 'v');
|
|
|
|
#if USE_PTHREADS
|
|
if (ENVIRONMENT_IS_PTHREAD) {
|
|
console.error('emscripten_async_load_script("' + Pointer_stringify(url) + '") failed, emscripten_async_load_script is currently not available in pthreads!');
|
|
return onerror ? onerror() : undefined;
|
|
}
|
|
#endif
|
|
Module['noExitRuntime'] = true;
|
|
|
|
assert(runDependencies === 0, 'async_load_script must be run when no other dependencies are active');
|
|
var script = document.createElement('script');
|
|
if (onload) {
|
|
script.onload = function script_onload() {
|
|
if (runDependencies > 0) {
|
|
dependenciesFulfilled = onload;
|
|
} else {
|
|
onload();
|
|
}
|
|
};
|
|
}
|
|
if (onerror) script.onerror = onerror;
|
|
script.src = Pointer_stringify(url);
|
|
document.body.appendChild(script);
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_get_main_loop_timing: function(mode, value) {
|
|
if (mode) {{{ makeSetValue('mode', 0, 'Browser.mainLoop.timingMode', 'i32') }}};
|
|
if (value) {{{ makeSetValue('value', 0, 'Browser.mainLoop.timingValue', 'i32') }}};
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_set_main_loop_timing: function(mode, value) {
|
|
Browser.mainLoop.timingMode = mode;
|
|
Browser.mainLoop.timingValue = value;
|
|
|
|
if (!Browser.mainLoop.func) {
|
|
#if ASSERTIONS
|
|
console.error('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.');
|
|
#endif
|
|
return 1; // Return non-zero on failure, can't set timing mode when there is no main loop.
|
|
}
|
|
|
|
if (mode == 0 /*EM_TIMING_SETTIMEOUT*/) {
|
|
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setTimeout() {
|
|
var timeUntilNextTick = Math.max(0, Browser.mainLoop.tickStartTime + value - _emscripten_get_now())|0;
|
|
setTimeout(Browser.mainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop
|
|
};
|
|
Browser.mainLoop.method = 'timeout';
|
|
} else if (mode == 1 /*EM_TIMING_RAF*/) {
|
|
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_rAF() {
|
|
Browser.requestAnimationFrame(Browser.mainLoop.runner);
|
|
};
|
|
Browser.mainLoop.method = 'rAF';
|
|
} else if (mode == 2 /*EM_TIMING_SETIMMEDIATE*/) {
|
|
if (typeof setImmediate === 'undefined') {
|
|
// Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed)
|
|
var setImmediates = [];
|
|
var emscriptenMainLoopMessageId = 'setimmediate';
|
|
function Browser_setImmediate_messageHandler(event) {
|
|
// When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events,
|
|
// so check for both cases.
|
|
if (event.data === emscriptenMainLoopMessageId || event.data.target === emscriptenMainLoopMessageId) {
|
|
event.stopPropagation();
|
|
setImmediates.shift()();
|
|
}
|
|
}
|
|
addEventListener("message", Browser_setImmediate_messageHandler, true);
|
|
setImmediate = function Browser_emulated_setImmediate(func) {
|
|
setImmediates.push(func);
|
|
if (ENVIRONMENT_IS_WORKER) {
|
|
if (Module['setImmediates'] === undefined) Module['setImmediates'] = [];
|
|
Module['setImmediates'].push(func);
|
|
postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js
|
|
} else postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself.
|
|
}
|
|
}
|
|
Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setImmediate() {
|
|
setImmediate(Browser.mainLoop.runner);
|
|
};
|
|
Browser.mainLoop.method = 'immediate';
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_set_main_loop__deps: ['emscripten_set_main_loop_timing', 'emscripten_get_now'],
|
|
emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop, arg, noSetTiming) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
assert(!Browser.mainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.');
|
|
|
|
Browser.mainLoop.func = func;
|
|
Browser.mainLoop.arg = arg;
|
|
|
|
var browserIterationFunc;
|
|
if (typeof arg !== 'undefined') {
|
|
browserIterationFunc = function() {
|
|
Module['dynCall_vi'](func, arg);
|
|
};
|
|
} else {
|
|
browserIterationFunc = function() {
|
|
Module['dynCall_v'](func);
|
|
};
|
|
}
|
|
|
|
var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop;
|
|
|
|
Browser.mainLoop.runner = function Browser_mainLoop_runner() {
|
|
if (ABORT) return;
|
|
if (Browser.mainLoop.queue.length > 0) {
|
|
var start = Date.now();
|
|
var blocker = Browser.mainLoop.queue.shift();
|
|
blocker.func(blocker.arg);
|
|
if (Browser.mainLoop.remainingBlockers) {
|
|
var remaining = Browser.mainLoop.remainingBlockers;
|
|
var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining);
|
|
if (blocker.counted) {
|
|
Browser.mainLoop.remainingBlockers = next;
|
|
} else {
|
|
// not counted, but move the progress along a tiny bit
|
|
next = next + 0.5; // do not steal all the next one's progress
|
|
Browser.mainLoop.remainingBlockers = (8*remaining + next)/9;
|
|
}
|
|
}
|
|
console.log('main loop blocker "' + blocker.name + '" took ' + (Date.now() - start) + ' ms'); //, left: ' + Browser.mainLoop.remainingBlockers);
|
|
Browser.mainLoop.updateStatus();
|
|
|
|
// catches pause/resume main loop from blocker execution
|
|
if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;
|
|
|
|
setTimeout(Browser.mainLoop.runner, 0);
|
|
return;
|
|
}
|
|
|
|
// catch pauses from non-main loop sources
|
|
if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;
|
|
|
|
// Implement very basic swap interval control
|
|
Browser.mainLoop.currentFrameNumber = Browser.mainLoop.currentFrameNumber + 1 | 0;
|
|
if (Browser.mainLoop.timingMode == 1/*EM_TIMING_RAF*/ && Browser.mainLoop.timingValue > 1 && Browser.mainLoop.currentFrameNumber % Browser.mainLoop.timingValue != 0) {
|
|
// Not the scheduled time to render this frame - skip.
|
|
Browser.mainLoop.scheduler();
|
|
return;
|
|
} else if (Browser.mainLoop.timingMode == 0/*EM_TIMING_SETTIMEOUT*/) {
|
|
Browser.mainLoop.tickStartTime = _emscripten_get_now();
|
|
}
|
|
|
|
// Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize
|
|
// VBO double-buffering and reduce GPU stalls.
|
|
#if USES_GL_EMULATION
|
|
GL.newRenderingFrameStarted();
|
|
#endif
|
|
|
|
#if OFFSCREENCANVAS_SUPPORT
|
|
// If the current GL context is an OffscreenCanvas, but it was initialized with implicit swap mode, perform the swap
|
|
// in behalf of the user.
|
|
if (typeof GL !== 'undefined' && GL.currentContext && !GL.currentContext.attributes.explicitSwapControl && GL.currentContext.GLctx.commit) {
|
|
GL.currentContext.GLctx.commit();
|
|
}
|
|
#endif
|
|
|
|
if (Browser.mainLoop.method === 'timeout' && Module.ctx) {
|
|
Module.printErr('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!');
|
|
Browser.mainLoop.method = ''; // just warn once per call to set main loop
|
|
}
|
|
|
|
Browser.mainLoop.runIter(browserIterationFunc);
|
|
|
|
#if STACK_OVERFLOW_CHECK
|
|
checkStackCookie();
|
|
#endif
|
|
|
|
// catch pauses from the main loop itself
|
|
if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;
|
|
|
|
// Queue new audio data. This is important to be right after the main loop invocation, so that we will immediately be able
|
|
// to queue the newest produced audio samples.
|
|
// TODO: Consider adding pre- and post- rAF callbacks so that GL.newRenderingFrameStarted() and SDL.audio.queueNewAudioData()
|
|
// do not need to be hardcoded into this function, but can be more generic.
|
|
if (typeof SDL === 'object' && SDL.audio && SDL.audio.queueNewAudioData) SDL.audio.queueNewAudioData();
|
|
|
|
Browser.mainLoop.scheduler();
|
|
}
|
|
|
|
if (!noSetTiming) {
|
|
if (fps && fps > 0) _emscripten_set_main_loop_timing(0/*EM_TIMING_SETTIMEOUT*/, 1000.0 / fps);
|
|
else _emscripten_set_main_loop_timing(1/*EM_TIMING_RAF*/, 1); // Do rAF by rendering each frame (no decimating)
|
|
|
|
Browser.mainLoop.scheduler();
|
|
}
|
|
|
|
if (simulateInfiniteLoop) {
|
|
throw 'SimulateInfiniteLoop';
|
|
}
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_set_main_loop_arg__deps: ['emscripten_set_main_loop'],
|
|
emscripten_set_main_loop_arg: function(func, arg, fps, simulateInfiniteLoop) {
|
|
_emscripten_set_main_loop(func, fps, simulateInfiniteLoop, arg);
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_cancel_main_loop: function() {
|
|
Browser.mainLoop.pause();
|
|
Browser.mainLoop.func = null;
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_pause_main_loop: function() {
|
|
Browser.mainLoop.pause();
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_resume_main_loop: function() {
|
|
Browser.mainLoop.resume();
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
_emscripten_push_main_loop_blocker: function(func, arg, name) {
|
|
Browser.mainLoop.queue.push({ func: function() {
|
|
Module['dynCall_vi'](func, arg);
|
|
}, name: Pointer_stringify(name), counted: true });
|
|
Browser.mainLoop.updateStatus();
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
_emscripten_push_uncounted_main_loop_blocker: function(func, arg, name) {
|
|
Browser.mainLoop.queue.push({ func: function() {
|
|
Module['dynCall_vi'](func, arg);
|
|
}, name: Pointer_stringify(name), counted: false });
|
|
Browser.mainLoop.updateStatus();
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_set_main_loop_expected_blockers: function(num) {
|
|
Browser.mainLoop.expectedBlockers = num;
|
|
Browser.mainLoop.remainingBlockers = num;
|
|
Browser.mainLoop.updateStatus();
|
|
},
|
|
|
|
// Runs natively in pthread, no __proxy needed.
|
|
emscripten_async_call: function(func, arg, millis) {
|
|
Module['noExitRuntime'] = true;
|
|
|
|
function wrapper() {
|
|
getFuncWrapper(func, 'vi')(arg);
|
|
}
|
|
|
|
if (millis >= 0) {
|
|
Browser.safeSetTimeout(wrapper, millis);
|
|
} else {
|
|
Browser.safeRequestAnimationFrame(wrapper);
|
|
}
|
|
},
|
|
|
|
// Callable in pthread without __proxy needed.
|
|
emscripten_exit_with_live_runtime: function() {
|
|
Module['noExitRuntime'] = true;
|
|
throw 'SimulateInfiniteLoop';
|
|
},
|
|
|
|
emscripten_force_exit__proxy: 'sync',
|
|
emscripten_force_exit__sig: 'vi',
|
|
emscripten_force_exit: function(status) {
|
|
#if NO_EXIT_RUNTIME
|
|
#if ASSERTIONS
|
|
warnOnce('emscripten_force_exit cannot actually shut down the runtime, as the build has NO_EXIT_RUNTIME set');
|
|
#endif
|
|
#endif
|
|
Module['noExitRuntime'] = false;
|
|
Module['exit'](status);
|
|
},
|
|
|
|
emscripten_get_device_pixel_ratio__proxy: 'sync',
|
|
emscripten_get_device_pixel_ratio__sig: 'd',
|
|
emscripten_get_device_pixel_ratio: function() {
|
|
return window.devicePixelRatio || 1.0;
|
|
},
|
|
|
|
emscripten_hide_mouse__proxy: 'sync',
|
|
emscripten_hide_mouse__sig: 'v',
|
|
emscripten_hide_mouse: function() {
|
|
var styleSheet = document.styleSheets[0];
|
|
var rules = styleSheet.cssRules;
|
|
for (var i = 0; i < rules.length; i++) {
|
|
if (rules[i].cssText.substr(0, 6) == 'canvas') {
|
|
styleSheet.deleteRule(i);
|
|
i--;
|
|
}
|
|
}
|
|
styleSheet.insertRule('canvas.emscripten { border: 1px solid black; cursor: none; }', 0);
|
|
},
|
|
|
|
emscripten_set_canvas_size__proxy: 'sync',
|
|
emscripten_set_canvas_size__sig: 'vii',
|
|
emscripten_set_canvas_size: function(width, height) {
|
|
Browser.setCanvasSize(width, height);
|
|
},
|
|
|
|
emscripten_get_canvas_size__proxy: 'sync',
|
|
emscripten_get_canvas_size__sig: 'viii',
|
|
emscripten_get_canvas_size: function(width, height, isFullscreen) {
|
|
var canvas = Module['canvas'];
|
|
{{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}};
|
|
{{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}};
|
|
{{{ makeSetValue('isFullscreen', '0', 'Browser.isFullscreen ? 1 : 0', 'i32') }}};
|
|
},
|
|
|
|
// To avoid creating worker parent->child chains, always proxies to execute on the main thread.
|
|
emscripten_create_worker__proxy: 'sync',
|
|
emscripten_create_worker__sig: 'ii',
|
|
emscripten_create_worker: function(url) {
|
|
url = Pointer_stringify(url);
|
|
var id = Browser.workers.length;
|
|
var info = {
|
|
worker: new Worker(url),
|
|
callbacks: [],
|
|
awaited: 0,
|
|
buffer: 0,
|
|
bufferSize: 0
|
|
};
|
|
info.worker.onmessage = function info_worker_onmessage(msg) {
|
|
if (ABORT) return;
|
|
var info = Browser.workers[id];
|
|
if (!info) return; // worker was destroyed meanwhile
|
|
var callbackId = msg.data['callbackId'];
|
|
var callbackInfo = info.callbacks[callbackId];
|
|
if (!callbackInfo) return; // no callback or callback removed meanwhile
|
|
// Don't trash our callback state if we expect additional calls.
|
|
if (msg.data['finalResponse']) {
|
|
info.awaited--;
|
|
info.callbacks[callbackId] = null; // TODO: reuse callbackIds, compress this
|
|
}
|
|
var data = msg.data['data'];
|
|
if (data) {
|
|
if (!data.byteLength) data = new Uint8Array(data);
|
|
if (!info.buffer || info.bufferSize < data.length) {
|
|
if (info.buffer) _free(info.buffer);
|
|
info.bufferSize = data.length;
|
|
info.buffer = _malloc(data.length);
|
|
}
|
|
HEAPU8.set(data, info.buffer);
|
|
callbackInfo.func(info.buffer, data.length, callbackInfo.arg);
|
|
} else {
|
|
callbackInfo.func(0, 0, callbackInfo.arg);
|
|
}
|
|
};
|
|
Browser.workers.push(info);
|
|
return id;
|
|
},
|
|
|
|
emscripten_destroy_worker__proxy: 'sync',
|
|
emscripten_destroy_worker__sig: 'vi',
|
|
emscripten_destroy_worker: function(id) {
|
|
var info = Browser.workers[id];
|
|
info.worker.terminate();
|
|
if (info.buffer) _free(info.buffer);
|
|
Browser.workers[id] = null;
|
|
},
|
|
|
|
emscripten_call_worker__proxy: 'sync',
|
|
emscripten_call_worker__sig: 'viiiiii',
|
|
emscripten_call_worker: function(id, funcName, data, size, callback, arg) {
|
|
Module['noExitRuntime'] = true; // should we only do this if there is a callback?
|
|
|
|
funcName = Pointer_stringify(funcName);
|
|
var info = Browser.workers[id];
|
|
var callbackId = -1;
|
|
if (callback) {
|
|
callbackId = info.callbacks.length;
|
|
info.callbacks.push({
|
|
func: getFuncWrapper(callback, 'viii'),
|
|
arg: arg
|
|
});
|
|
info.awaited++;
|
|
}
|
|
var transferObject = {
|
|
'funcName': funcName,
|
|
'callbackId': callbackId,
|
|
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
|
|
};
|
|
if (data) {
|
|
info.worker.postMessage(transferObject, [transferObject.data.buffer]);
|
|
} else {
|
|
info.worker.postMessage(transferObject);
|
|
}
|
|
},
|
|
|
|
emscripten_worker_respond_provisionally__proxy: 'sync',
|
|
emscripten_worker_respond_provisionally__sig: 'vii',
|
|
emscripten_worker_respond_provisionally: function(data, size) {
|
|
if (workerResponded) throw 'already responded with final response!';
|
|
var transferObject = {
|
|
'callbackId': workerCallbackId,
|
|
'finalResponse': false,
|
|
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
|
|
};
|
|
if (data) {
|
|
postMessage(transferObject, [transferObject.data.buffer]);
|
|
} else {
|
|
postMessage(transferObject);
|
|
}
|
|
},
|
|
|
|
emscripten_worker_respond__proxy: 'sync',
|
|
emscripten_worker_respond__sig: 'vii',
|
|
emscripten_worker_respond: function(data, size) {
|
|
if (workerResponded) throw 'already responded with final response!';
|
|
workerResponded = true;
|
|
var transferObject = {
|
|
'callbackId': workerCallbackId,
|
|
'finalResponse': true,
|
|
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
|
|
};
|
|
if (data) {
|
|
postMessage(transferObject, [transferObject.data.buffer]);
|
|
} else {
|
|
postMessage(transferObject);
|
|
}
|
|
},
|
|
|
|
emscripten_get_worker_queue_size__proxy: 'sync',
|
|
emscripten_get_worker_queue_size__sig: 'i',
|
|
emscripten_get_worker_queue_size: function(id) {
|
|
var info = Browser.workers[id];
|
|
if (!info) return -1;
|
|
return info.awaited;
|
|
},
|
|
|
|
emscripten_get_preloaded_image_data__deps: ['$PATH'],
|
|
emscripten_get_preloaded_image_data__proxy: 'sync',
|
|
emscripten_get_preloaded_image_data__sig: 'iiii',
|
|
emscripten_get_preloaded_image_data: function(path, w, h) {
|
|
if (typeof path === "number") {
|
|
path = Pointer_stringify(path);
|
|
}
|
|
|
|
path = PATH.resolve(path);
|
|
|
|
var canvas = Module["preloadedImages"][path];
|
|
if (canvas) {
|
|
var ctx = canvas.getContext("2d");
|
|
var image = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
var buf = _malloc(canvas.width * canvas.height * 4);
|
|
|
|
HEAPU8.set(image.data, buf);
|
|
|
|
{{{ makeSetValue('w', '0', 'canvas.width', 'i32') }}};
|
|
{{{ makeSetValue('h', '0', 'canvas.height', 'i32') }}};
|
|
return buf;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
emscripten_get_preloaded_image_data_from_FILE__deps: ['emscripten_get_preloaded_image_data'],
|
|
emscripten_get_preloaded_image_data_from_FILE__proxy: 'sync',
|
|
emscripten_get_preloaded_image_data_from_FILE__sig: 'iiii',
|
|
emscripten_get_preloaded_image_data_from_FILE: function(file, w, h) {
|
|
var fd = Module['_fileno'](file);
|
|
var stream = FS.getStream(fd);
|
|
if (stream) {
|
|
return _emscripten_get_preloaded_image_data(stream.path, w, h);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
autoAddDeps(LibraryBrowser, '$Browser');
|
|
|
|
mergeInto(LibraryManager.library, LibraryBrowser);
|
|
|
|
/* Useful stuff for browser debugging
|
|
|
|
function slowLog(label, text) {
|
|
if (!slowLog.labels) slowLog.labels = {};
|
|
if (!slowLog.labels[label]) slowLog.labels[label] = 0;
|
|
var now = Date.now();
|
|
if (now - slowLog.labels[label] > 1000) {
|
|
Module.print(label + ': ' + text);
|
|
slowLog.labels[label] = now;
|
|
}
|
|
}
|
|
|
|
*/
|
|
|