Skip to content

Commit 1f86c32

Browse files
committed
Add support for calling back from web workers
1 parent 144e913 commit 1f86c32

8 files changed

Lines changed: 215 additions & 154 deletions

File tree

src/vs/base/common/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,31 @@ export function getAllPropertyNames(obj: object): string[] {
169169
return res;
170170
}
171171

172+
export function getAllMethodNames(obj: object): string[] {
173+
let methods: string[] = [];
174+
for (const prop of getAllPropertyNames(obj)) {
175+
if (typeof obj[prop] === 'function') {
176+
methods.push(prop);
177+
}
178+
}
179+
return methods;
180+
}
181+
182+
export function createProxyObject<T extends object>(methodNames: string[], invoke: (method: string, args: any[]) => any): T {
183+
const createProxyMethod = (method: string): () => any => {
184+
return function () {
185+
const args = Array.prototype.slice.call(arguments, 0);
186+
return invoke(method, args);
187+
};
188+
};
189+
190+
let result = {} as T;
191+
for (const methodName of methodNames) {
192+
(<any>result)[methodName] = createProxyMethod(methodName);
193+
}
194+
return result;
195+
}
196+
172197
/**
173198
* Converts null to undefined, passes all other values through.
174199
*/

src/vs/base/common/worker/simpleWorker.ts

Lines changed: 57 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { transformErrorForSerialization } from 'vs/base/common/errors';
77
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
88
import { isWeb } from 'vs/base/common/platform';
9-
import { getAllPropertyNames } from 'vs/base/common/types';
9+
import * as types from 'vs/base/common/types';
1010

1111
const INITIALIZE = '$initialize';
1212

@@ -173,17 +173,22 @@ class SimpleWorkerProtocol {
173173
}
174174
}
175175

176+
export interface IWorkerClient<W> {
177+
getProxyObject(): Promise<W>;
178+
dispose(): void;
179+
}
180+
176181
/**
177182
* Main thread side
178183
*/
179-
export class SimpleWorkerClient<T> extends Disposable {
184+
export class SimpleWorkerClient<W extends object, H extends object> extends Disposable implements IWorkerClient<W> {
180185

181-
private _worker: IWorker;
182-
private _onModuleLoaded: Promise<string[]>;
183-
private _protocol: SimpleWorkerProtocol;
184-
private _lazyProxy: Promise<T>;
186+
private readonly _worker: IWorker;
187+
private readonly _onModuleLoaded: Promise<string[]>;
188+
private readonly _protocol: SimpleWorkerProtocol;
189+
private readonly _lazyProxy: Promise<W>;
185190

186-
constructor(workerFactory: IWorkerFactory, moduleId: string) {
191+
constructor(workerFactory: IWorkerFactory, moduleId: string, host: H) {
187192
super();
188193

189194
let lazyProxyReject: ((err: any) => void) | null = null;
@@ -207,8 +212,15 @@ export class SimpleWorkerClient<T> extends Disposable {
207212
this._worker.postMessage(msg);
208213
},
209214
handleMessage: (method: string, args: any[]): Promise<any> => {
210-
// Intentionally not supporting worker -> main requests
211-
return Promise.resolve(null);
215+
if (typeof host[method] !== 'function') {
216+
return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
217+
}
218+
219+
try {
220+
return Promise.resolve(host[method].apply(host, args));
221+
} catch (e) {
222+
return Promise.reject(e);
223+
}
212224
}
213225
});
214226
this._protocol.setWorkerId(this._worker.getId());
@@ -223,41 +235,33 @@ export class SimpleWorkerClient<T> extends Disposable {
223235
loaderConfiguration = (<any>self).requirejs.s.contexts._.config;
224236
}
225237

238+
const hostMethods = types.getAllMethodNames(host);
239+
226240
// Send initialize message
227241
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
228242
this._worker.getId(),
243+
loaderConfiguration,
229244
moduleId,
230-
loaderConfiguration
245+
hostMethods,
231246
]);
232247

233-
this._lazyProxy = new Promise<T>((resolve, reject) => {
248+
// Create proxy to loaded code
249+
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
250+
return this._request(method, args);
251+
};
252+
253+
this._lazyProxy = new Promise<W>((resolve, reject) => {
234254
lazyProxyReject = reject;
235255
this._onModuleLoaded.then((availableMethods: string[]) => {
236-
let proxy = <T>{};
237-
for (const methodName of availableMethods) {
238-
(proxy as any)[methodName] = createProxyMethod(methodName, proxyMethodRequest);
239-
}
240-
resolve(proxy);
256+
resolve(types.createProxyObject<W>(availableMethods, proxyMethodRequest));
241257
}, (e) => {
242258
reject(e);
243259
this._onError('Worker failed to load ' + moduleId, e);
244260
});
245261
});
246-
247-
// Create proxy to loaded code
248-
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
249-
return this._request(method, args);
250-
};
251-
252-
const createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => Promise<any>): () => Promise<any> => {
253-
return function () {
254-
let args = Array.prototype.slice.call(arguments, 0);
255-
return proxyMethodRequest(method, args);
256-
};
257-
};
258262
}
259263

260-
public getProxyObject(): Promise<T> {
264+
public getProxyObject(): Promise<W> {
261265
return this._lazyProxy;
262266
}
263267

@@ -280,16 +284,22 @@ export interface IRequestHandler {
280284
[prop: string]: any;
281285
}
282286

287+
export interface IRequestHandlerFactory<H> {
288+
(host: H): IRequestHandler;
289+
}
290+
283291
/**
284292
* Worker side
285293
*/
286-
export class SimpleWorkerServer {
294+
export class SimpleWorkerServer<H extends object> {
287295

296+
private _requestHandlerFactory: IRequestHandlerFactory<H> | null;
288297
private _requestHandler: IRequestHandler | null;
289298
private _protocol: SimpleWorkerProtocol;
290299

291-
constructor(postSerializedMessage: (msg: string) => void, requestHandler: IRequestHandler | null) {
292-
this._requestHandler = requestHandler;
300+
constructor(postSerializedMessage: (msg: string) => void, requestHandlerFactory: IRequestHandlerFactory<H> | null) {
301+
this._requestHandlerFactory = requestHandlerFactory;
302+
this._requestHandler = null;
293303
this._protocol = new SimpleWorkerProtocol({
294304
sendMessage: (msg: string): void => {
295305
postSerializedMessage(msg);
@@ -304,7 +314,7 @@ export class SimpleWorkerServer {
304314

305315
private _handleMessage(method: string, args: any[]): Promise<any> {
306316
if (method === INITIALIZE) {
307-
return this.initialize(<number>args[0], <string>args[1], <any>args[2]);
317+
return this.initialize(<number>args[0], <any>args[1], <string>args[2], <string[]>args[3]);
308318
}
309319

310320
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
@@ -318,18 +328,19 @@ export class SimpleWorkerServer {
318328
}
319329
}
320330

321-
private initialize(workerId: number, moduleId: string, loaderConfig: any): Promise<string[]> {
331+
private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise<string[]> {
322332
this._protocol.setWorkerId(workerId);
323333

324-
if (this._requestHandler) {
334+
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
335+
return this._protocol.sendMessage(method, args);
336+
};
337+
338+
const hostProxy = types.createProxyObject<H>(hostMethods, proxyMethodRequest);
339+
340+
if (this._requestHandlerFactory) {
325341
// static request handler
326-
let methods: string[] = [];
327-
for (const prop of getAllPropertyNames(this._requestHandler)) {
328-
if (typeof this._requestHandler[prop] === 'function') {
329-
methods.push(prop);
330-
}
331-
}
332-
return Promise.resolve(methods);
342+
this._requestHandler = this._requestHandlerFactory(hostProxy);
343+
return Promise.resolve(types.getAllMethodNames(this._requestHandler));
333344
}
334345

335346
if (loaderConfig) {
@@ -350,23 +361,15 @@ export class SimpleWorkerServer {
350361

351362
return new Promise<string[]>((resolve, reject) => {
352363
// Use the global require to be sure to get the global config
353-
(<any>self).require([moduleId], (...result: any[]) => {
354-
let handlerModule = result[0];
355-
this._requestHandler = handlerModule.create();
364+
(<any>self).require([moduleId], (module: { create: IRequestHandlerFactory<H> }) => {
365+
this._requestHandler = module.create(hostProxy);
356366

357367
if (!this._requestHandler) {
358368
reject(new Error(`No RequestHandler!`));
359369
return;
360370
}
361371

362-
let methods: string[] = [];
363-
for (const prop of getAllPropertyNames(this._requestHandler)) {
364-
if (typeof this._requestHandler[prop] === 'function') {
365-
methods.push(prop);
366-
}
367-
}
368-
369-
resolve(methods);
372+
resolve(types.getAllMethodNames(this._requestHandler));
370373
}, reject);
371374
});
372375
}
@@ -375,6 +378,6 @@ export class SimpleWorkerServer {
375378
/**
376379
* Called on the worker side
377380
*/
378-
export function create(postMessage: (msg: string) => void): SimpleWorkerServer {
381+
export function create(postMessage: (msg: string) => void): SimpleWorkerServer<any> {
379382
return new SimpleWorkerServer(postMessage, null);
380383
}

0 commit comments

Comments
 (0)