blob: 84564a80542fb65ad128a45febf4dd7b0d7bafd1 [file] [log] [blame]
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {applyPatches, Patch} from 'immer';
import {assertExists} from '../base/logging';
import {DeferredAction} from '../common/actions';
import {createEmptyState} from '../common/empty_state';
import {State} from '../common/state';
import {globals as frontendGlobals} from '../frontend/globals';
import {ControllerAny} from './controller';
export interface App {
state: State;
dispatch(action: DeferredAction): void;
}
/**
* Global accessors for state/dispatch in the controller.
*/
class Globals implements App {
private _state?: State;
private _rootController?: ControllerAny;
private _runningControllers = false;
initialize(rootController: ControllerAny) {
this._rootController = rootController;
this._state = createEmptyState();
}
dispatch(action: DeferredAction): void {
frontendGlobals.dispatch(action);
}
// Send the passed dispatch actions to the frontend. The frontend logic
// will run the actions, compute the new state and invoke patchState() so
// our copy is updated.
dispatchMultiple(actions: DeferredAction[]): void {
for (const action of actions) {
this.dispatch(action);
}
}
// This is called by the frontend logic which now owns and handle the
// source-of-truth state, to give us an update on the newer state updates.
patchState(patches: Patch[]): void {
this._state = applyPatches(assertExists(this._state), patches);
this.runControllers();
}
private runControllers() {
if (this._runningControllers) throw new Error('Re-entrant call detected');
// Run controllers locally until all state machines reach quiescence.
let runAgain = true;
for (let iter = 0; runAgain; iter++) {
if (iter > 100) throw new Error('Controllers are stuck in a livelock');
this._runningControllers = true;
try {
runAgain = assertExists(this._rootController).invoke();
} finally {
this._runningControllers = false;
}
}
}
get state(): State {
return assertExists(this._state);
}
resetForTesting() {
this._state = undefined;
this._rootController = undefined;
}
}
export const globals = new Globals();