all files / src/ fsm.ts

96.88% Statements 62/64
78.26% Branches 18/23
95% Functions 19/20
96.61% Lines 57/59
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                        18×       22× 22× 22× 22× 22×       22× 20× 20×       18× 18× 16×   18×                            
/**
 * Created by thinhth2 on 5/25/2017.
 */
 
export interface IStateEvent {
  [name:string]: {from: any, to: any};
}
 
export const EventStatus = {
  ASYNC: "async",
  CANCEL: "cancel",
  CONTINUE: "continue"
};
 
export interface IStateOption {
  initial: any;
  events: IStateEvent;
}
 
export class FiniteStateMachine {
  events: any = {};
  current: any = "";
  previous: any = "";
 
  constructor() {
 
  }
 
  public setInitState(initState) {
    this.current = initState;
    this.previous = initState;
  }
 
  public registerEvent(name, callback, self) {
    this.events[name] =  callback;
    this.events[name + "_self"] = self;
  }
 
  private runEvent(prefix, name, args, done) {
    if (this.events[`${prefix}${name}`] && this.events[`${prefix}${name}`].call) {
      return this.events[`${prefix}${name}`].call(this.events[`${prefix}${name}_self`], ...args, done);
    }
  }
 
  private processEvent(steps) {
    let step = steps.shift();
    let isAsycn = true;
    let doneCalled = false;
    let done = () => {
      Eif (isAsycn) {
        this.processEvent(steps);
        doneCalled = true;
      }
    };
 
    if (step) {
      let status = step(done);
      switch (status) {
        case EventStatus.CANCEL:
          isAsycn = false;
          break;
        case EventStatus.ASYNC:
          break;
        default:
          isAsycn = false;
          if (!doneCalled) {
            this.processEvent(steps);
          }
          break;
      }
    }
  }
 
  public pushEvents(events: IStateEvent) {
    for (let name in events) {
      Eif (events.hasOwnProperty(name)) {
        let event = events[name];
        this.events[name] = (...args) => {
          Eif (this.current === event.from || event.from.indexOf(this.current) >= 0) {
            this.processEvent([
              (done)=>{return this.runEvent("before", "any", args, done)},
              (done)=>{return this.runEvent("before", name, args, done)},
              (done)=>{return this.runEvent("leave", this.current, args, done)},
              (done)=>{return this.runEvent("leave", "any", args, done)},
              (done)=>{
                this.previous = this.current;
                this.current = event.to;
                done();
              },
              (done)=>{return this.runEvent("enter", "any", args, done)},
              (done)=>{return this.runEvent("enter", this.current, args, done)},
              (done)=>{return this.runEvent("after", name, args, done)},
              (done)=>{return this.runEvent("after", "any", args, done)},
            ]);
          }
        };
      }
    }
  }
}
 
export class FiniteStateMachineStatic {
  public config(options: IStateOption): FiniteStateMachine {
    let fsm = new FiniteStateMachine();
    fsm.setInitState(options.initial || "");
    fsm.pushEvents(options.events || {});
    return fsm;
  }
}