Commit 8e8ac26b authored by Jason Stafford's avatar Jason Stafford

first cut at supporting web workers

parent 61512f17
...@@ -183,7 +183,7 @@ EM_JS(void, hiwire_push_object_pair, (int idobj, int idkey, int idval), { ...@@ -183,7 +183,7 @@ EM_JS(void, hiwire_push_object_pair, (int idobj, int idkey, int idval), {
EM_JS(int, hiwire_get_global, (int idname), { EM_JS(int, hiwire_get_global, (int idname), {
var jsname = UTF8ToString(idname); var jsname = UTF8ToString(idname);
return Module.hiwire_new_value(window[jsname]); return Module.hiwire_new_value(self[jsname]);
}); });
EM_JS(int, hiwire_get_member_string, (int idobj, int idkey), { EM_JS(int, hiwire_get_member_string, (int idobj, int idkey), {
......
...@@ -6,7 +6,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -6,7 +6,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// This is filled in by the Makefile to be either a local file or the // This is filled in by the Makefile to be either a local file or the
// deployed location. TODO: This should be done in a less hacky // deployed location. TODO: This should be done in a less hacky
// way. // way.
var baseURL = window.languagePluginUrl || '{{DEPLOY}}'; var baseURL = self.languagePluginUrl || '{{DEPLOY}}';
baseURL = baseURL.substr(0, baseURL.lastIndexOf('/')) + '/'; baseURL = baseURL.substr(0, baseURL.lastIndexOf('/')) + '/';
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
...@@ -50,7 +50,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -50,7 +50,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
} catch { } catch {
return; return;
} }
for (entry of dirs) { for (let entry of dirs) {
if (entry.startsWith('.')) { if (entry.startsWith('.')) {
continue; continue;
} }
...@@ -76,10 +76,31 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -76,10 +76,31 @@ var languagePluginLoader = new Promise((resolve, reject) => {
} }
// clang-format on // clang-format on
function loadScript(url, onload, onerror) {
if (self.document) { // browser
const script = self.document.createElement('script');
script.src = url;
script.onload = (e) => {
onload();
};
script.onerror = (e) => {
onerror();
};
self.document.body.appendChild(script);
} else if (self.importScripts) { // webworker
try {
self.importScripts(url);
onload();
} catch {
onerror();
}
}
}
let _loadPackage = (names, messageCallback) => { let _loadPackage = (names, messageCallback) => {
// DFS to find all dependencies of the requested packages // DFS to find all dependencies of the requested packages
let packages = window.pyodide._module.packages.dependencies; let packages = self.pyodide._module.packages.dependencies;
let loadedPackages = window.pyodide.loadedPackages; let loadedPackages = self.pyodide.loadedPackages;
let queue = [].concat(names || []); let queue = [].concat(names || []);
let toLoad = new Array(); let toLoad = new Array();
while (queue.length) { while (queue.length) {
...@@ -124,7 +145,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -124,7 +145,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
} }
} }
window.pyodide._module.locateFile = (path) => { self.pyodide._module.locateFile = (path) => {
// handle packages loaded from custom URLs // handle packages loaded from custom URLs
let package = path.replace(/\.data$/, ""); let package = path.replace(/\.data$/, "");
if (package in toLoad) { if (package in toLoad) {
...@@ -152,14 +173,14 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -152,14 +173,14 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// exactly "toLoad * 2" times. // exactly "toLoad * 2" times.
var packageCounter = Object.keys(toLoad).length * 2; var packageCounter = Object.keys(toLoad).length * 2;
window.pyodide._module.monitorRunDependencies = () => { self.pyodide._module.monitorRunDependencies = () => {
packageCounter--; packageCounter--;
if (packageCounter === 0) { if (packageCounter === 0) {
for (let package in toLoad) { for (let package in toLoad) {
window.pyodide.loadedPackages[package] = toLoad[package]; self.pyodide.loadedPackages[package] = toLoad[package];
} }
delete window.pyodide._module.monitorRunDependencies; delete self.pyodide._module.monitorRunDependencies;
window.removeEventListener('error', windowErrorHandler); self.removeEventListener('error', windowErrorHandler);
if (!isFirefox) { if (!isFirefox) {
preloadWasm().then(() => {resolve(`Loaded ${packageList}`)}); preloadWasm().then(() => {resolve(`Loaded ${packageList}`)});
} else { } else {
...@@ -171,42 +192,41 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -171,42 +192,41 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// Add a handler for any exceptions that are thrown in the process of // Add a handler for any exceptions that are thrown in the process of
// loading a package // loading a package
var windowErrorHandler = (err) => { var windowErrorHandler = (err) => {
delete window.pyodide._module.monitorRunDependencies; delete self.pyodide._module.monitorRunDependencies;
window.removeEventListener('error', windowErrorHandler); self.removeEventListener('error', windowErrorHandler);
// Set up a new Promise chain, since this one failed // Set up a new Promise chain, since this one failed
loadPackagePromise = new Promise((resolve) => resolve()); loadPackagePromise = new Promise((resolve) => resolve());
reject(err.message); reject(err.message);
}; };
window.addEventListener('error', windowErrorHandler); self.addEventListener('error', windowErrorHandler);
for (let package in toLoad) { for (let package in toLoad) {
let script = document.createElement('script'); let scriptSrc;
let package_uri = toLoad[package]; let package_uri = toLoad[package];
if (package_uri == 'default channel') { if (package_uri == 'default channel') {
script.src = `${baseURL}${package}.js`; scriptSrc = `${baseURL}${package}.js`;
} else { } else {
script.src = `${package_uri}`; scriptSrc = `${package_uri}`;
} }
script.onerror = (e) => { loadScript(scriptSrc, () => {}, () => {
// If the package_uri fails to load, call monitorRunDependencies twice // If the package_uri fails to load, call monitorRunDependencies twice
// (so packageCounter will still hit 0 and finish loading), and remove // (so packageCounter will still hit 0 and finish loading), and remove
// the package from toLoad so we don't mark it as loaded. // the package from toLoad so we don't mark it as loaded.
console.error(`Couldn't load package from URL ${script.src}`) console.error(`Couldn't load package from URL ${scriptSrc}`)
let index = toLoad.indexOf(package); let index = toLoad.indexOf(package);
if (index !== -1) { if (index !== -1) {
toLoad.splice(index, 1); toLoad.splice(index, 1);
} }
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
window.pyodide._module.monitorRunDependencies(); self.pyodide._module.monitorRunDependencies();
} }
}; });
document.body.appendChild(script);
} }
// We have to invalidate Python's import caches, or it won't // We have to invalidate Python's import caches, or it won't
// see the new files. This is done here so it happens in parallel // see the new files. This is done here so it happens in parallel
// with the fetching over the network. // with the fetching over the network.
window.pyodide.runPython('import importlib as _importlib\n' + self.pyodide.runPython('import importlib as _importlib\n' +
'_importlib.invalidate_caches()\n'); '_importlib.invalidate_caches()\n');
}); });
...@@ -275,7 +295,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -275,7 +295,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
// Loading Pyodide // Loading Pyodide
let wasmURL = `${baseURL}pyodide.asm.wasm`; let wasmURL = `${baseURL}pyodide.asm.wasm`;
let Module = {}; let Module = {};
window.Module = Module; self.Module = Module;
Module.noImageDecoding = true; Module.noImageDecoding = true;
Module.noAudioDecoding = true; Module.noAudioDecoding = true;
...@@ -303,15 +323,15 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -303,15 +323,15 @@ var languagePluginLoader = new Promise((resolve, reject) => {
Module.locateFile = (path) => baseURL + path; Module.locateFile = (path) => baseURL + path;
var postRunPromise = new Promise((resolve, reject) => { var postRunPromise = new Promise((resolve, reject) => {
Module.postRun = () => { Module.postRun = () => {
delete window.Module; delete self.Module;
fetch(`${baseURL}packages.json`) fetch(`${baseURL}packages.json`)
.then((response) => response.json()) .then((response) => response.json())
.then((json) => { .then((json) => {
fixRecursionLimit(window.pyodide); fixRecursionLimit(self.pyodide);
window.pyodide.globals = self.pyodide.globals =
window.pyodide.runPython('import sys\nsys.modules["__main__"]'); self.pyodide.runPython('import sys\nsys.modules["__main__"]');
window.pyodide = makePublicAPI(window.pyodide, PUBLIC_API); self.pyodide = makePublicAPI(self.pyodide, PUBLIC_API);
window.pyodide._module.packages = json; self.pyodide._module.packages = json;
resolve(); resolve();
}); });
}; };
...@@ -329,28 +349,23 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -329,28 +349,23 @@ var languagePluginLoader = new Promise((resolve, reject) => {
Promise.all([ postRunPromise, dataLoadPromise ]).then(() => resolve()); Promise.all([ postRunPromise, dataLoadPromise ]).then(() => resolve());
let data_script = document.createElement('script'); const data_script_src = `${baseURL}pyodide.asm.data.js`;
data_script.src = `${baseURL}pyodide.asm.data.js`; loadScript(data_script_src, () => {
data_script.onload = (event) => { const scriptSrc = `${baseURL}pyodide.asm.js`;
let script = document.createElement('script'); loadScript(scriptSrc, () => {
script.src = `${baseURL}pyodide.asm.js`;
script.onload = () => {
// The emscripten module needs to be at this location for the core // The emscripten module needs to be at this location for the core
// filesystem to install itself. Once that's complete, it will be replaced // filesystem to install itself. Once that's complete, it will be replaced
// by the call to `makePublicAPI` with a more limited public API. // by the call to `makePublicAPI` with a more limited public API.
window.pyodide = pyodide(Module); self.pyodide = pyodide(Module);
window.pyodide.loadedPackages = new Array(); self.pyodide.loadedPackages = new Array();
window.pyodide.loadPackage = loadPackage; self.pyodide.loadPackage = loadPackage;
}; }, () => {});
document.head.appendChild(script); }, () => {});
};
document.head.appendChild(data_script);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Iodide-specific functionality, that doesn't make sense // Iodide-specific functionality, that doesn't make sense
// if not using with Iodide. // if not using with Iodide.
if (window.iodide !== undefined) { if (self.iodide !== undefined) {
// Load the custom CSS for Pyodide // Load the custom CSS for Pyodide
let link = document.createElement('link'); let link = document.createElement('link');
link.rel = 'stylesheet'; link.rel = 'stylesheet';
...@@ -359,7 +374,7 @@ var languagePluginLoader = new Promise((resolve, reject) => { ...@@ -359,7 +374,7 @@ var languagePluginLoader = new Promise((resolve, reject) => {
document.getElementsByTagName('head')[0].appendChild(link); document.getElementsByTagName('head')[0].appendChild(link);
// Add a custom output handler for Python objects // Add a custom output handler for Python objects
window.iodide.addOutputHandler({ self.iodide.addOutputHandler({
shouldHandle : (val) => { shouldHandle : (val) => {
return (typeof val === 'function' && return (typeof val === 'function' &&
pyodide._module.PyProxy.isPyProxy(val)); pyodide._module.PyProxy.isPyProxy(val));
......
...@@ -159,10 +159,10 @@ EM_JS(int, pyproxy_init, (), { ...@@ -159,10 +159,10 @@ EM_JS(int, pyproxy_init, (), {
get: function (jsobj, jskey) { get: function (jsobj, jskey) {
if (jskey === 'toString') { if (jskey === 'toString') {
return function() { return function() {
if (window.pyodide.repr === undefined) { if (self.pyodide.repr === undefined) {
window.pyodide.repr = window.pyodide.pyimport('repr'); self.pyodide.repr = self.pyodide.pyimport('repr');
} }
return window.pyodide.repr(jsobj); return self.pyodide.repr(jsobj);
} }
} else if (jskey === '$$') { } else if (jskey === '$$') {
return jsobj['$$']; return jsobj['$$'];
......
...@@ -88,7 +88,7 @@ EM_JS(int, runpython_init_js, (), { ...@@ -88,7 +88,7 @@ EM_JS(int, runpython_init_js, (), {
if (jsimports.length) { if (jsimports.length) {
var packageNames = var packageNames =
window.pyodide._module.packages.import_name_to_package_name; self.pyodide._module.packages.import_name_to_package_name;
var packages = {}; var packages = {};
for (var i = 0; i < jsimports.length; ++i) { for (var i = 0; i < jsimports.length; ++i) {
var name = jsimports[i]; var name = jsimports[i];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment