Offer a promise for extension worker init

This commit is contained in:
Christopher Willis-Ford 2017-08-22 14:28:38 -07:00
parent 403adb743c
commit 64bde25d22
2 changed files with 62 additions and 20 deletions

View file

@ -28,16 +28,18 @@ const BlockType = require('./block-type');
* @property {string} color1 - the primary color for this category, in '#rrggbb' format * @property {string} color1 - the primary color for this category, in '#rrggbb' format
* @property {string} color2 - the secondary color for this category, in '#rrggbb' format * @property {string} color2 - the secondary color for this category, in '#rrggbb' format
* @property {string} color3 - the tertiary color for this category, in '#rrggbb' format * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
* @property {Array.<BlockInfo> block - the blocks in this category
*/
/**
* @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
* @property {string} extensionURL - the URL of the extension to be loaded by this worker
* @property {Function} resolve - function to call on successful worker startup
* @property {Function} reject - function to call on failed worker startup
*/ */
class ExtensionManager { class ExtensionManager {
constructor () { constructor () {
/**
* The list of current active extension workers.
* @type {Array.<ExtensionWorker>}
*/
this.workers = [];
/** /**
* The ID number to provide to the next extension worker. * The ID number to provide to the next extension worker.
* @type {int} * @type {int}
@ -45,10 +47,18 @@ class ExtensionManager {
this.nextExtensionWorker = 0; this.nextExtensionWorker = 0;
/** /**
* The list of extension URLs which have been requested but not yet loaded in a worker. * FIFO queue of extensions which have been requested but not yet loaded in a worker,
* @type {Array} * along with promise resolution functions to call once the worker is ready or failed.
*
* @type {Array.<PendingExtensionWorker>}
*/ */
this.pendingExtensionURLs = []; this.pendingExtensions = [];
/**
* Map of worker ID to workers which have been allocated but have not yet finished initialization.
* @type {Array.<PendingExtensionWorker>}
*/
this.pendingWorkers = [];
dispatch.setService('extensions', this).catch(e => { dispatch.setService('extensions', this).catch(e => {
log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`); log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
@ -56,21 +66,29 @@ class ExtensionManager {
} }
foo () { foo () {
this.loadExtensionURL('extensions/example-extension.js'); return this.loadExtensionURL('extensions/example-extension.js');
} }
/**
* Load an extension by URL
* @param {string} extensionURL - the URL for the extension to load
* @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
*/
loadExtensionURL (extensionURL) { loadExtensionURL (extensionURL) {
// If we `require` this at the global level it breaks non-webpack targets, including tests return new Promise((resolve, reject) => {
const ExtensionWorker = require('worker-loader!./extension-worker'); // If we `require` this at the global level it breaks non-webpack targets, including tests
const ExtensionWorker = require('worker-loader!./extension-worker');
this.pendingExtensionURLs.push(extensionURL); this.pendingExtensions.push({extensionURL, resolve, reject});
dispatch.addWorker(new ExtensionWorker()); dispatch.addWorker(new ExtensionWorker());
});
} }
allocateWorker () { allocateWorker () {
const id = this.nextExtensionWorker++; const id = this.nextExtensionWorker++;
const extFile = this.pendingExtensionURLs.shift(); const workerInfo = this.pendingExtensions.shift();
return [id, extFile]; this.pendingWorkers[id] = workerInfo;
return [id, workerInfo.extensionURL];
} }
registerExtensionService (serviceName) { registerExtensionService (serviceName) {
@ -79,6 +97,16 @@ class ExtensionManager {
}); });
} }
onWorkerInit (id, e) {
const workerInfo = this.pendingWorkers[id];
delete this.pendingWorkers[id];
if (e) {
workerInfo.reject(e);
} else {
workerInfo.resolve(id);
}
}
_registerExtensionInfo (serviceName, extensionInfo) { _registerExtensionInfo (serviceName, extensionInfo) {
extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo); extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => { dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
@ -115,7 +143,7 @@ class ExtensionManager {
result.push(this._prepareBlockInfo(serviceName, blockInfo)); result.push(this._prepareBlockInfo(serviceName, blockInfo));
} catch (e) { } catch (e) {
// TODO: more meaningful error reporting // TODO: more meaningful error reporting
log.error(`Skipping malformed block: ${e}`); log.error(`Skipping malformed block: ${JSON.stringify(e)}`);
} }
return result; return result;
}, []); }, []);

View file

@ -8,13 +8,23 @@ class ExtensionWorker {
constructor () { constructor () {
this.nextExtensionId = 0; this.nextExtensionId = 0;
this.initialRegistrations = [];
dispatch.waitForConnection.then(() => { dispatch.waitForConnection.then(() => {
dispatch.call('extensions', 'allocateWorker').then(x => { dispatch.call('extensions', 'allocateWorker').then(x => {
const [id, extension] = x; const [id, extension] = x;
this.workerId = id; this.workerId = id;
// TODO: catch and report any exceptions here try {
importScripts(extension); importScripts(extension);
const initialRegistrations = this.initialRegistrations;
this.initialRegistrations = null;
Promise.all(initialRegistrations).then(() => dispatch.call('extensions', 'onWorkerInit', id));
} catch (e) {
dispatch.call('extensions', 'onWorkerInit', id, e);
}
}); });
}); });
@ -25,8 +35,12 @@ class ExtensionWorker {
const extensionId = this.nextExtensionId++; const extensionId = this.nextExtensionId++;
this.extensions.push(extensionObject); this.extensions.push(extensionObject);
const serviceName = `extension.${this.workerId}.${extensionId}`; const serviceName = `extension.${this.workerId}.${extensionId}`;
return dispatch.setService(serviceName, extensionObject) const promise = dispatch.setService(serviceName, extensionObject)
.then(() => dispatch.call('extensions', 'registerExtensionService', serviceName)); .then(() => dispatch.call('extensions', 'registerExtensionService', serviceName));
if (this.initialRegistrations) {
this.initialRegistrations.push(promise);
}
return promise;
} }
} }