mirror of
https://github.com/mapillary/mapillary-js.git
synced 2026-02-01 14:33:45 +00:00
refactor: validate state transition in context
This commit is contained in:
parent
1098096a49
commit
2142e83c02
@ -16,15 +16,17 @@ export class StateContext implements IStateContext {
|
||||
|
||||
constructor(transitionMode?: TransitionMode) {
|
||||
this._transitions = new StateTransitionMatrix();
|
||||
this._state = this._transitions.initialize({
|
||||
alpha: 1,
|
||||
camera: new Camera(),
|
||||
currentIndex: -1,
|
||||
reference: { alt: 0, lat: 0, lng: 0 },
|
||||
trajectory: [],
|
||||
transitionMode: transitionMode == null ? TransitionMode.Default : transitionMode,
|
||||
zoom: 0,
|
||||
});
|
||||
this._state = this._transitions.generate(
|
||||
State.Traversing,
|
||||
{
|
||||
alpha: 1,
|
||||
camera: new Camera(),
|
||||
currentIndex: -1,
|
||||
reference: { alt: 0, lat: 0, lng: 0 },
|
||||
trajectory: [],
|
||||
transitionMode: transitionMode == null ? TransitionMode.Default : transitionMode,
|
||||
zoom: 0,
|
||||
});
|
||||
}
|
||||
|
||||
public get state(): State {
|
||||
@ -208,6 +210,12 @@ export class StateContext implements IStateContext {
|
||||
}
|
||||
|
||||
private _transition(to: State): void {
|
||||
if (!this._transitions.validate(this._state, to)) {
|
||||
const from = this._transitions.getState(this._state);
|
||||
console.warn(
|
||||
`Transition not valid (${State[from]} - ${State[to]})`);
|
||||
return;
|
||||
}
|
||||
const state = this._transitions.transition(this._state, to);
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
@ -556,7 +556,6 @@ export class StateService {
|
||||
.next(
|
||||
(context: IStateContext): IStateContext => {
|
||||
action(context);
|
||||
|
||||
return context;
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { StateBase } from "./state/StateBase";
|
||||
import { TraversingState } from "./state/TraversingState";
|
||||
import { WaitingState } from "./state/WaitingState";
|
||||
|
||||
type StateCreators = Map<string, new (state: StateBase) => StateBase>;
|
||||
type StateCreators = Map<string, new (state: IStateBase) => StateBase>;
|
||||
|
||||
export class StateTransitionMatrix {
|
||||
private readonly _creators: StateCreators;
|
||||
@ -27,7 +27,7 @@ export class StateTransitionMatrix {
|
||||
|
||||
this._transitions = new Map();
|
||||
const transitions = this._transitions;
|
||||
transitions.set(earth, [traverse, wait, waitInteractively]);
|
||||
transitions.set(earth, [traverse]);
|
||||
transitions.set(traverse, [earth, wait, waitInteractively]);
|
||||
transitions.set(wait, [traverse, waitInteractively]);
|
||||
transitions.set(waitInteractively, [traverse, wait]);
|
||||
@ -46,21 +46,24 @@ export class StateTransitionMatrix {
|
||||
throw new Error("Invalid state instance");
|
||||
}
|
||||
|
||||
public initialize(state: IStateBase): StateBase {
|
||||
return new TraversingState(state);
|
||||
public generate(state: State, options: IStateBase): StateBase {
|
||||
const stateImplementation = this._creators.get(State[state]);
|
||||
return new stateImplementation(options);
|
||||
}
|
||||
|
||||
public transition(state: StateBase, to: State): StateBase {
|
||||
if (!this.validate(state, to)) {
|
||||
throw new Error("Invalid transition");
|
||||
}
|
||||
return this.generate(to, state);
|
||||
}
|
||||
|
||||
public validate(state: StateBase, to: State): boolean {
|
||||
const source = State[this.getState(state)];
|
||||
const target = State[to];
|
||||
const transitions = this._transitions;
|
||||
|
||||
if (!transitions.has(source) ||
|
||||
!transitions.get(source).includes(target)) {
|
||||
throw new Error("Invalid transition");
|
||||
}
|
||||
|
||||
const stateImplementation = this._creators.get(target);
|
||||
return new stateImplementation(state);
|
||||
return transitions.has(source) &&
|
||||
transitions.get(source).includes(target);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1217,23 +1217,15 @@ export class Viewer extends EventEmitter implements IViewer {
|
||||
* ```
|
||||
*/
|
||||
public setCameraControls(controls: CameraControls): void {
|
||||
this._navigator.stateService.state$
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(s): void => {
|
||||
if (s === State.Earth &&
|
||||
controls === CameraControls.Street) {
|
||||
this._navigator.stateService.traverse();
|
||||
} else if (
|
||||
s === State.Traversing &&
|
||||
controls === CameraControls.Earth) {
|
||||
this._navigator.stateService.earth();
|
||||
} else {
|
||||
const to = CameraControls[controls];
|
||||
console.warn(
|
||||
`Unsupported camera control transition (${to})`);
|
||||
}
|
||||
})
|
||||
if (controls === CameraControls.Street) {
|
||||
this._navigator.stateService.traverse();
|
||||
} else if (controls === CameraControls.Earth) {
|
||||
this._navigator.stateService.earth();
|
||||
} else {
|
||||
const to = CameraControls[controls];
|
||||
console.warn(
|
||||
`Unsupported camera control transition (${to})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -162,66 +162,30 @@ describe("Viewer.setCameraControls", () => {
|
||||
spyOn(console, "warn").and.stub();
|
||||
})
|
||||
|
||||
it("should set different state", () => {
|
||||
it("should invoke state transition", () => {
|
||||
const mocks = createMocks();
|
||||
const viewer = new Viewer({ apiClient: "", container: "" });
|
||||
|
||||
const earthSpy = (<jasmine.Spy>mocks.navigator.stateService.earth);
|
||||
const traverseSpy = (<jasmine.Spy>mocks.navigator.stateService.traverse);
|
||||
|
||||
const state$ = <Subject<State>>mocks.navigator.stateService.state$;
|
||||
|
||||
viewer.setCameraControls(CameraControls.Earth);
|
||||
state$.next(State.Traversing);
|
||||
expect(earthSpy.calls.count()).toBe(1);
|
||||
expect(traverseSpy.calls.count()).toBe(0);
|
||||
|
||||
viewer.setCameraControls(CameraControls.Street);
|
||||
state$.next(State.Earth);
|
||||
expect(earthSpy.calls.count()).toBe(1);
|
||||
expect(traverseSpy.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it("should not set same state", () => {
|
||||
it("should not invoke state transition", () => {
|
||||
const mocks = createMocks();
|
||||
const viewer = new Viewer({ apiClient: "", container: "" });
|
||||
|
||||
const earthSpy = (<jasmine.Spy>mocks.navigator.stateService.earth);
|
||||
const traverseSpy = (<jasmine.Spy>mocks.navigator.stateService.traverse);
|
||||
|
||||
const state$ = <Subject<State>>mocks.navigator.stateService.state$;
|
||||
|
||||
viewer.setCameraControls(CameraControls.Earth);
|
||||
state$.next(State.Earth);
|
||||
expect(earthSpy.calls.count()).toBe(0);
|
||||
expect(traverseSpy.calls.count()).toBe(0);
|
||||
|
||||
viewer.setCameraControls(CameraControls.Street);
|
||||
state$.next(State.Traversing);
|
||||
expect(earthSpy.calls.count()).toBe(0);
|
||||
expect(traverseSpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it("should not change state when waiting", () => {
|
||||
const mocks = createMocks();
|
||||
const viewer = new Viewer({ apiClient: "", container: "" });
|
||||
|
||||
const earthSpy = (<jasmine.Spy>mocks.navigator.stateService.earth);
|
||||
const traverseSpy = (<jasmine.Spy>mocks.navigator.stateService.traverse);
|
||||
|
||||
const state$ = <Subject<State>>mocks.navigator.stateService.state$;
|
||||
|
||||
viewer.setCameraControls(CameraControls.Earth);
|
||||
state$.next(State.Waiting);
|
||||
viewer.setCameraControls(CameraControls.Street);
|
||||
state$.next(State.Waiting);
|
||||
expect(earthSpy.calls.count()).toBe(0);
|
||||
expect(traverseSpy.calls.count()).toBe(0);
|
||||
|
||||
viewer.setCameraControls(CameraControls.Earth);
|
||||
state$.next(State.WaitingInteractively);
|
||||
viewer.setCameraControls(CameraControls.Street);
|
||||
state$.next(State.WaitingInteractively);
|
||||
viewer.setCameraControls(<CameraControls>-1);
|
||||
expect(earthSpy.calls.count()).toBe(0);
|
||||
expect(traverseSpy.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user