UNPKG

oosmos

Version:

A Hierarchical State Machine Class

266 lines (265 loc) 11.2 kB
"use strict"; var StateMachine = (function () { function StateMachine(Composite) { this.m_Timeouts = {}; this.m_DotPath2State = {}; this.m_DebugMode = false; this.m_InBrowser = typeof (window) !== 'undefined'; this.m_LinesOut = 0; this.m_ROOT = { COMPOSITE: Composite }; } StateMachine.prototype.InstrumentStateMachine = function () { var StateStack = []; function InstrumentComposite(Composite) { var StateName; for (StateName in Composite) { if (StateName === 'DEFAULT') { continue; } if (typeof (Composite[StateName]) === 'function') { Composite[StateName] = Composite[StateName](); } InstrumentState.call(this, Composite[StateName], StateName); } if (Object.keys(Composite).length === 1) { Composite.DEFAULT = Object.keys(Composite)[0]; } else { if (!Composite.DEFAULT) { this.Alert('You must specify a DEFAULT if there are more than one state in the composite.'); } } } function InstrumentState(State, StateName) { if (State.ENTER && typeof (State.ENTER) !== 'function') { this.Alert('ENTER must be a function.'); } if (State.EXIT && typeof (State.EXIT) !== 'function') { this.Alert('EXIT must be a function.'); } if (typeof (State.COMPOSITE) === 'function') { State.COMPOSITE = State.COMPOSITE(); } StateStack.push(StateName); State.DOTPATH = StateStack.join('.'); this.m_DotPath2State[State.DOTPATH] = State; if (State.COMPOSITE) { InstrumentComposite.call(this, State.COMPOSITE); } StateStack.pop(); } InstrumentState.call(this, this.m_ROOT, 'ROOT'); }; ; StateMachine.prototype.StripROOT = function (StateName) { return StateName.replace('ROOT.', ''); }; StateMachine.prototype.EnterDefaultStates = function (Composite) { this.m_State = Composite[Composite.DEFAULT]; this.DebugPrint('==> ' + this.StripROOT(this.m_State.DOTPATH)); if (this.m_State.ENTER) { this.m_State.ENTER.call(this); } if (this.m_State.COMPOSITE) { this.EnterDefaultStates.call(this, this.m_State.COMPOSITE); } }; StateMachine.prototype.CalculateLCA = function (StringA, StringB) { var A = StringA.split('.'); var B = StringB.split('.'); var Iterations = Math.min(A.length, B.length); var Return = []; for (var I = 0; I < Iterations && A[I] === B[I]; I++) { Return.push(A[I]); } return Return.join('.'); }; StateMachine.prototype.Transition = function (To) { var Args = []; for (var _i = 1; _i < arguments.length; _i++) { Args[_i - 1] = arguments[_i]; } if (this.m_EventSourceState === undefined) { this.m_EventSourceState = this.m_State; } To = 'ROOT.' + To; this.DebugPrint('TRANSITION: ' + this.StripROOT(this.m_EventSourceState.DOTPATH) + ' -> ' + this.StripROOT(To)); var LCA = this.CalculateLCA(this.m_EventSourceState.DOTPATH, To); if (To === LCA) { var A = LCA.split('.'); A.splice(-1, 1); LCA = A.join('.'); } var ArgArray = Array.prototype.splice.call(arguments, 1); function EnterStates(FromState, ToState) { if (FromState === ToState) { return; } var FromArray = FromState.split('.'); var ToSuffix = ToState.replace(FromState + '.', ''); var ToArray = ToSuffix.split('.'); var StatePath; do { FromArray.push(ToArray.shift()); StatePath = FromArray.join('.'); this.m_State = this.m_DotPath2State[StatePath]; this.DebugPrint('--> ' + this.StripROOT(StatePath)); if (this.m_State.ENTER) { this.m_State.ENTER.apply(this, ArgArray); } } while (StatePath !== ToState); } function ExitStates(ToState, FromState) { var FromArray = FromState.split('.'); while (ToState !== FromState) { this.m_State = this.m_DotPath2State[FromState]; this.DebugPrint(' ' + this.StripROOT(FromState) + '-->'); if (this.m_State.EXIT) { this.m_State.EXIT.call(this); } if (this.m_Timeouts[this.m_State.DOTPATH]) { this.DebugPrint('Delete Timeout: ' + this.m_State.DOTPATH + ' ' + this.m_Timeouts[this.m_State.DOTPATH]); delete this.m_Timeouts[this.m_State.DOTPATH]; } FromArray.splice(-1, 1); FromState = FromArray.join('.'); } } ExitStates.call(this, LCA, this.m_State.DOTPATH); EnterStates.call(this, LCA, To); if (this.m_DotPath2State[this.m_State.DOTPATH].COMPOSITE) { this.EnterDefaultStates.call(this, this.m_DotPath2State[this.m_State.DOTPATH].COMPOSITE); } this.m_EventSourceState = undefined; }; StateMachine.prototype.Start = function () { this.InstrumentStateMachine(); this.EnterDefaultStates.call(this, this.m_ROOT.COMPOSITE); return this; }; StateMachine.prototype.Restart = function () { this.m_State = {}; this.m_Timeouts = {}; this.m_Interval = undefined; this.m_EventSourceState = {}; this.m_DotPath2State = {}; if (this.m_InBrowser) { document.getElementById(this.m_DebugID).innerHTML = ''; this.m_LinesOut = 0; } this.Start(); }; StateMachine.prototype.IsIn = function (StateDotPath) { StateDotPath = 'ROOT.' + StateDotPath; if (StateDotPath === this.m_State.DOTPATH) { return true; } var Beginning = StateDotPath + '.'; return this.m_State.DOTPATH.substr(0, Beginning.length) === Beginning; }; StateMachine.prototype.Event = function (EventString) { var Args = []; for (var _i = 1; _i < arguments.length; _i++) { Args[_i - 1] = arguments[_i]; } var CandidateStatePath = this.m_State.DOTPATH.split('.'); while (CandidateStatePath.length > 0) { var CandidateStateDotPath = CandidateStatePath.join('.'); var CandidateState = this.m_DotPath2State[CandidateStateDotPath]; if (EventString in CandidateState) { this.DebugPrint('EVENT: ' + EventString + ' sent to ' + this.StripROOT(this.m_State.DOTPATH)); var EventFunc = CandidateState[EventString]; if (EventFunc) { var ArgArray = Array.prototype.splice.call(arguments, 1); this.m_EventSourceState = CandidateState; EventFunc.apply(this, ArgArray); this.m_EventSourceState = undefined; } return; } CandidateStatePath.splice(-1, 1); } this.DebugPrint('EVENT: ' + EventString + '. No handler from ' + this.StripROOT(this.m_State.DOTPATH)); }; StateMachine.prototype.SetTimeoutSeconds = function (TimeoutSeconds) { var _this = this; this.m_Timeouts[this.m_State.DOTPATH] = TimeoutSeconds; if (this.m_Interval === undefined) { var IntervalTick = function () { for (var StateDotPath in _this.m_Timeouts) { _this.m_Timeouts[StateDotPath] -= 1; if (_this.m_Timeouts[StateDotPath] <= 0) { var State = _this.m_DotPath2State[StateDotPath]; _this.m_EventSourceState = _this.m_State; _this.DebugPrint('Delete Timeout: ' + _this.m_State.DOTPATH + ' ' + _this.m_Timeouts[StateDotPath]); delete _this.m_Timeouts[StateDotPath]; if (State.TIMEOUT) { State.TIMEOUT.call(_this); } } } }; this.m_Interval = setInterval(IntervalTick, 1000); } this.DebugPrint('SetTimeoutSeconds:' + this.m_State.DOTPATH + ' ' + TimeoutSeconds); }; StateMachine.prototype.DebugPrint = function (Message) { if (this.m_DebugMode) { this.Print(Message); } }; StateMachine.prototype.SetDebug = function (DebugMode, DebugID, MaxLinesOut, ScrollIntoView) { if (typeof (DebugMode) !== 'boolean') { this.Alert('First argument of SetDebug must be a boolean value. Defaulting to false.'); DebugMode = false; } this.m_DebugMode = DebugMode; if (this.m_InBrowser) { if (DebugID === undefined) { this.Alert('DebugID must be set to the ID of a <div> element.'); return; } this.m_DebugID = DebugID; this.m_MaxLinesOut = MaxLinesOut || 200; this.m_ScrollIntoView = ScrollIntoView || false; } }; StateMachine.prototype.Print = function (Message) { if (!this.m_InBrowser) { console.log(Message); return; } var DebugDIV = document.getElementById(this.m_DebugID); var TextDIV = document.createElement('div'); var Text = document.createTextNode(Message); TextDIV.appendChild(Text); DebugDIV.appendChild(TextDIV); function IsVisible(Element) { var Rect = Element.getBoundingClientRect(); var ViewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight); return !(Rect.bottom < 0 || Rect.top - ViewHeight >= 0); } if (this.m_ScrollIntoView && IsVisible(DebugDIV)) { TextDIV.scrollIntoView(false); } this.m_LinesOut += 1; if (this.m_LinesOut > this.m_MaxLinesOut) { DebugDIV.removeChild(DebugDIV.childNodes[0]); } }; StateMachine.prototype.Assert = function (Condition, Message) { if (!Condition) { this.Alert(Message || 'Assertion failed'); } }; StateMachine.prototype.Alert = function (Message) { if (this.m_InBrowser) { window.alert(Message); } else { console.log(Message); } }; return StateMachine; }()); exports.StateMachine = StateMachine;