• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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