# HG changeset patch # User Lucas Thompson # Date 1494947817 -3600 # Node ID e433a2da0adaca5e0aee14534314531e8752074f # Parent 72673c954216ff84c7d1aa46a3a8a5ee8639fa87 Refactor the import library logic slightly to waterfall the loading of the libraries and list requests, and send one response when all libraries have been loaded. diff -r 72673c954216 -r e433a2da0ada src/app/feature-extraction-menu/feature-extraction-menu.component.ts --- a/src/app/feature-extraction-menu/feature-extraction-menu.component.ts Tue May 16 11:15:43 2017 +0100 +++ b/src/app/feature-extraction-menu/feature-extraction-menu.component.ts Tue May 16 16:16:57 2017 +0100 @@ -80,12 +80,9 @@ } ngOnInit() { - this.piperService.list().then(this.populateExtractors).then(() => { - this.piperService.load('pyin'); - // this.piperService.load('nnls-chroma'); - }); this.librariesUpdatedSubscription = this.piperService.librariesUpdated$.subscribe(this.populateExtractors); + this.piperService.list().then(this.populateExtractors); } extract(combinedKey: string): void { @@ -97,9 +94,7 @@ } load(): void { - this.piperService.updateAvailableLibraries().subscribe(res => { - Object.keys(res).forEach(key => this.piperService.load(key)); - }); + this.piperService.updateAvailableLibraries(); } ngOnDestroy(): void { diff -r 72673c954216 -r e433a2da0ada src/app/services/feature-extraction/FeatureExtractionWorker.ts --- a/src/app/services/feature-extraction/FeatureExtractionWorker.ts Tue May 16 11:15:43 2017 +0100 +++ b/src/app/services/feature-extraction/FeatureExtractionWorker.ts Tue May 16 16:16:57 2017 +0100 @@ -2,7 +2,7 @@ * Created by lucas on 01/12/2016. */ -import {PiperVampService, ListRequest, ListResponse, Service} from 'piper'; +import {PiperVampService, ListRequest, ListResponse} from 'piper'; import { SimpleRequest } from 'piper/HigherLevelUtilities'; @@ -33,6 +33,21 @@ type RequireJs = (libs: string[], callback: (...libs: any[]) => void) => void; type Factory = () => T; +function waterfall(tasks: (() => Promise)[]): Promise { + const reducer = (running: T[], next: Promise): Promise => { + return next.then(response => { + running = running.concat(response); + return running; + }); + }; + + return tasks.reduce((runningResponses, nextResponse) => { + return runningResponses.then(response => { + return reducer(response, nextResponse()); + }) + }, Promise.resolve([])); +} + class AggregateStreamingService implements StreamingService { private services: Map>; @@ -50,26 +65,21 @@ this.services.set(key, service); } + hasRemoteService(key: LibraryKey): boolean { + return this.services.has(key); + } + list(request: ListRequest): Promise { - //TODO refactor const listThunks: (() => Promise)[] = [ ...this.services.values() ].map(client => () => client().list({})); - const concatAvailable = (running: ListResponse, - nextResponse: Promise) - : Promise => { - return nextResponse.then(response => { - running.available = running.available.concat(response.available); - return running; - }); - }; - - return listThunks.reduce((runningResponses, nextResponse) => { - return runningResponses.then(response => { - return concatAvailable(response, nextResponse()); - }) - }, Promise.resolve({available: []})); + return waterfall(listThunks).then(responses => { + return responses.reduce((allAvailable, res) => { + allAvailable.available = allAvailable.available.concat(res.available); + return allAvailable; + }, {available: []}); + }) } process(request: SimpleRequest): Observable { @@ -124,10 +134,7 @@ constructor(workerScope: DedicatedWorkerGlobalScope, private requireJs: RequireJs) { this.workerScope = workerScope; - this.remoteLibraries = new Map([ - // ['nnls-chroma', 'assets/extractors/NNLSChroma.js'], - ['pyin', 'assets/extractors/PYin.umd.js'], - ]); + this.remoteLibraries = new Map(); this.service = new ThrottledReducingAggregateService(); this.setupImportLibraryListener(); this.server = new WebWorkerStreamingServer( @@ -139,40 +146,49 @@ private setupImportLibraryListener(): void { this.workerScope.onmessage = (ev: MessageEvent) => { - const sendResponse = (result) => { - console.warn(ev.data.method, ev.data); - this.workerScope.postMessage({ - method: ev.data.method, - result: result - }); - }; switch (ev.data.method) { - case 'import': - const key: LibraryKey = ev.data.params; - if (this.remoteLibraries.has(key)) { - this.requireJs([this.remoteLibraries.get(key)], (plugin) => { - - const service = () => { - // TODO a factory with more logic probably belongs in piper-js - const lib: any | EmscriptenModule = plugin.createLibrary(); - const isEmscriptenModule = typeof lib.cwrap === 'function'; - return new PiperStreamingService( - isEmscriptenModule ? new PiperVampService(lib) : lib // TODO - ); - }; - this.service.addService(key, service); - this.service.list({}).then(sendResponse); - }); - } else { - console.error('Non registered library key.'); // TODO handle error - } - break; case 'addRemoteLibraries': // TODO rename const available: AvailableLibraries = ev.data.params; - Object.keys(available).forEach(libraryKey => { - this.remoteLibraries.set(libraryKey, available[libraryKey]); + const importThunks = Object.keys(available).map(libraryKey => { + return () => { + this.remoteLibraries.set(libraryKey, available[libraryKey]); + return this.import(libraryKey).then(key => { + return key; + }); + }; }); + waterfall(importThunks).then(() => { + this.service.list({}).then(response => { + this.workerScope.postMessage({ + method: 'import', + result: response + }); + }); + }) } }; } + + private import(key: LibraryKey): Promise { // TODO return type? + return new Promise((res, rej) => { + if (this.remoteLibraries.has(key)) { + // TODO RequireJs can fail... need to reject the promise then + this.requireJs([this.remoteLibraries.get(key)], (plugin) => { + + const service = () => { + // TODO a factory with more logic probably belongs in piper-js + const lib: any | EmscriptenModule = plugin.createLibrary(); + const isEmscriptenModule = typeof lib.cwrap === 'function'; + return new PiperStreamingService( + isEmscriptenModule ? new PiperVampService(lib) : lib // TODO + ); + }; + this.service.addService(key, service); + res(key); + }); + } else { + rej('Invalid remote library key'); + } + }); + } } diff -r 72673c954216 -r e433a2da0ada src/app/services/feature-extraction/feature-extraction.service.ts --- a/src/app/services/feature-extraction/feature-extraction.service.ts Tue May 16 11:15:43 2017 +0100 +++ b/src/app/services/feature-extraction/feature-extraction.service.ts Tue May 16 16:16:57 2017 +0100 @@ -8,7 +8,7 @@ } from 'piper/HigherLevelUtilities'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; -import {Http, Response} from '@angular/http'; +import {Http} from '@angular/http'; import { countingIdProvider, WebWorkerStreamingClient @@ -63,7 +63,10 @@ } list(): Promise { - return this.client.list({}); + return this.client.list({}).then(response => { + this.librariesUpdated.next(response); + return response; + }); } extract(analysisItemId: string, request: SimpleRequest): Promise { @@ -87,20 +90,16 @@ }); } - updateAvailableLibraries(): Observable { - return this.http.get(this.repositoryUri) - .map(res => { - const map = res.json(); + updateAvailableLibraries(): void { + this.http.get(this.repositoryUri) + .toPromise() // just turn into a promise for now to subscribe / execute + .then(res => { this.worker.postMessage({ method: 'addRemoteLibraries', - params: map - }); - return map; + params: res.json() + }) }) - .catch((error: Response | any) => { - console.error(error); - return Observable.throw(error); - }); + .catch(console.error); // TODO Report error to user } load(libraryKey: string): void {