1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {defer, Deferred} from './deferred'; 16 17interface RemoteResponse { 18 id: number; 19 result: {}; 20} 21 22/** 23 * A proxy for an object that lives on another thread. 24 */ 25export class Remote { 26 private nextRequestId: number; 27 private port: MessagePort; 28 // tslint:disable-next-line no-any 29 private deferredRequests: Map<number, Deferred<any>>; 30 31 constructor(port: MessagePort) { 32 this.nextRequestId = 0; 33 this.deferredRequests = new Map(); 34 this.port = port; 35 this.port.onmessage = (event: MessageEvent) => { 36 this.receive(event.data); 37 }; 38 } 39 40 /** 41 * Invoke method with name |method| with |args| on the remote object. 42 * Optionally set |transferList| to transfer those objects. 43 */ 44 // tslint:disable-next-line no-any 45 send<T extends any>( 46 method: string, 47 args: Array<{}>, transferList?: Transferable[]): Promise<T> { 48 const d = defer<T>(); 49 this.deferredRequests.set(this.nextRequestId, d); 50 const message = { 51 responseId: this.nextRequestId, 52 method, 53 args, 54 }; 55 if (transferList === undefined) { 56 this.port.postMessage(message); 57 } else { 58 this.port.postMessage(message, transferList); 59 } 60 this.nextRequestId += 1; 61 return d; 62 } 63 64 private receive(response: RemoteResponse): void { 65 const d = this.deferredRequests.get(response.id); 66 if (!d) throw new Error(`No deferred response with ID ${response.id}`); 67 this.deferredRequests.delete(response.id); 68 d.resolve(response.result); 69 } 70} 71 72/** 73 * Given a MessagePort |port| where the other end is owned by a Remote 74 * (see above) turn each incoming MessageEvent into a call on |handler| 75 * and post the result back to the calling thread. 76 */ 77export function forwardRemoteCalls( 78 port: MessagePort, 79 // tslint:disable-next-line no-any 80 handler: {[key: string]: any}) { 81 port.onmessage = (msg: MessageEvent) => { 82 const method = msg.data.method; 83 const id = msg.data.responseId; 84 const args = msg.data.args || []; 85 if (method === undefined || id === undefined) { 86 throw new Error(`Invalid call method: ${method} id: ${id}`); 87 } 88 89 if (!(handler[method] instanceof Function)) { 90 throw new Error(`Method not known: ${method}(${args})`); 91 } 92 93 const result = handler[method].apply(handler, args); 94 const transferList = []; 95 96 if (result !== undefined && result.port instanceof MessagePort) { 97 transferList.push(result.port); 98 } 99 100 port.postMessage( 101 { 102 id, 103 result, 104 }, 105 transferList); 106 }; 107} 108