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 this.port.postMessage( 51 { 52 responseId: this.nextRequestId, 53 method, 54 args, 55 }, 56 transferList); 57 this.nextRequestId += 1; 58 return d; 59 } 60 61 private receive(response: RemoteResponse): void { 62 const d = this.deferredRequests.get(response.id); 63 if (!d) throw new Error(`No deferred response with ID ${response.id}`); 64 this.deferredRequests.delete(response.id); 65 d.resolve(response.result); 66 } 67} 68 69/** 70 * Given a MessagePort |port| where the other end is owned by a Remote 71 * (see above) turn each incoming MessageEvent into a call on |handler| 72 * and post the result back to the calling thread. 73 */ 74export function forwardRemoteCalls( 75 port: MessagePort, 76 // tslint:disable-next-line no-any 77 handler: {[key: string]: any}) { 78 port.onmessage = (msg: MessageEvent) => { 79 const method = msg.data.method; 80 const id = msg.data.responseId; 81 const args = msg.data.args || []; 82 if (method === undefined || id === undefined) { 83 throw new Error(`Invalid call method: ${method} id: ${id}`); 84 } 85 86 if (!(handler[method] instanceof Function)) { 87 throw new Error(`Method not known: ${method}(${args})`); 88 } 89 90 const result = handler[method].apply(handler, args); 91 const transferList = []; 92 93 if (result !== undefined && result.port instanceof MessagePort) { 94 transferList.push(result.port); 95 } 96 97 port.postMessage( 98 { 99 id, 100 result, 101 }, 102 transferList); 103 }; 104} 105