michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["StateMachine"]; michael@0: michael@0: const DEBUG = false; michael@0: michael@0: this.StateMachine = function(aDebugTag) { michael@0: function debug(aMsg) { michael@0: dump('-------------- StateMachine:' + aDebugTag + ': ' + aMsg); michael@0: } michael@0: michael@0: var sm = {}; michael@0: michael@0: var _initialState; michael@0: var _curState; michael@0: var _prevState; michael@0: var _paused; michael@0: var _eventQueue = []; michael@0: var _deferredEventQueue = []; michael@0: var _defaultEventHandler; michael@0: michael@0: // Public interfaces. michael@0: michael@0: sm.setDefaultEventHandler = function(aDefaultEventHandler) { michael@0: _defaultEventHandler = aDefaultEventHandler; michael@0: }; michael@0: michael@0: sm.start = function(aInitialState) { michael@0: _initialState = aInitialState; michael@0: sm.gotoState(_initialState); michael@0: }; michael@0: michael@0: sm.sendEvent = function (aEvent) { michael@0: if (!_initialState) { michael@0: if (DEBUG) { michael@0: debug('StateMachine is not running. Call StateMachine.start() first.'); michael@0: } michael@0: return; michael@0: } michael@0: _eventQueue.push(aEvent); michael@0: asyncCall(handleFirstEvent); michael@0: }; michael@0: michael@0: sm.getPreviousState = function() { michael@0: return _prevState; michael@0: }; michael@0: michael@0: sm.getCurrentState = function() { michael@0: return _curState; michael@0: }; michael@0: michael@0: // State object maker. michael@0: // @param aName string for this state's name. michael@0: // @param aDelegate object: michael@0: // .handleEvent: required. michael@0: // .enter: called before entering this state (optional). michael@0: // .exit: called before exiting this state (optional). michael@0: sm.makeState = function (aName, aDelegate) { michael@0: if (!aDelegate.handleEvent) { michael@0: throw "handleEvent is a required delegate function."; michael@0: } michael@0: var nop = function() {}; michael@0: return { michael@0: name: aName, michael@0: enter: (aDelegate.enter || nop), michael@0: exit: (aDelegate.exit || nop), michael@0: handleEvent: aDelegate.handleEvent michael@0: }; michael@0: }; michael@0: michael@0: sm.deferEvent = function (aEvent) { michael@0: // The definition of a 'deferred event' is: michael@0: // We are not able to handle this event now but after receiving michael@0: // certain event or entering a new state, we might be able to handle michael@0: // it. For example, we couldn't handle CONNECT_EVENT in the michael@0: // diconnecting state. But once we finish doing "disconnecting", we michael@0: // could then handle CONNECT_EVENT! michael@0: // michael@0: // So, the deferred event may be handled in the following cases: michael@0: // 1. Once we entered a new state. michael@0: // 2. Once we handled a regular event. michael@0: if (DEBUG) { michael@0: debug('Deferring event: ' + JSON.stringify(aEvent)); michael@0: } michael@0: _deferredEventQueue.push(aEvent); michael@0: }; michael@0: michael@0: // Goto the new state. If the current state is null, the exit michael@0: // function won't be called. michael@0: sm.gotoState = function (aNewState) { michael@0: if (_curState) { michael@0: if (DEBUG) { michael@0: debug("exiting state: " + _curState.name); michael@0: } michael@0: _curState.exit(); michael@0: } michael@0: michael@0: _prevState = _curState; michael@0: _curState = aNewState; michael@0: michael@0: if (DEBUG) { michael@0: debug("entering state: " + _curState.name); michael@0: } michael@0: _curState.enter(); michael@0: michael@0: // We are in the new state now. We got a chance to handle the michael@0: // deferred events. michael@0: handleDeferredEvents(); michael@0: michael@0: sm.resume(); michael@0: }; michael@0: michael@0: // No incoming event will be handled after you call pause(). michael@0: // (But they will be queued.) michael@0: sm.pause = function() { michael@0: _paused = true; michael@0: }; michael@0: michael@0: // Continue to handle incoming events. michael@0: sm.resume = function() { michael@0: _paused = false; michael@0: asyncCall(handleFirstEvent); michael@0: }; michael@0: michael@0: //---------------------------------------------------------- michael@0: // Private stuff michael@0: //---------------------------------------------------------- michael@0: michael@0: function asyncCall(f) { michael@0: Services.tm.currentThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL); michael@0: } michael@0: michael@0: function handleFirstEvent() { michael@0: var hadDeferredEvents; michael@0: michael@0: if (0 === _eventQueue.length) { michael@0: return; michael@0: } michael@0: michael@0: if (_paused) { michael@0: return; // The state machine is paused now. michael@0: } michael@0: michael@0: hadDeferredEvents = _deferredEventQueue.length > 0; michael@0: michael@0: handleOneEvent(_eventQueue.shift()); // The handler may defer this event. michael@0: michael@0: // We've handled one event. If we had deferred events before, now is michael@0: // a good chance to handle them. michael@0: if (hadDeferredEvents) { michael@0: handleDeferredEvents(); michael@0: } michael@0: michael@0: // Continue to handle the next regular event. michael@0: handleFirstEvent(); michael@0: } michael@0: michael@0: function handleDeferredEvents() { michael@0: if (_deferredEventQueue.length && DEBUG) { michael@0: debug('Handle deferred events: ' + _deferredEventQueue.length); michael@0: } michael@0: for (let i = 0; i < _deferredEventQueue.length; i++) { michael@0: handleOneEvent(_deferredEventQueue.shift()); michael@0: } michael@0: } michael@0: michael@0: function handleOneEvent(aEvent) michael@0: { michael@0: if (DEBUG) { michael@0: debug('Handling event: ' + JSON.stringify(aEvent)); michael@0: } michael@0: michael@0: var handled = _curState.handleEvent(aEvent); michael@0: michael@0: if (undefined === handled) { michael@0: throw "handleEvent returns undefined: " + _curState.name; michael@0: } michael@0: if (!handled) { michael@0: // Event is not handled in the current state. Try handleEventCommon(). michael@0: handled = (_defaultEventHandler ? _defaultEventHandler(aEvent) : handled); michael@0: } michael@0: if (undefined === handled) { michael@0: throw "handleEventCommon returns undefined: " + _curState.name; michael@0: } michael@0: if (!handled) { michael@0: if (DEBUG) { michael@0: debug('!!!!!!!!! FIXME !!!!!!!!! Event not handled: ' + JSON.stringify(aEvent)); michael@0: } michael@0: } michael@0: michael@0: return handled; michael@0: } michael@0: michael@0: return sm; michael@0: };