| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {defer, Deferred} from './deferred'; |
| |
| interface RemoteResponse { |
| id: number; |
| result: {}; |
| } |
| |
| /** |
| * A proxy for an object that lives on another thread. |
| */ |
| export class Remote { |
| private nextRequestId: number; |
| private port: MessagePort; |
| // tslint:disable-next-line no-any |
| private deferredRequests: Map<number, Deferred<any>>; |
| |
| constructor(port: MessagePort) { |
| this.nextRequestId = 0; |
| this.deferredRequests = new Map(); |
| this.port = port; |
| this.port.onmessage = (event: MessageEvent) => { |
| this.receive(event.data); |
| }; |
| } |
| |
| /** |
| * Invoke method with name |method| with |args| on the remote object. |
| * Optionally set |transferList| to transfer those objects. |
| */ |
| // tslint:disable-next-line no-any |
| send<T extends any>( |
| method: string, |
| args: Array<{}>, transferList?: Transferable[]): Promise<T> { |
| const d = defer<T>(); |
| this.deferredRequests.set(this.nextRequestId, d); |
| const message = { |
| responseId: this.nextRequestId, |
| method, |
| args, |
| }; |
| if (transferList === undefined) { |
| this.port.postMessage(message); |
| } else { |
| this.port.postMessage(message, transferList); |
| } |
| this.nextRequestId += 1; |
| return d; |
| } |
| |
| private receive(response: RemoteResponse): void { |
| const d = this.deferredRequests.get(response.id); |
| if (!d) throw new Error(`No deferred response with ID ${response.id}`); |
| this.deferredRequests.delete(response.id); |
| d.resolve(response.result); |
| } |
| } |
| |
| /** |
| * Given a MessagePort |port| where the other end is owned by a Remote |
| * (see above) turn each incoming MessageEvent into a call on |handler| |
| * and post the result back to the calling thread. |
| */ |
| export function forwardRemoteCalls( |
| port: MessagePort, |
| // tslint:disable-next-line no-any |
| handler: {[key: string]: any}) { |
| port.onmessage = (msg: MessageEvent) => { |
| const method = msg.data.method; |
| const id = msg.data.responseId; |
| const args = msg.data.args || []; |
| if (method === undefined || id === undefined) { |
| throw new Error(`Invalid call method: ${method} id: ${id}`); |
| } |
| |
| if (!(handler[method] instanceof Function)) { |
| throw new Error(`Method not known: ${method}(${args})`); |
| } |
| |
| const result = handler[method].apply(handler, args); |
| const transferList = []; |
| |
| if (result !== undefined && result.port instanceof MessagePort) { |
| transferList.push(result.port); |
| } |
| |
| port.postMessage( |
| { |
| id, |
| result, |
| }, |
| transferList); |
| }; |
| } |