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