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