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 {applyPatches, Patch} from 'immer'; 16 17import {assertExists} from '../base/logging'; 18import {DeferredAction} from '../common/actions'; 19import {createEmptyState} from '../common/empty_state'; 20import {State} from '../common/state'; 21import {globals as frontendGlobals} from '../frontend/globals'; 22 23import {ControllerAny} from './controller'; 24 25export interface App { 26 state: State; 27 dispatch(action: DeferredAction): void; 28} 29 30/** 31 * Global accessors for state/dispatch in the controller. 32 */ 33class Globals implements App { 34 private _state?: State; 35 private _rootController?: ControllerAny; 36 private _runningControllers = false; 37 38 initialize(rootController: ControllerAny) { 39 this._rootController = rootController; 40 this._state = createEmptyState(); 41 } 42 43 dispatch(action: DeferredAction): void { 44 frontendGlobals.dispatch(action); 45 } 46 47 // Send the passed dispatch actions to the frontend. The frontend logic 48 // will run the actions, compute the new state and invoke patchState() so 49 // our copy is updated. 50 dispatchMultiple(actions: DeferredAction[]): void { 51 for (const action of actions) { 52 this.dispatch(action); 53 } 54 } 55 56 // This is called by the frontend logic which now owns and handle the 57 // source-of-truth state, to give us an update on the newer state updates. 58 patchState(patches: Patch[]): void { 59 this._state = applyPatches(assertExists(this._state), patches); 60 this.runControllers(); 61 } 62 63 private runControllers() { 64 if (this._runningControllers) throw new Error('Re-entrant call detected'); 65 66 // Run controllers locally until all state machines reach quiescence. 67 let runAgain = true; 68 for (let iter = 0; runAgain; iter++) { 69 if (iter > 100) throw new Error('Controllers are stuck in a livelock'); 70 this._runningControllers = true; 71 try { 72 runAgain = assertExists(this._rootController).invoke(); 73 } finally { 74 this._runningControllers = false; 75 } 76 } 77 } 78 79 get state(): State { 80 return assertExists(this._state); 81 } 82 83 resetForTesting() { 84 this._state = undefined; 85 this._rootController = undefined; 86 } 87} 88 89export const globals = new Globals(); 90