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 {Patch, produce} from 'immer'; 16 17import {assertExists} from '../base/logging'; 18import {Remote} from '../base/remote'; 19import {DeferredAction, StateActions} from '../common/actions'; 20import {Engine} from '../common/engine'; 21import {createEmptyState, State} from '../common/state'; 22import { 23 createWasmEngine, 24 destroyWasmEngine, 25 WasmEngineProxy 26} from '../common/wasm_engine_proxy'; 27 28import {ControllerAny} from './controller'; 29 30 31export interface App { 32 state: State; 33 dispatch(action: DeferredAction): void; 34 publish( 35 what: 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'| 36 'SliceDetails', 37 data: {}, transferList?: Array<{}>): void; 38} 39 40/** 41 * Global accessors for state/dispatch in the controller. 42 */ 43class Globals implements App { 44 private _state?: State; 45 private _rootController?: ControllerAny; 46 private _frontend?: Remote; 47 private _runningControllers = false; 48 private _queuedActions = new Array<DeferredAction>(); 49 50 initialize(rootController: ControllerAny, frontendProxy: Remote) { 51 this._rootController = rootController; 52 this._frontend = frontendProxy; 53 this._state = createEmptyState(); 54 } 55 56 dispatch(action: DeferredAction): void { 57 this.dispatchMultiple([action]); 58 } 59 60 dispatchMultiple(actions: DeferredAction[]): void { 61 this._queuedActions = this._queuedActions.concat(actions); 62 63 // If we are in the middle of running the controllers, queue the actions 64 // and run them at the end of the run, so the state is atomically updated 65 // only at the end and all controllers see the same state. 66 if (this._runningControllers) return; 67 68 this.runControllers(); 69 } 70 71 private runControllers() { 72 if (this._runningControllers) throw new Error('Re-entrant call detected'); 73 74 // Run controllers locally until all state machines reach quiescence. 75 let runAgain = false; 76 const patches: Patch[] = []; 77 for (let iter = 0; runAgain || this._queuedActions.length > 0; iter++) { 78 if (iter > 100) throw new Error('Controllers are stuck in a livelock'); 79 const actions = this._queuedActions; 80 this._queuedActions = new Array<DeferredAction>(); 81 for (const action of actions) { 82 patches.push(...this.applyAction(action)); 83 } 84 this._runningControllers = true; 85 try { 86 runAgain = assertExists(this._rootController).invoke(); 87 } finally { 88 this._runningControllers = false; 89 } 90 } 91 assertExists(this._frontend).send<void>('patchState', [patches]); 92 } 93 94 createEngine(): Engine { 95 const id = new Date().toUTCString(); 96 const portAndId = {id, worker: createWasmEngine(id)}; 97 return new WasmEngineProxy(portAndId); 98 } 99 100 destroyEngine(id: string): void { 101 destroyWasmEngine(id); 102 } 103 104 // TODO: this needs to be cleaned up. 105 publish( 106 what: 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'| 107 'SliceDetails', 108 data: {}, transferList?: Transferable[]) { 109 assertExists(this._frontend) 110 .send<void>(`publish${what}`, [data], transferList); 111 } 112 113 get state(): State { 114 return assertExists(this._state); 115 } 116 117 applyAction(action: DeferredAction): Patch[] { 118 assertExists(this._state); 119 const patches: Patch[] = []; 120 121 // 'produce' creates a immer proxy which wraps the current state turning 122 // all imperative mutations of the state done in the callback into 123 // immutable changes to the returned state. 124 this._state = produce( 125 this.state, 126 draft => { 127 // tslint:disable-next-line no-any 128 (StateActions as any)[action.type](draft, action.args); 129 }, 130 (morePatches, _) => { 131 patches.push(...morePatches); 132 }); 133 return patches; 134 } 135 136 resetForTesting() { 137 this._state = undefined; 138 this._rootController = undefined; 139 } 140} 141 142export const globals = new Globals(); 143