OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * We avoid using a "let" statement so that we can test this module with js-test
-driver, which doesn't support |
| 3 * non-standard JavaScript syntax. |
| 4 */ |
| 5 var x = require( "action_platform" ); |
| 6 var Action_Platform = x.Action_Platform; |
| 7 |
| 8 /** |
| 9 * @namespace The action library, working with both synchronous and asynchronous
actions. |
| 10 */ |
| 11 Action = {}; |
| 12 |
| 13 //------------------------------------------------------- |
| 14 // Platform functions |
| 15 //------------------------------------------------------- |
| 16 /* |
| 17 * We forward the basic platform functions into this name space for ease of use
and a single place for documentation. |
| 18 */ |
| 19 /** |
| 20 * Dispatch a function into a future JavaScript thread. JS is single-threaded, s
o the function won't run concurrently |
| 21 * with the present thread. Rather it creates a new JS call stack with the argum
ent function at the root of the stack. |
| 22 * The browser implementation is setTimeout( f, 0 ). |
| 23 * |
| 24 * @type {function(function)} |
| 25 */ |
| 26 Action.dispatch = Action_Platform.dispatch; |
| 27 |
| 28 //------------------------------------------------------- |
| 29 |
| 30 /** |
| 31 * The common states of all actions. The ordinary start state is Ready leads to
only three transitions: from Ready to |
| 32 * Running, and from Running to both Done and Exception. For actions that are no
t fully initialized by their constructors, |
| 33 * there's also the state Init and a transition to Ready. |
| 34 * @enum {number} |
| 35 */ |
| 36 Action.State = { |
| 37 /** |
| 38 * An available start state for actions that use more then their constructors
for initialization. |
| 39 */ |
| 40 Init: 0, |
| 41 /** |
| 42 * The ordinary start state. An action is ready after it is fully initialized. |
| 43 */ |
| 44 Ready: 1, |
| 45 /** |
| 46 * The subprogram of the action is currently running. The state is changed imm
ediately upon the call to go() or run(). |
| 47 */ |
| 48 Running: 2, |
| 49 /** |
| 50 * The action completed without exception. In this case no catcher was called.
The state is changed after the |
| 51 * subprogram has finished and before calling the finisher. |
| 52 */ |
| 53 Done: 3, |
| 54 /** |
| 55 * The action threw an exception. In this case any catcher specified would be
called. The state is changed |
| 56 * after the subprogram has finished and before calling the catcher. |
| 57 */ |
| 58 Exception: 4 |
| 59 }; |
| 60 |
| 61 //------------------------------------------------------- |
| 62 // Action interfaces |
| 63 //------------------------------------------------------- |
| 64 /** |
| 65 * The base action interface is just a marker. |
| 66 * |
| 67 * This interface declaration is in Closure Compiler syntax, but for use by Inte
lliJ IDEA. The compiler would optimize |
| 68 * it out, but it ought to go into a separate file anyway. |
| 69 * |
| 70 * @interface |
| 71 */ |
| 72 Action.Action_interface = function() |
| 73 { |
| 74 /** |
| 75 * Every action is either reliable, which means that it's guaranteed to return
control to the caller, or unreliable, |
| 76 * which means no such guarantee exists. Unreliable does not mean "never retur
ns"; what would be the point of that? |
| 77 * |
| 78 * Reliability is a self-declaration for primitive actions. For composite acti
ons, that is, actions that have at least |
| 79 * one other action within themselves, reliability can (often) be inferred. |
| 80 * |
| 81 * @expose |
| 82 * @type {boolean} |
| 83 */ |
| 84 this.reliable = null; |
| 85 }; |
| 86 |
| 87 /** |
| 88 * |
| 89 * @interface |
| 90 * @extends Action.Action_interface |
| 91 */ |
| 92 Action.Synchronous_Action_interface = function() |
| 93 { |
| 94 /** |
| 95 * Every synchronous action is, by definition, reliable, since it always retur
ns control to its caller. The return |
| 96 * of control can be either ordinary or exceptional, but that distinction is i
rrelevant to the meaning of "reliable". |
| 97 |
| 98 * @type {boolean} |
| 99 */ |
| 100 this.reliable = true; |
| 101 }; |
| 102 |
| 103 /** |
| 104 * The subprogram of a synchronous action is called 'run', to distinguish it fro
m an asynchronous subprogram. |
| 105 */ |
| 106 Action.Synchronous_Action_interface.prototype.run = function() |
| 107 { |
| 108 }; |
| 109 |
| 110 //------------------------------------------------------- |
| 111 /** |
| 112 * @interface |
| 113 * @extends Action.Action_interface |
| 114 */ |
| 115 Action.Asynchronous_Action_interface = function() |
| 116 { |
| 117 /** |
| 118 * The default for an asynchronous action is unreliable. While some asynchrono
us actions are reliable, its prudent not |
| 119 * to assume that otherwise without specific knowledge. |
| 120 * |
| 121 * @type {boolean} |
| 122 */ |
| 123 this.reliable = false; |
| 124 }; |
| 125 |
| 126 Action.Asynchronous_Action_interface.prototype._go = function() |
| 127 { |
| 128 }; |
| 129 |
| 130 |
| 131 //------------------------------------------------------------------------------
----------- |
| 132 // Join Messaging |
| 133 //------------------------------------------------------------------------------
----------- |
| 134 /* |
| 135 * Join messages are part of the basic action implementation, because the behavi
or of a join requires augmenting the |
| 136 * behavior of the finisher. Upon an action completing, it notifies other action
s (directly or indirectly) that it has |
| 137 * completed. Thus, in addition to the caller-designated finisher functions, the
re may be an additional set of calls |
| 138 * made at this time. |
| 139 * |
| 140 * Some classes generate join messages, such as those that |
| 141 * wait for another action to complete. Some accept join messages, just as Join_
class itself. Yet others, however, the |
| 142 * category of condition transformers, both generate and accept such messages, j
ust as the gate for join-with-timeout. |
| 143 * As a consequence, we need to define these interfaces separately, so that, for
example, both join actions and |
| 144 * condition transformers can each consistently accept messages |
| 145 */ |
| 146 |
| 147 /** |
| 148 * Interface for a receiver of join messages. |
| 149 * @interface |
| 150 */ |
| 151 Action.JM_Attentive = function() |
| 152 { |
| 153 }; |
| 154 |
| 155 /** |
| 156 * Receive a notice that a dependent condition has completed well. |
| 157 * |
| 158 * @param id |
| 159 */ |
| 160 Action.JM_Attentive.prototype.notice_good = function( id ) |
| 161 { |
| 162 }; |
| 163 |
| 164 /** |
| 165 * Receive a notice that a dependent condition has completed badly. |
| 166 * |
| 167 * @param {*} id |
| 168 * The identifier for the dependent condition in case there's more than one. |
| 169 * @param {*} e |
| 170 * An exception object as it appears in a catch clause. |
| 171 */ |
| 172 Action.JM_Attentive.prototype.notice_bad = function( id, e ) |
| 173 { |
| 174 }; |
| 175 |
| 176 /** |
| 177 * Interface for a sender of join messages. This interface is required because r
eporters have state that may need to be |
| 178 * queried by a receiver. |
| 179 * @interface |
| 180 */ |
| 181 Action.JM_Reporting = function() |
| 182 { |
| 183 }; |
| 184 |
| 185 /** |
| 186 * Watch the ending of this action. |
| 187 * |
| 188 * @param {Action.JM_Attentive} watcher |
| 189 * The watcher object. |
| 190 * @param {*} their_id |
| 191 * An opaque identifier by which the peer identifies the relation. |
| 192 * @returns {*} |
| 193 * Our identifier for the relation. |
| 194 */ |
| 195 Action.JM_Reporting.prototype.watch = function( watcher, their_id ) |
| 196 { |
| 197 }; |
| 198 |
| 199 //------------------------------------------------------- |
| 200 /** |
| 201 * Base class implementation. |
| 202 * @constructor |
| 203 * @implements {Action.JM_Reporting} |
| 204 * @implements {Action.Asynchronous_Action_interface} |
| 205 */ |
| 206 Action.Asynchronous_Action = function() |
| 207 { |
| 208 }; |
| 209 |
| 210 /** |
| 211 * We set up the finisher and catcher function at construction time. The design
principle here is that flow of control |
| 212 * is structural and should be set up at the beginning. In addition, it enables
run() and go() to be parallel, both |
| 213 * accepting arguments for the body of the action. |
| 214 * |
| 215 * @this {Action.Asynchronous_Action} |
| 216 * @param {function} [finisher] |
| 217 * @param {function} [catcher] |
| 218 */ |
| 219 Action.Asynchronous_Action.init = function( finisher, catcher ) |
| 220 { |
| 221 this.finisher = finisher; |
| 222 this.catcher = catcher; |
| 223 |
| 224 /** |
| 225 * The common state of a asynchronous action |
| 226 * @type {Action.State} |
| 227 * @private |
| 228 */ |
| 229 this._state = Action.State.Ready; |
| 230 |
| 231 /** |
| 232 * @type {Array.<{watcher,id}>} |
| 233 */ |
| 234 this._end_watchers = []; |
| 235 |
| 236 /** |
| 237 * The value of an action, used to invoke the finisher as its array of argumen
ts. |
| 238 * @type {Array} |
| 239 * @protected |
| 240 */ |
| 241 this._argv = []; |
| 242 }; |
| 243 |
| 244 Object.defineProperty( Action.Asynchronous_Action.prototype, "state", { |
| 245 /** |
| 246 * @this {Action.Asynchronous_Action} |
| 247 */ |
| 248 get: function() |
| 249 { |
| 250 return this._state; |
| 251 } |
| 252 } ); |
| 253 |
| 254 Object.defineProperty( Action.Asynchronous_Action.prototype, "completed", { |
| 255 /** |
| 256 * @this {Action.Asynchronous_Action} |
| 257 */ |
| 258 get: function() |
| 259 { |
| 260 return this._state >= Action.State.Done; |
| 261 } |
| 262 } ); |
| 263 |
| 264 Object.defineProperty( Action.Asynchronous_Action.prototype, "completed_well", { |
| 265 /** |
| 266 * @this {Action.Asynchronous_Action} |
| 267 */ |
| 268 get: function() |
| 269 { |
| 270 return this._state == Action.State.Done; |
| 271 } |
| 272 } ); |
| 273 |
| 274 Object.defineProperty( Action.Asynchronous_Action.prototype, "exception", { |
| 275 /** |
| 276 * @this {Action.Asynchronous_Action} |
| 277 */ |
| 278 get: function() |
| 279 { |
| 280 if ( this._state == Action.State.Exception ) |
| 281 { |
| 282 return this._exception; |
| 283 } |
| 284 else |
| 285 { |
| 286 throw new Error( "Action is not in an exception state." ); |
| 287 } |
| 288 } |
| 289 } ); |
| 290 |
| 291 /** |
| 292 * Start up the subprogram body for this action instance. |
| 293 * |
| 294 * Any arguments here are passed along unchanged to the action, which may or may
not ignore them. |
| 295 */ |
| 296 Action.Asynchronous_Action.prototype.go = function() |
| 297 { |
| 298 if ( this._state != Action.State.Ready ) |
| 299 { |
| 300 throw new Error( "Call to go() is invalid because the action is not in state
'Ready'." ); |
| 301 } |
| 302 this._state = Action.State.Running; |
| 303 this._go.apply( this, arguments ); |
| 304 }; |
| 305 |
| 306 /** |
| 307 * The default action body is to do nothing. |
| 308 */ |
| 309 Action.Asynchronous_Action.prototype._go = function() |
| 310 { |
| 311 }; |
| 312 |
| 313 /** |
| 314 * Cancellation is an ordinary end to an action. We halt execution of the action
and end immediately. |
| 315 * |
| 316 * The analog of this method for exceptional end is abort(). |
| 317 * |
| 318 * Design Note: Commands the force early end to an action only affect the action
itself, at most. They do not affect |
| 319 * the execution of finisher and catcher functions that the action may have been
invoked with. Nor do they affect |
| 320 * notices to join actions waiting on the early-terminated action. One of the mo
tivations for this behavior is that we |
| 321 * want reliable actions to remain reliable. If cancellation were to cause finis
hers, not to run, no action could be |
| 322 * considered reliable. |
| 323 */ |
| 324 Action.Asynchronous_Action.prototype.cancel = function() |
| 325 { |
| 326 this.end_well(); |
| 327 }; |
| 328 |
| 329 /** |
| 330 * Abortion in an exceptional end to an action. We halt execution of the action
and end immediately. |
| 331 * |
| 332 * The analog of this method for ordinary end is cancel(), which see for more co
mmentary. |
| 333 * |
| 334 * @param {*} [e] |
| 335 * An exception object to associate with exceptional termination. If absent,
defaults to a new Error object. |
| 336 */ |
| 337 Action.Asynchronous_Action.prototype.abort = function( e ) |
| 338 { |
| 339 this.end_badly( e ? e : new Error( "Action aborted by external command." ) ); |
| 340 }; |
| 341 |
| 342 /** |
| 343 * The default termination behavior is to do nothing. |
| 344 * |
| 345 * Actions that allocate resources should override this method and release their
resources here. This method is always |
| 346 * called when the action ends. |
| 347 * |
| 348 * @protected |
| 349 */ |
| 350 Action.Asynchronous_Action.prototype.terminate = function() |
| 351 { |
| 352 }; |
| 353 |
| 354 /** |
| 355 * Change state to Done and execute the finisher. |
| 356 * |
| 357 * @protected |
| 358 */ |
| 359 Action.Asynchronous_Action.prototype.end_well = function() |
| 360 { |
| 361 function good() |
| 362 { |
| 363 this.watcher.notice_good( this.id ); |
| 364 } |
| 365 |
| 366 /* |
| 367 * We may only complete once. |
| 368 */ |
| 369 if ( this.completed ) |
| 370 return; |
| 371 /* |
| 372 * Note that there's no exception handling in this function. In order to mimic
the behavior of the try-finally |
| 373 * statement, an exception thrown from a finisher is treated as if it had happ
ened within a finally block, which is to |
| 374 * say, it throws the exception. There's no need for extra code to do that. |
| 375 * |
| 376 * In addition, the state is left at Done if the finisher throws an exception.
In this case, the exception does not |
| 377 * come from the action itself, but from user code. So regardless of how the f
inisher terminates, it does not change |
| 378 * that the action completed ordinarily. |
| 379 */ |
| 380 this._state = Action.State.Done; |
| 381 this.terminate(); |
| 382 this._each_watcher( good ); |
| 383 if ( this.finisher ) this.finisher.apply( null, this._argv ); |
| 384 }; |
| 385 |
| 386 /** |
| 387 * Change state to Exception and execute the catcher followed by the finisher. |
| 388 * |
| 389 * @protected |
| 390 * @param e |
| 391 * An exception value |
| 392 */ |
| 393 Action.Asynchronous_Action.prototype.end_badly = function( e ) |
| 394 { |
| 395 function bad() |
| 396 { |
| 397 this.watcher.notice_bad( this.id, e ); |
| 398 } |
| 399 |
| 400 /* |
| 401 * We may only complete once. |
| 402 */ |
| 403 if ( this.completed ) |
| 404 return; |
| 405 /* |
| 406 * In contrast to end_well(), this function does require a try-finally stateme
nt. If the catcher throws an |
| 407 * exception, then we still have to execute the finisher anyway. |
| 408 */ |
| 409 this._state = Action.State.Exception; |
| 410 /** |
| 411 * The object identified with the exceptional completion of the action. |
| 412 * |
| 413 * @type {*} |
| 414 * @private |
| 415 */ |
| 416 this._exception = e; |
| 417 this.terminate(); |
| 418 this._each_watcher( bad ); |
| 419 try |
| 420 { |
| 421 this._argv.unshift( e ); |
| 422 if ( this.catcher ) this.catcher.apply( null, this._argv ); |
| 423 } |
| 424 finally |
| 425 { |
| 426 this._argv.shift(); |
| 427 if ( this.finisher ) this.finisher.apply( null, this._argv ); |
| 428 } |
| 429 }; |
| 430 |
| 431 /** |
| 432 * Call a function on each watcher. |
| 433 * |
| 434 * @param {function} f |
| 435 * A function to be called on the watcher structure. |
| 436 * @private |
| 437 */ |
| 438 Action.Asynchronous_Action.prototype._each_watcher = function( f ) |
| 439 { |
| 440 for ( var j = 0 ; j < this._end_watchers.length ; ++j ) |
| 441 { |
| 442 try |
| 443 { |
| 444 /** |
| 445 * @type {{watcher:Action.JM_Attentive, id}} |
| 446 */ |
| 447 var w = this._end_watchers[ j ]; |
| 448 if ( !w ) |
| 449 { |
| 450 /* |
| 451 * It's OK for a watcher to be null. All this means is that the watcher
withdrew before completion. |
| 452 */ |
| 453 continue; |
| 454 } |
| 455 f.call( w ); |
| 456 } |
| 457 catch ( e ) |
| 458 { |
| 459 /* |
| 460 * The use of this catch block is a defense so that we can ignore exceptio
ns. There shouldn't be any, though, but |
| 461 * just in case. |
| 462 */ |
| 463 } |
| 464 } |
| 465 }; |
| 466 |
| 467 /** |
| 468 * Watch the ending of this action. |
| 469 * |
| 470 * @param {Action.JM_Attentive} watcher |
| 471 * The watcher object. |
| 472 * @param {*} their_id |
| 473 * An opaque identifier by which the peer identifies itself. |
| 474 * @returns {number} |
| 475 * Our identifier, which is the index in the _end_watchers array. |
| 476 */ |
| 477 Action.Asynchronous_Action.prototype.watch = function( watcher, their_id ) |
| 478 { |
| 479 return this._end_watchers.push( { watcher: watcher, id: their_id } ) - 1; |
| 480 }; |
| 481 |
| 482 //noinspection JSUnusedGlobalSymbols |
| 483 /** |
| 484 * Withdraw a watcher |
| 485 */ |
| 486 Action.Asynchronous_Action.prototype.withdraw = function( our_id ) |
| 487 { |
| 488 this._end_watchers[ our_id ] = null; |
| 489 }; |
| 490 |
| 491 //------------------------------------------------------- |
| 492 /** |
| 493 * @interface |
| 494 * @extends Action.Action_interface |
| 495 */ |
| 496 Action.Joinable = function() |
| 497 { |
| 498 }; |
| 499 |
| 500 //------------------------------------------------------------------------------
----------- |
| 501 // ACTIONS |
| 502 //------------------------------------------------------------------------------
----------- |
| 503 |
| 504 //------------------------------------------------------- |
| 505 // Defer |
| 506 //------------------------------------------------------- |
| 507 /** |
| 508 * Class constructor for Defer actions, which defer execution of a function (the
"trial") until after the current |
| 509 * JavaScript-thread has run to completion. |
| 510 * |
| 511 * @constructor |
| 512 * @extends Action.Asynchronous_Action |
| 513 */ |
| 514 Action.Defer_class = function() |
| 515 { |
| 516 /** |
| 517 * @const |
| 518 * @type {boolean} |
| 519 */ |
| 520 this.reliable = true; |
| 521 }; |
| 522 Action.Defer_class.prototype = new Action.Asynchronous_Action(); |
| 523 |
| 524 /** |
| 525 * |
| 526 */ |
| 527 Action.Defer_class.prototype._go = function() |
| 528 { |
| 529 Action.dispatch( this._body.bind( this ) ); |
| 530 }; |
| 531 |
| 532 /** |
| 533 * The deferred trial is run inside of a try-catch-finally statement. |
| 534 * @private |
| 535 */ |
| 536 Action.Defer_class.prototype._body = function() |
| 537 { |
| 538 try |
| 539 { |
| 540 if ( this.trial ) |
| 541 this._argv = this.trial(); |
| 542 } |
| 543 catch ( e ) |
| 544 { |
| 545 /* |
| 546 * Note that because the trial did not return, it had no return value. Thus
we have no context for a sensible |
| 547 * return value argument for end_badly(). |
| 548 */ |
| 549 this.end_badly( e ); |
| 550 return; |
| 551 } |
| 552 this.end_well(); |
| 553 }; |
| 554 |
| 555 /** |
| 556 * Instance constructor for standard Defer actions. |
| 557 * @constructor |
| 558 * @param f |
| 559 * @param {function} [finisher] |
| 560 * @param {function} [catcher] |
| 561 */ |
| 562 Action.Defer = function( f, finisher, catcher ) |
| 563 { |
| 564 Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| 565 this.trial = f; |
| 566 }; |
| 567 Action.Defer.prototype = new Action.Defer_class(); |
| 568 |
| 569 //------------------------------------------------------- |
| 570 /** |
| 571 * |
| 572 * @constructor |
| 573 * @extends Action.Asynchronous_Action |
| 574 */ |
| 575 Action.Delay_class = function() |
| 576 { |
| 577 /** |
| 578 * Delay actions always complete, even if cancelled or aborted early. |
| 579 * @const |
| 580 * @type {boolean} |
| 581 */ |
| 582 this.reliable = true; |
| 583 }; |
| 584 Action.Delay_class.prototype = new Action.Asynchronous_Action(); |
| 585 |
| 586 /** |
| 587 * Initialization function for use by instance constructors. |
| 588 * @param f |
| 589 * @param duration |
| 590 * @param finisher |
| 591 * @param catcher |
| 592 */ |
| 593 Action.Delay_class.init = function( f, duration, finisher, catcher ) |
| 594 { |
| 595 Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| 596 this.trial = f; |
| 597 this.duration = duration; |
| 598 }; |
| 599 |
| 600 Action.Delay_class.prototype._go = function() |
| 601 { |
| 602 this.timer_id = Action_Platform.set_timer( this._body.bind( this ), this.durat
ion ); |
| 603 }; |
| 604 |
| 605 Action.Delay_class.prototype._body = function() |
| 606 { |
| 607 if ( this.completed ) |
| 608 return; |
| 609 try |
| 610 { |
| 611 if ( this.trial ) |
| 612 this._argv = this.trial(); |
| 613 } |
| 614 catch ( e ) |
| 615 { |
| 616 this.end_badly( e ); |
| 617 return; |
| 618 } |
| 619 this.end_well(); |
| 620 }; |
| 621 |
| 622 Action.Delay_class.prototype.terminate = function() |
| 623 { |
| 624 Action_Platform.clear_timer( this.timer_id ); |
| 625 }; |
| 626 |
| 627 Action.Delay = function( f, duration, finisher, catcher ) |
| 628 { |
| 629 Action.Delay_class.init.call( this, f, duration, finisher, catcher ); |
| 630 }; |
| 631 Action.Delay.prototype = new Action.Delay_class(); |
| 632 |
| 633 //------------------------------------------------------- |
| 634 // Join_class |
| 635 //------------------------------------------------------- |
| 636 /** |
| 637 * Class constructor for objects that join on a single action, perhaps with modi
fications. |
| 638 * |
| 639 * The policy for this class is that completion of the joined action causes comp
letion of this action. This is a simple |
| 640 * join policy. Joining on more than action requires a more complicated action.
This policy, however, is suitable not |
| 641 * only for Join itself, but also for Join_Timeout. Join_Timeout also depends on
ly upon a single action; its timer is |
| 642 * internal and doesn't incur the overhead of an action for that simple case. |
| 643 * |
| 644 * @constructor |
| 645 * @extends {Action.Asynchronous_Action} |
| 646 */ |
| 647 Action.Join_class = function() |
| 648 { |
| 649 }; |
| 650 Action.Join_class.prototype = new Action.Asynchronous_Action(); |
| 651 |
| 652 /** |
| 653 * Initialization function for instance constructors. |
| 654 * |
| 655 * @this {Action.Join_class} |
| 656 * @param action |
| 657 * @param finisher |
| 658 * @param catcher |
| 659 */ |
| 660 Action.Join_class.init = function( action, finisher, catcher ) |
| 661 { |
| 662 if ( !action ) |
| 663 throw new Error( "Action to be joined may not be null" ); |
| 664 Action.Asynchronous_Action.init.call( this, finisher, catcher ); |
| 665 this.joined_action = action; |
| 666 }; |
| 667 |
| 668 /** |
| 669 * The action body for a join is to do nothing when the joined action is not yet
completed. |
| 670 * @private |
| 671 */ |
| 672 Action.Join_class.prototype._go = function() |
| 673 { |
| 674 this._argv = Array.prototype.slice.call( arguments ); |
| 675 |
| 676 if ( this.joined_action.completed ) |
| 677 { |
| 678 if ( this.joined_action.completed_well ) |
| 679 { |
| 680 this.end_well(); |
| 681 } |
| 682 else |
| 683 { |
| 684 this.end_badly( this.joined_action.exception ); |
| 685 } |
| 686 } |
| 687 else |
| 688 { |
| 689 this.watch_id = this.joined_action.watch( this, null ); |
| 690 } |
| 691 }; |
| 692 |
| 693 /** |
| 694 * A good completion of the joined action yields a good completion for us. |
| 695 */ |
| 696 Action.Join_class.prototype.notice_good = function() |
| 697 { |
| 698 this.end_well(); |
| 699 }; |
| 700 |
| 701 /** |
| 702 * A bad completion of the joined action yields a bad completion for us. |
| 703 */ |
| 704 Action.Join_class.prototype.notice_bad = function( id, e ) |
| 705 { |
| 706 this.end_badly( e ); |
| 707 }; |
| 708 |
| 709 /** |
| 710 * We treat object references as resources and null them out. This is a defense
against creating or aggravating |
| 711 * memory leaks. |
| 712 */ |
| 713 Action.Join_class.prototype.terminate = function() |
| 714 { |
| 715 this.joined_action.withdraw( this.watch_id ); |
| 716 this.joined_action = null; |
| 717 }; |
| 718 |
| 719 //------------------------------------------------------- |
| 720 // Join |
| 721 //------------------------------------------------------- |
| 722 /** |
| 723 * Join with another action. The completion of the action joined allows the join
action to complete. |
| 724 * |
| 725 * @constructor |
| 726 * @param {Action.Asynchronous_Action} action |
| 727 * @param [finisher] |
| 728 * @param [catcher] |
| 729 */ |
| 730 Action.Join = function( action, finisher, catcher ) |
| 731 { |
| 732 Action.Join_class.init.call( this, action, finisher, catcher ); |
| 733 }; |
| 734 Action.Join.prototype = new Action.Join_class(); |
| 735 |
| 736 //------------------------------------------------------- |
| 737 // Join_Timeout |
| 738 //------------------------------------------------------- |
| 739 /** |
| 740 * Join with another action and set a timer that may preemptively complete. |
| 741 * |
| 742 * Note that the timer starts on go() regardless of the state of the joined acti
on. If you invoke a Join_Timeout on an |
| 743 * action which never itself is invoked, then the timeout always causes completi
on. |
| 744 * |
| 745 * It would be perfectly reasonable to have an action which started a timer when
some other action began, but that would |
| 746 * require a subclass of Asynchronous_Action that had a hook at the start of exe
cution, rather than merely upon |
| 747 * completion. |
| 748 * |
| 749 * @constructor |
| 750 * @param {Action.Asynchronous_Action} action |
| 751 * @param duration |
| 752 * @param [finisher] |
| 753 * @param [catcher] |
| 754 */ |
| 755 Action.Join_Timeout = function( action, duration, finisher, catcher ) |
| 756 { |
| 757 Action.Join_class.init.call( this, action, finisher, catcher ); |
| 758 |
| 759 /** |
| 760 * The identifier of the platform timer. It's used for early termination of th
e timer, if needed. |
| 761 * @type {*} |
| 762 */ |
| 763 this._timer_id = null; |
| 764 |
| 765 this.duration = duration; |
| 766 |
| 767 /** |
| 768 * Flag indicating that the action has timed out. |
| 769 * @type {boolean} |
| 770 * @private |
| 771 */ |
| 772 this._timed_out = false; |
| 773 }; |
| 774 Action.Join_Timeout.prototype = new Action.Join_class(); |
| 775 |
| 776 /** |
| 777 * Flag indicating that the action has timed out. This is a read-only version of
a private property, but with the |
| 778 * caveat that calling it is only valid in a completed state. |
| 779 * |
| 780 * @this {Action.Join_Timeout} |
| 781 * @return {boolean} |
| 782 */ |
| 783 Object.defineProperty( Action.Join_Timeout.prototype, "timed_out", { |
| 784 /** |
| 785 * @this {Action.Join_Timeout} |
| 786 */ |
| 787 get: function() |
| 788 { |
| 789 if ( this.completed ) |
| 790 { |
| 791 return this._timed_out; |
| 792 } |
| 793 else |
| 794 { |
| 795 throw new Error( "Action is not yet completed." ); |
| 796 } |
| 797 } |
| 798 } ); |
| 799 |
| 800 Action.Join_Timeout.prototype._go = function() |
| 801 { |
| 802 Action.Join_class.prototype._go.apply( this, arguments ); |
| 803 if ( !this.completed ) |
| 804 { |
| 805 this._timer_id = Action_Platform.set_timer( this.ding.bind( this ), this.dur
ation ); |
| 806 } |
| 807 }; |
| 808 |
| 809 /** |
| 810 * The timer just went off. |
| 811 */ |
| 812 Action.Join_Timeout.prototype.ding = function() |
| 813 { |
| 814 this._timer_id = null; |
| 815 /* |
| 816 * If we've already completed, we don't change the completion state. |
| 817 */ |
| 818 if ( this.completed ) |
| 819 return; |
| 820 /* |
| 821 * Since we haven't completed at this point, the timer has gone off before the
action completed. Timeout is |
| 822 * considered an exceptional completion. |
| 823 */ |
| 824 this._timed_out = true; |
| 825 this.end_badly( new Error( "Action timed out." ) ); |
| 826 }; |
| 827 |
| 828 /** |
| 829 * Termination requires clearing any timer that may still be active. |
| 830 * |
| 831 * @override |
| 832 */ |
| 833 Action.Join_Timeout.prototype.terminate = function() |
| 834 { |
| 835 /* |
| 836 * Clear the timer so it doesn't ding. Clear the timer id because as an object
reference it acts as a resource. |
| 837 */ |
| 838 if ( this._timer_id ) |
| 839 { |
| 840 Action_Platform.clear_timer( this._timer_id ); |
| 841 this._timer_id = null; |
| 842 } |
| 843 |
| 844 Action.Join_class.prototype.terminate.call( this ); |
| 845 }; |
| 846 |
| 847 //------------------------------------------------------- |
| 848 /** |
| 849 * @constructor |
| 850 * @implements {Action.JM_Attentive} |
| 851 * @param {Array.Joinable} actions |
| 852 */ |
| 853 Action.Join_Conjunction = function( actions ) |
| 854 { |
| 855 /** |
| 856 * The conjunction of actions is reliable only if all the actions are reliable
. |
| 857 */ |
| 858 this.reliable = true; |
| 859 for ( var j = 0 ; j < actions.length ; ++j ) |
| 860 { |
| 861 if ( !actions[ j ].reliable ) |
| 862 { |
| 863 this.reliable = false; |
| 864 break; |
| 865 } |
| 866 } |
| 867 }; |
| 868 |
| 869 Action.Join_Conjunction.prototype.notice_good = function() |
| 870 { |
| 871 }; |
| 872 |
| 873 Action.Join_Conjunction.prototype.notice_bad = function() |
| 874 { |
| 875 }; |
| 876 |
| 877 /* |
| 878 * The guard around the definition of 'exports' is present because in js-test-dr
iver it's not in scope. |
| 879 */ |
| 880 if ( typeof exports === 'undefined' ) |
| 881 exports = {}; |
| 882 exports.Action = Action; |
OLD | NEW |