| Index: lib/action.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/lib/action.js |
| @@ -0,0 +1,882 @@ |
| +/* |
| + * We avoid using a "let" statement so that we can test this module with js-test-driver, which doesn't support |
| + * non-standard JavaScript syntax. |
| + */ |
| +var x = require( "action_platform" ); |
| +var Action_Platform = x.Action_Platform; |
| + |
| +/** |
| + * @namespace The action library, working with both synchronous and asynchronous actions. |
| + */ |
| +Action = {}; |
| + |
| +//------------------------------------------------------- |
| +// Platform functions |
| +//------------------------------------------------------- |
| +/* |
| + * We forward the basic platform functions into this name space for ease of use and a single place for documentation. |
| + */ |
| +/** |
| + * Dispatch a function into a future JavaScript thread. JS is single-threaded, so the function won't run concurrently |
| + * with the present thread. Rather it creates a new JS call stack with the argument function at the root of the stack. |
| + * The browser implementation is setTimeout( f, 0 ). |
| + * |
| + * @type {function(function)} |
| + */ |
| +Action.dispatch = Action_Platform.dispatch; |
| + |
| +//------------------------------------------------------- |
| + |
| +/** |
| + * The common states of all actions. The ordinary start state is Ready leads to only three transitions: from Ready to |
| + * Running, and from Running to both Done and Exception. For actions that are not fully initialized by their constructors, |
| + * there's also the state Init and a transition to Ready. |
| + * @enum {number} |
| + */ |
| +Action.State = { |
| + /** |
| + * An available start state for actions that use more then their constructors for initialization. |
| + */ |
| + Init: 0, |
| + /** |
| + * The ordinary start state. An action is ready after it is fully initialized. |
| + */ |
| + Ready: 1, |
| + /** |
| + * The subprogram of the action is currently running. The state is changed immediately upon the call to go() or run(). |
| + */ |
| + Running: 2, |
| + /** |
| + * The action completed without exception. In this case no catcher was called. The state is changed after the |
| + * subprogram has finished and before calling the finisher. |
| + */ |
| + Done: 3, |
| + /** |
| + * The action threw an exception. In this case any catcher specified would be called. The state is changed |
| + * after the subprogram has finished and before calling the catcher. |
| + */ |
| + Exception: 4 |
| +}; |
| + |
| +//------------------------------------------------------- |
| +// Action interfaces |
| +//------------------------------------------------------- |
| +/** |
| + * The base action interface is just a marker. |
| + * |
| + * This interface declaration is in Closure Compiler syntax, but for use by IntelliJ IDEA. The compiler would optimize |
| + * it out, but it ought to go into a separate file anyway. |
| + * |
| + * @interface |
| + */ |
| +Action.Action_interface = function() |
| +{ |
| + /** |
| + * Every action is either reliable, which means that it's guaranteed to return control to the caller, or unreliable, |
| + * which means no such guarantee exists. Unreliable does not mean "never returns"; what would be the point of that? |
| + * |
| + * Reliability is a self-declaration for primitive actions. For composite actions, that is, actions that have at least |
| + * one other action within themselves, reliability can (often) be inferred. |
| + * |
| + * @expose |
| + * @type {boolean} |
| + */ |
| + this.reliable = null; |
| +}; |
| + |
| +/** |
| + * |
| + * @interface |
| + * @extends Action.Action_interface |
| + */ |
| +Action.Synchronous_Action_interface = function() |
| +{ |
| + /** |
| + * Every synchronous action is, by definition, reliable, since it always returns control to its caller. The return |
| + * of control can be either ordinary or exceptional, but that distinction is irrelevant to the meaning of "reliable". |
| + |
| + * @type {boolean} |
| + */ |
| + this.reliable = true; |
| +}; |
| + |
| +/** |
| + * The subprogram of a synchronous action is called 'run', to distinguish it from an asynchronous subprogram. |
| + */ |
| +Action.Synchronous_Action_interface.prototype.run = function() |
| +{ |
| +}; |
| + |
| +//------------------------------------------------------- |
| +/** |
| + * @interface |
| + * @extends Action.Action_interface |
| + */ |
| +Action.Asynchronous_Action_interface = function() |
| +{ |
| + /** |
| + * The default for an asynchronous action is unreliable. While some asynchronous actions are reliable, its prudent not |
| + * to assume that otherwise without specific knowledge. |
| + * |
| + * @type {boolean} |
| + */ |
| + this.reliable = false; |
| +}; |
| + |
| +Action.Asynchronous_Action_interface.prototype._go = function() |
| +{ |
| +}; |
| + |
| + |
| +//----------------------------------------------------------------------------------------- |
| +// Join Messaging |
| +//----------------------------------------------------------------------------------------- |
| +/* |
| + * Join messages are part of the basic action implementation, because the behavior of a join requires augmenting the |
| + * behavior of the finisher. Upon an action completing, it notifies other actions (directly or indirectly) that it has |
| + * completed. Thus, in addition to the caller-designated finisher functions, there may be an additional set of calls |
| + * made at this time. |
| + * |
| + * Some classes generate join messages, such as those that |
| + * wait for another action to complete. Some accept join messages, just as Join_class itself. Yet others, however, the |
| + * category of condition transformers, both generate and accept such messages, just as the gate for join-with-timeout. |
| + * As a consequence, we need to define these interfaces separately, so that, for example, both join actions and |
| + * condition transformers can each consistently accept messages |
| + */ |
| + |
| +/** |
| + * Interface for a receiver of join messages. |
| + * @interface |
| + */ |
| +Action.JM_Attentive = function() |
| +{ |
| +}; |
| + |
| +/** |
| + * Receive a notice that a dependent condition has completed well. |
| + * |
| + * @param id |
| + */ |
| +Action.JM_Attentive.prototype.notice_good = function( id ) |
| +{ |
| +}; |
| + |
| +/** |
| + * Receive a notice that a dependent condition has completed badly. |
| + * |
| + * @param {*} id |
| + * The identifier for the dependent condition in case there's more than one. |
| + * @param {*} e |
| + * An exception object as it appears in a catch clause. |
| + */ |
| +Action.JM_Attentive.prototype.notice_bad = function( id, e ) |
| +{ |
| +}; |
| + |
| +/** |
| + * Interface for a sender of join messages. This interface is required because reporters have state that may need to be |
| + * queried by a receiver. |
| + * @interface |
| + */ |
| +Action.JM_Reporting = function() |
| +{ |
| +}; |
| + |
| +/** |
| + * Watch the ending of this action. |
| + * |
| + * @param {Action.JM_Attentive} watcher |
| + * The watcher object. |
| + * @param {*} their_id |
| + * An opaque identifier by which the peer identifies the relation. |
| + * @returns {*} |
| + * Our identifier for the relation. |
| + */ |
| +Action.JM_Reporting.prototype.watch = function( watcher, their_id ) |
| +{ |
| +}; |
| + |
| +//------------------------------------------------------- |
| +/** |
| + * Base class implementation. |
| + * @constructor |
| + * @implements {Action.JM_Reporting} |
| + * @implements {Action.Asynchronous_Action_interface} |
| + */ |
| +Action.Asynchronous_Action = function() |
| +{ |
| +}; |
| + |
| +/** |
| + * We set up the finisher and catcher function at construction time. The design principle here is that flow of control |
| + * is structural and should be set up at the beginning. In addition, it enables run() and go() to be parallel, both |
| + * accepting arguments for the body of the action. |
| + * |
| + * @this {Action.Asynchronous_Action} |
| + * @param {function} [finisher] |
| + * @param {function} [catcher] |
| + */ |
| +Action.Asynchronous_Action.init = function( finisher, catcher ) |
| +{ |
| + this.finisher = finisher; |
| + this.catcher = catcher; |
| + |
| + /** |
| + * The common state of a asynchronous action |
| + * @type {Action.State} |
| + * @private |
| + */ |
| + this._state = Action.State.Ready; |
| + |
| + /** |
| + * @type {Array.<{watcher,id}>} |
| + */ |
| + this._end_watchers = []; |
| + |
| + /** |
| + * The value of an action, used to invoke the finisher as its array of arguments. |
| + * @type {Array} |
| + * @protected |
| + */ |
| + this._argv = []; |
| +}; |
| + |
| +Object.defineProperty( Action.Asynchronous_Action.prototype, "state", { |
| + /** |
| + * @this {Action.Asynchronous_Action} |
| + */ |
| + get: function() |
| + { |
| + return this._state; |
| + } |
| +} ); |
| + |
| +Object.defineProperty( Action.Asynchronous_Action.prototype, "completed", { |
| + /** |
| + * @this {Action.Asynchronous_Action} |
| + */ |
| + get: function() |
| + { |
| + return this._state >= Action.State.Done; |
| + } |
| +} ); |
| + |
| +Object.defineProperty( Action.Asynchronous_Action.prototype, "completed_well", { |
| + /** |
| + * @this {Action.Asynchronous_Action} |
| + */ |
| + get: function() |
| + { |
| + return this._state == Action.State.Done; |
| + } |
| +} ); |
| + |
| +Object.defineProperty( Action.Asynchronous_Action.prototype, "exception", { |
| + /** |
| + * @this {Action.Asynchronous_Action} |
| + */ |
| + get: function() |
| + { |
| + if ( this._state == Action.State.Exception ) |
| + { |
| + return this._exception; |
| + } |
| + else |
| + { |
| + throw new Error( "Action is not in an exception state." ); |
| + } |
| + } |
| +} ); |
| + |
| +/** |
| + * Start up the subprogram body for this action instance. |
| + * |
| + * Any arguments here are passed along unchanged to the action, which may or may not ignore them. |
| + */ |
| +Action.Asynchronous_Action.prototype.go = function() |
| +{ |
| + if ( this._state != Action.State.Ready ) |
| + { |
| + throw new Error( "Call to go() is invalid because the action is not in state 'Ready'." ); |
| + } |
| + this._state = Action.State.Running; |
| + this._go.apply( this, arguments ); |
| +}; |
| + |
| +/** |
| + * The default action body is to do nothing. |
| + */ |
| +Action.Asynchronous_Action.prototype._go = function() |
| +{ |
| +}; |
| + |
| +/** |
| + * Cancellation is an ordinary end to an action. We halt execution of the action and end immediately. |
| + * |
| + * The analog of this method for exceptional end is abort(). |
| + * |
| + * Design Note: Commands the force early end to an action only affect the action itself, at most. They do not affect |
| + * the execution of finisher and catcher functions that the action may have been invoked with. Nor do they affect |
| + * notices to join actions waiting on the early-terminated action. One of the motivations for this behavior is that we |
| + * want reliable actions to remain reliable. If cancellation were to cause finishers, not to run, no action could be |
| + * considered reliable. |
| + */ |
| +Action.Asynchronous_Action.prototype.cancel = function() |
| +{ |
| + this.end_well(); |
| +}; |
| + |
| +/** |
| + * Abortion in an exceptional end to an action. We halt execution of the action and end immediately. |
| + * |
| + * The analog of this method for ordinary end is cancel(), which see for more commentary. |
| + * |
| + * @param {*} [e] |
| + * An exception object to associate with exceptional termination. If absent, defaults to a new Error object. |
| + */ |
| +Action.Asynchronous_Action.prototype.abort = function( e ) |
| +{ |
| + this.end_badly( e ? e : new Error( "Action aborted by external command." ) ); |
| +}; |
| + |
| +/** |
| + * The default termination behavior is to do nothing. |
| + * |
| + * Actions that allocate resources should override this method and release their resources here. This method is always |
| + * called when the action ends. |
| + * |
| + * @protected |
| + */ |
| +Action.Asynchronous_Action.prototype.terminate = function() |
| +{ |
| +}; |
| + |
| +/** |
| + * Change state to Done and execute the finisher. |
| + * |
| + * @protected |
| + */ |
| +Action.Asynchronous_Action.prototype.end_well = function() |
| +{ |
| + function good() |
| + { |
| + this.watcher.notice_good( this.id ); |
| + } |
| + |
| + /* |
| + * We may only complete once. |
| + */ |
| + if ( this.completed ) |
| + return; |
| + /* |
| + * Note that there's no exception handling in this function. In order to mimic the behavior of the try-finally |
| + * statement, an exception thrown from a finisher is treated as if it had happened within a finally block, which is to |
| + * say, it throws the exception. There's no need for extra code to do that. |
| + * |
| + * In addition, the state is left at Done if the finisher throws an exception. In this case, the exception does not |
| + * come from the action itself, but from user code. So regardless of how the finisher terminates, it does not change |
| + * that the action completed ordinarily. |
| + */ |
| + this._state = Action.State.Done; |
| + this.terminate(); |
| + this._each_watcher( good ); |
| + if ( this.finisher ) this.finisher.apply( null, this._argv ); |
| +}; |
| + |
| +/** |
| + * Change state to Exception and execute the catcher followed by the finisher. |
| + * |
| + * @protected |
| + * @param e |
| + * An exception value |
| + */ |
| +Action.Asynchronous_Action.prototype.end_badly = function( e ) |
| +{ |
| + function bad() |
| + { |
| + this.watcher.notice_bad( this.id, e ); |
| + } |
| + |
| + /* |
| + * We may only complete once. |
| + */ |
| + if ( this.completed ) |
| + return; |
| + /* |
| + * In contrast to end_well(), this function does require a try-finally statement. If the catcher throws an |
| + * exception, then we still have to execute the finisher anyway. |
| + */ |
| + this._state = Action.State.Exception; |
| + /** |
| + * The object identified with the exceptional completion of the action. |
| + * |
| + * @type {*} |
| + * @private |
| + */ |
| + this._exception = e; |
| + this.terminate(); |
| + this._each_watcher( bad ); |
| + try |
| + { |
| + this._argv.unshift( e ); |
| + if ( this.catcher ) this.catcher.apply( null, this._argv ); |
| + } |
| + finally |
| + { |
| + this._argv.shift(); |
| + if ( this.finisher ) this.finisher.apply( null, this._argv ); |
| + } |
| +}; |
| + |
| +/** |
| + * Call a function on each watcher. |
| + * |
| + * @param {function} f |
| + * A function to be called on the watcher structure. |
| + * @private |
| + */ |
| +Action.Asynchronous_Action.prototype._each_watcher = function( f ) |
| +{ |
| + for ( var j = 0 ; j < this._end_watchers.length ; ++j ) |
| + { |
| + try |
| + { |
| + /** |
| + * @type {{watcher:Action.JM_Attentive, id}} |
| + */ |
| + var w = this._end_watchers[ j ]; |
| + if ( !w ) |
| + { |
| + /* |
| + * It's OK for a watcher to be null. All this means is that the watcher withdrew before completion. |
| + */ |
| + continue; |
| + } |
| + f.call( w ); |
| + } |
| + catch ( e ) |
| + { |
| + /* |
| + * The use of this catch block is a defense so that we can ignore exceptions. There shouldn't be any, though, but |
| + * just in case. |
| + */ |
| + } |
| + } |
| +}; |
| + |
| +/** |
| + * Watch the ending of this action. |
| + * |
| + * @param {Action.JM_Attentive} watcher |
| + * The watcher object. |
| + * @param {*} their_id |
| + * An opaque identifier by which the peer identifies itself. |
| + * @returns {number} |
| + * Our identifier, which is the index in the _end_watchers array. |
| + */ |
| +Action.Asynchronous_Action.prototype.watch = function( watcher, their_id ) |
| +{ |
| + return this._end_watchers.push( { watcher: watcher, id: their_id } ) - 1; |
| +}; |
| + |
| +//noinspection JSUnusedGlobalSymbols |
| +/** |
| + * Withdraw a watcher |
| + */ |
| +Action.Asynchronous_Action.prototype.withdraw = function( our_id ) |
| +{ |
| + this._end_watchers[ our_id ] = null; |
| +}; |
| + |
| +//------------------------------------------------------- |
| +/** |
| + * @interface |
| + * @extends Action.Action_interface |
| + */ |
| +Action.Joinable = function() |
| +{ |
| +}; |
| + |
| +//----------------------------------------------------------------------------------------- |
| +// ACTIONS |
| +//----------------------------------------------------------------------------------------- |
| + |
| +//------------------------------------------------------- |
| +// Defer |
| +//------------------------------------------------------- |
| +/** |
| + * Class constructor for Defer actions, which defer execution of a function (the "trial") until after the current |
| + * JavaScript-thread has run to completion. |
| + * |
| + * @constructor |
| + * @extends Action.Asynchronous_Action |
| + */ |
| +Action.Defer_class = function() |
| +{ |
| + /** |
| + * @const |
| + * @type {boolean} |
| + */ |
| + this.reliable = true; |
| +}; |
| +Action.Defer_class.prototype = new Action.Asynchronous_Action(); |
| + |
| +/** |
| + * |
| + */ |
| +Action.Defer_class.prototype._go = function() |
| +{ |
| + Action.dispatch( this._body.bind( this ) ); |
| +}; |
| + |
| +/** |
| + * The deferred trial is run inside of a try-catch-finally statement. |
| + * @private |
| + */ |
| +Action.Defer_class.prototype._body = function() |
| +{ |
| + try |
| + { |
| + if ( this.trial ) |
| + this._argv = this.trial(); |
| + } |
| + catch ( e ) |
| + { |
| + /* |
| + * Note that because the trial did not return, it had no return value. Thus we have no context for a sensible |
| + * return value argument for end_badly(). |
| + */ |
| + this.end_badly( e ); |
| + return; |
| + } |
| + this.end_well(); |
| +}; |
| + |
| +/** |
| + * Instance constructor for standard Defer actions. |
| + * @constructor |
| + * @param f |
| + * @param {function} [finisher] |
| + * @param {function} [catcher] |
| + */ |
| +Action.Defer = function( f, finisher, catcher ) |
| +{ |
| + Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| + this.trial = f; |
| +}; |
| +Action.Defer.prototype = new Action.Defer_class(); |
| + |
| +//------------------------------------------------------- |
| +/** |
| + * |
| + * @constructor |
| + * @extends Action.Asynchronous_Action |
| + */ |
| +Action.Delay_class = function() |
| +{ |
| + /** |
| + * Delay actions always complete, even if cancelled or aborted early. |
| + * @const |
| + * @type {boolean} |
| + */ |
| + this.reliable = true; |
| +}; |
| +Action.Delay_class.prototype = new Action.Asynchronous_Action(); |
| + |
| +/** |
| + * Initialization function for use by instance constructors. |
| + * @param f |
| + * @param duration |
| + * @param finisher |
| + * @param catcher |
| + */ |
| +Action.Delay_class.init = function( f, duration, finisher, catcher ) |
| +{ |
| + Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| + this.trial = f; |
| + this.duration = duration; |
| +}; |
| + |
| +Action.Delay_class.prototype._go = function() |
| +{ |
| + this.timer_id = Action_Platform.set_timer( this._body.bind( this ), this.duration ); |
| +}; |
| + |
| +Action.Delay_class.prototype._body = function() |
| +{ |
| + if ( this.completed ) |
| + return; |
| + try |
| + { |
| + if ( this.trial ) |
| + this._argv = this.trial(); |
| + } |
| + catch ( e ) |
| + { |
| + this.end_badly( e ); |
| + return; |
| + } |
| + this.end_well(); |
| +}; |
| + |
| +Action.Delay_class.prototype.terminate = function() |
| +{ |
| + Action_Platform.clear_timer( this.timer_id ); |
| +}; |
| + |
| +Action.Delay = function( f, duration, finisher, catcher ) |
| +{ |
| + Action.Delay_class.init.call( this, f, duration, finisher, catcher ); |
| +}; |
| +Action.Delay.prototype = new Action.Delay_class(); |
| + |
| +//------------------------------------------------------- |
| +// Join_class |
| +//------------------------------------------------------- |
| +/** |
| + * Class constructor for objects that join on a single action, perhaps with modifications. |
| + * |
| + * The policy for this class is that completion of the joined action causes completion of this action. This is a simple |
| + * join policy. Joining on more than action requires a more complicated action. This policy, however, is suitable not |
| + * only for Join itself, but also for Join_Timeout. Join_Timeout also depends only upon a single action; its timer is |
| + * internal and doesn't incur the overhead of an action for that simple case. |
| + * |
| + * @constructor |
| + * @extends {Action.Asynchronous_Action} |
| + */ |
| +Action.Join_class = function() |
| +{ |
| +}; |
| +Action.Join_class.prototype = new Action.Asynchronous_Action(); |
| + |
| +/** |
| + * Initialization function for instance constructors. |
| + * |
| + * @this {Action.Join_class} |
| + * @param action |
| + * @param finisher |
| + * @param catcher |
| + */ |
| +Action.Join_class.init = function( action, finisher, catcher ) |
| +{ |
| + if ( !action ) |
| + throw new Error( "Action to be joined may not be null" ); |
| + Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| + this.joined_action = action; |
| +}; |
| + |
| +/** |
| + * The action body for a join is to do nothing when the joined action is not yet completed. |
| + * @private |
| + */ |
| +Action.Join_class.prototype._go = function() |
| +{ |
| + this._argv = Array.prototype.slice.call( arguments ); |
| + |
| + if ( this.joined_action.completed ) |
| + { |
| + if ( this.joined_action.completed_well ) |
| + { |
| + this.end_well(); |
| + } |
| + else |
| + { |
| + this.end_badly( this.joined_action.exception ); |
| + } |
| + } |
| + else |
| + { |
| + this.watch_id = this.joined_action.watch( this, null ); |
| + } |
| +}; |
| + |
| +/** |
| + * A good completion of the joined action yields a good completion for us. |
| + */ |
| +Action.Join_class.prototype.notice_good = function() |
| +{ |
| + this.end_well(); |
| +}; |
| + |
| +/** |
| + * A bad completion of the joined action yields a bad completion for us. |
| + */ |
| +Action.Join_class.prototype.notice_bad = function( id, e ) |
| +{ |
| + this.end_badly( e ); |
| +}; |
| + |
| +/** |
| + * We treat object references as resources and null them out. This is a defense against creating or aggravating |
| + * memory leaks. |
| + */ |
| +Action.Join_class.prototype.terminate = function() |
| +{ |
| + this.joined_action.withdraw( this.watch_id ); |
| + this.joined_action = null; |
| +}; |
| + |
| +//------------------------------------------------------- |
| +// Join |
| +//------------------------------------------------------- |
| +/** |
| + * Join with another action. The completion of the action joined allows the join action to complete. |
| + * |
| + * @constructor |
| + * @param {Action.Asynchronous_Action} action |
| + * @param [finisher] |
| + * @param [catcher] |
| + */ |
| +Action.Join = function( action, finisher, catcher ) |
| +{ |
| + Action.Join_class.init.call( this, action, finisher, catcher ); |
| +}; |
| +Action.Join.prototype = new Action.Join_class(); |
| + |
| +//------------------------------------------------------- |
| +// Join_Timeout |
| +//------------------------------------------------------- |
| +/** |
| + * Join with another action and set a timer that may preemptively complete. |
| + * |
| + * Note that the timer starts on go() regardless of the state of the joined action. If you invoke a Join_Timeout on an |
| + * action which never itself is invoked, then the timeout always causes completion. |
| + * |
| + * It would be perfectly reasonable to have an action which started a timer when some other action began, but that would |
| + * require a subclass of Asynchronous_Action that had a hook at the start of execution, rather than merely upon |
| + * completion. |
| + * |
| + * @constructor |
| + * @param {Action.Asynchronous_Action} action |
| + * @param duration |
| + * @param [finisher] |
| + * @param [catcher] |
| + */ |
| +Action.Join_Timeout = function( action, duration, finisher, catcher ) |
| +{ |
| + Action.Join_class.init.call( this, action, finisher, catcher ); |
| + |
| + /** |
| + * The identifier of the platform timer. It's used for early termination of the timer, if needed. |
| + * @type {*} |
| + */ |
| + this._timer_id = null; |
| + |
| + this.duration = duration; |
| + |
| + /** |
| + * Flag indicating that the action has timed out. |
| + * @type {boolean} |
| + * @private |
| + */ |
| + this._timed_out = false; |
| +}; |
| +Action.Join_Timeout.prototype = new Action.Join_class(); |
| + |
| +/** |
| + * Flag indicating that the action has timed out. This is a read-only version of a private property, but with the |
| + * caveat that calling it is only valid in a completed state. |
| + * |
| + * @this {Action.Join_Timeout} |
| + * @return {boolean} |
| + */ |
| +Object.defineProperty( Action.Join_Timeout.prototype, "timed_out", { |
| + /** |
| + * @this {Action.Join_Timeout} |
| + */ |
| + get: function() |
| + { |
| + if ( this.completed ) |
| + { |
| + return this._timed_out; |
| + } |
| + else |
| + { |
| + throw new Error( "Action is not yet completed." ); |
| + } |
| + } |
| +} ); |
| + |
| +Action.Join_Timeout.prototype._go = function() |
| +{ |
| + Action.Join_class.prototype._go.apply( this, arguments ); |
| + if ( !this.completed ) |
| + { |
| + this._timer_id = Action_Platform.set_timer( this.ding.bind( this ), this.duration ); |
| + } |
| +}; |
| + |
| +/** |
| + * The timer just went off. |
| + */ |
| +Action.Join_Timeout.prototype.ding = function() |
| +{ |
| + this._timer_id = null; |
| + /* |
| + * If we've already completed, we don't change the completion state. |
| + */ |
| + if ( this.completed ) |
| + return; |
| + /* |
| + * Since we haven't completed at this point, the timer has gone off before the action completed. Timeout is |
| + * considered an exceptional completion. |
| + */ |
| + this._timed_out = true; |
| + this.end_badly( new Error( "Action timed out." ) ); |
| +}; |
| + |
| +/** |
| + * Termination requires clearing any timer that may still be active. |
| + * |
| + * @override |
| + */ |
| +Action.Join_Timeout.prototype.terminate = function() |
| +{ |
| + /* |
| + * Clear the timer so it doesn't ding. Clear the timer id because as an object reference it acts as a resource. |
| + */ |
| + if ( this._timer_id ) |
| + { |
| + Action_Platform.clear_timer( this._timer_id ); |
| + this._timer_id = null; |
| + } |
| + |
| + Action.Join_class.prototype.terminate.call( this ); |
| +}; |
| + |
| +//------------------------------------------------------- |
| +/** |
| + * @constructor |
| + * @implements {Action.JM_Attentive} |
| + * @param {Array.Joinable} actions |
| + */ |
| +Action.Join_Conjunction = function( actions ) |
| +{ |
| + /** |
| + * The conjunction of actions is reliable only if all the actions are reliable. |
| + */ |
| + this.reliable = true; |
| + for ( var j = 0 ; j < actions.length ; ++j ) |
| + { |
| + if ( !actions[ j ].reliable ) |
| + { |
| + this.reliable = false; |
| + break; |
| + } |
| + } |
| +}; |
| + |
| +Action.Join_Conjunction.prototype.notice_good = function() |
| +{ |
| +}; |
| + |
| +Action.Join_Conjunction.prototype.notice_bad = function() |
| +{ |
| +}; |
| + |
| +/* |
| + * The guard around the definition of 'exports' is present because in js-test-driver it's not in scope. |
| + */ |
| +if ( typeof exports === 'undefined' ) |
| + exports = {}; |
| +exports.Action = Action; |