| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: events |
| | 7 | * |
| | 8 | * This module defines the event framework. An event is a programmed |
| | 9 | * operation that occurs at a particular point in the game; an event can |
| | 10 | * be turn-based, in which case it occurs after a given number of turns |
| | 11 | * has elapsed, or it can occur in real time, which means that it occurs |
| | 12 | * after a particular interval of time has elapsed. |
| | 13 | */ |
| | 14 | |
| | 15 | #include "adv3.h" |
| | 16 | #include <dict.h> |
| | 17 | #include <gramprod.h> |
| | 18 | |
| | 19 | |
| | 20 | /* ------------------------------------------------------------------------ */ |
| | 21 | /* |
| | 22 | * Run the main scheduling loop. This continues until we encounter an |
| | 23 | * end-of-file error reading from the console, or a QuitException is |
| | 24 | * thrown to terminate the game. |
| | 25 | */ |
| | 26 | runScheduler() |
| | 27 | { |
| | 28 | /* keep going until we quit the game */ |
| | 29 | for (;;) |
| | 30 | { |
| | 31 | /* catch the exceptions that terminate the game */ |
| | 32 | try |
| | 33 | { |
| | 34 | local minTime; |
| | 35 | local vec; |
| | 36 | |
| | 37 | /* start with an empty list of schedulable items */ |
| | 38 | vec = new Vector(10); |
| | 39 | |
| | 40 | /* find the lowest time at which something is ready to run */ |
| | 41 | minTime = nil; |
| | 42 | foreach (local cur in Schedulable.allSchedulables) |
| | 43 | { |
| | 44 | local curTime; |
| | 45 | |
| | 46 | /* get this item's next eligible run time */ |
| | 47 | curTime = cur.getNextRunTime(); |
| | 48 | |
| | 49 | /* |
| | 50 | * if it's not nil, and it's equal to or below the |
| | 51 | * lowest we've seen so far, note it |
| | 52 | */ |
| | 53 | if (curTime != nil && (minTime == nil || curTime <= minTime)) |
| | 54 | { |
| | 55 | /* |
| | 56 | * if this is different from the current minimum |
| | 57 | * schedulable time, clear out the list of |
| | 58 | * schedulables, because the list keeps track of the |
| | 59 | * items at the lowest time only |
| | 60 | */ |
| | 61 | if (minTime != nil && curTime < minTime) |
| | 62 | vec.removeRange(1, vec.length()); |
| | 63 | |
| | 64 | /* add this item to the list */ |
| | 65 | vec.append(cur); |
| | 66 | |
| | 67 | /* note the new lowest schedulable time */ |
| | 68 | minTime = curTime; |
| | 69 | } |
| | 70 | } |
| | 71 | |
| | 72 | /* |
| | 73 | * if nothing's ready to run, the game is over by default, |
| | 74 | * since we cannot escape this state - we can't ourselves |
| | 75 | * change anything's run time, so if nothing's ready to run |
| | 76 | * now, we won't be able to change that, and so nothing will |
| | 77 | * ever be ready to run |
| | 78 | */ |
| | 79 | if (minTime == nil) |
| | 80 | { |
| | 81 | "\b[Error: nothing is available for scheduling - |
| | 82 | terminating]\b"; |
| | 83 | return; |
| | 84 | } |
| | 85 | |
| | 86 | /* |
| | 87 | * Advance the global turn counter by the amount of game |
| | 88 | * clock time we're consuming now. |
| | 89 | */ |
| | 90 | libGlobal.totalTurns += minTime - Schedulable.gameClockTime; |
| | 91 | |
| | 92 | /* |
| | 93 | * advance the game clock to the minimum run time - nothing |
| | 94 | * interesting happens in game time until then, so we can |
| | 95 | * skip straight ahead to this time |
| | 96 | */ |
| | 97 | Schedulable.gameClockTime = minTime; |
| | 98 | |
| | 99 | /* calculate the schedule order for each item */ |
| | 100 | vec.forEach({x: x.calcScheduleOrder()}); |
| | 101 | |
| | 102 | /* |
| | 103 | * We have a list of everything schedulable at the current |
| | 104 | * game clock time. Sort the list in ascending scheduling |
| | 105 | * order, so that the higher priority items come first in |
| | 106 | * the list. |
| | 107 | */ |
| | 108 | vec = vec.sort( |
| | 109 | SortAsc, {a, b: a.scheduleOrder - b.scheduleOrder}); |
| | 110 | |
| | 111 | /* |
| | 112 | * Run through the list and run each item. Keep running |
| | 113 | * each item as long as it's ready to run - that is, as long |
| | 114 | * as its schedulable time equals the game clock time. |
| | 115 | */ |
| | 116 | vecLoop: |
| | 117 | foreach (local cur in vec) |
| | 118 | { |
| | 119 | /* run this item for as long as it's ready to run */ |
| | 120 | while (cur.getNextRunTime() == minTime) |
| | 121 | { |
| | 122 | try |
| | 123 | { |
| | 124 | /* |
| | 125 | * execute this item - if it doesn't want to be |
| | 126 | * called again without considering other |
| | 127 | * objects, stop looping and refigure the |
| | 128 | * scheduling order from scratch |
| | 129 | */ |
| | 130 | if (!cur.executeTurn()) |
| | 131 | break vecLoop; |
| | 132 | } |
| | 133 | catch (Exception exc) |
| | 134 | { |
| | 135 | /* |
| | 136 | * The scheduled operation threw an exception. |
| | 137 | * If the schedulable's next run time didn't get |
| | 138 | * updated, then the same schedulable will be |
| | 139 | * considered ready to run again immediately on |
| | 140 | * the next time through the loop. It's quite |
| | 141 | * possible in this case that we'll simply repeat |
| | 142 | * the operation that threw the exception and get |
| | 143 | * right back here again. If this happens, it |
| | 144 | * will effectively starve all of the other |
| | 145 | * schedulables. To ensure that other |
| | 146 | * schedulables get a chance to run before we try |
| | 147 | * this erroneous operation again, advance its |
| | 148 | * next run time by one unit if it hasn't already |
| | 149 | * been advanced. |
| | 150 | */ |
| | 151 | if (cur.getNextRunTime() == minTime) |
| | 152 | cur.incNextRunTime(1); |
| | 153 | |
| | 154 | /* re-throw the exception */ |
| | 155 | throw exc; |
| | 156 | } |
| | 157 | } |
| | 158 | } |
| | 159 | } |
| | 160 | catch (EndOfFileException eofExc) |
| | 161 | { |
| | 162 | /* end of file reading command input - we're done */ |
| | 163 | return; |
| | 164 | } |
| | 165 | catch (QuittingException quitExc) |
| | 166 | { |
| | 167 | /* explicitly quitting - we're done */ |
| | 168 | return; |
| | 169 | } |
| | 170 | catch (RestartSignal rsSig) |
| | 171 | { |
| | 172 | /* |
| | 173 | * Restarting - re-throw the signal for handling in the |
| | 174 | * system startup code. Note that we explicitly catch this |
| | 175 | * signal, only to rethrow it, because we'd otherwise flag it |
| | 176 | * as an unhandled error in the catch-all Exception handler. |
| | 177 | */ |
| | 178 | throw rsSig; |
| | 179 | } |
| | 180 | catch (RuntimeError rtErr) |
| | 181 | { |
| | 182 | /* if this is a debugger error of some kind, re-throw it */ |
| | 183 | if (rtErr.isDebuggerSignal) |
| | 184 | throw rtErr; |
| | 185 | |
| | 186 | /* display the error, but keep going */ |
| | 187 | "\b[<<rtErr.displayException()>>]\b"; |
| | 188 | } |
| | 189 | catch (TerminateCommandException tce) |
| | 190 | { |
| | 191 | /* |
| | 192 | * Aborted command - ignore it. This is most like to occur |
| | 193 | * when a fuse, daemon, or the like tries to terminate itself |
| | 194 | * with this exception, thinking it's operating in a normal |
| | 195 | * command execution environment. As a convenience, simply |
| | 196 | * ignore these exceptions so that any code can use them to |
| | 197 | * abort everything and return to the main scheduling loop. |
| | 198 | */ |
| | 199 | } |
| | 200 | catch (ExitSignal es) |
| | 201 | { |
| | 202 | /* ignore this, just as we ignore TerminateCommandException */ |
| | 203 | } |
| | 204 | catch (ExitActionSignal eas) |
| | 205 | { |
| | 206 | /* ignore this, just as we ignore TerminateCommandException */ |
| | 207 | } |
| | 208 | catch (Exception exc) |
| | 209 | { |
| | 210 | /* some other unhandled exception - display it and keep going */ |
| | 211 | "\b[Unhandled exception: <<exc.displayException()>>]\b"; |
| | 212 | } |
| | 213 | } |
| | 214 | } |
| | 215 | |
| | 216 | /* ------------------------------------------------------------------------ */ |
| | 217 | /* |
| | 218 | * An item that can be scheduled for time-based notifications. The main |
| | 219 | * scheduler loop in runScheduler() operates on objects of this class. |
| | 220 | * |
| | 221 | * Note that we build a list of all Schedulable instances during |
| | 222 | * pre-initialization. If any Schedulable objects are dynamically |
| | 223 | * created, they must be added to the list explicitly after creation in |
| | 224 | * order for the event manager to schedule them for execution. The |
| | 225 | * default constructor does this automatically, so subclasses can simply |
| | 226 | * inherit our constructor to be added to the master list. |
| | 227 | */ |
| | 228 | class Schedulable: object |
| | 229 | /* construction - add myself to the Schedulable list */ |
| | 230 | construct() |
| | 231 | { |
| | 232 | /* |
| | 233 | * Add myself to the master list of Schedulable instances. Note |
| | 234 | * that we must update the list in the Schedulable class itself. |
| | 235 | */ |
| | 236 | Schedulable.allSchedulables += self; |
| | 237 | } |
| | 238 | |
| | 239 | /* |
| | 240 | * Get the next time (on the game clock) at which I'm eligible for |
| | 241 | * execution. We won't receive any scheduling notifications until |
| | 242 | * this time. If this object doesn't want any scheduling |
| | 243 | * notifications, return nil. |
| | 244 | */ |
| | 245 | getNextRunTime() { return nextRunTime; } |
| | 246 | |
| | 247 | /* advance my next run time by the given number of clock units */ |
| | 248 | incNextRunTime(amt) |
| | 249 | { |
| | 250 | if (nextRunTime != nil) |
| | 251 | nextRunTime += amt; |
| | 252 | } |
| | 253 | |
| | 254 | /* |
| | 255 | * Notify this object that its scheduled run time has arrived. This |
| | 256 | * should perform the scheduled task. If the scheduled task takes |
| | 257 | * any game time, the object's internal next run time should be |
| | 258 | * updated accordingly. |
| | 259 | * |
| | 260 | * The scheduler will invoke this method of the same object |
| | 261 | * repeatedly for as long as its nextRunTime remains unchanged AND |
| | 262 | * this method returns true. If the object's scheduling priority |
| | 263 | * changes relative to other schedulable objects, it should return |
| | 264 | * nil here to tell the scheduler to recalculate scheduling |
| | 265 | * priorities. |
| | 266 | */ |
| | 267 | executeTurn() { return true; } |
| | 268 | |
| | 269 | /* |
| | 270 | * Scheduling order. This determines which item goes first when |
| | 271 | * multiple items are schedulable at the same time (i.e., they all |
| | 272 | * have the same getNextRunTime() values). The item with the lowest |
| | 273 | * number here goes first. |
| | 274 | * |
| | 275 | * This should never be evaluated except immediately after a call to |
| | 276 | * calcScheduleOrder. |
| | 277 | */ |
| | 278 | scheduleOrder = 100 |
| | 279 | |
| | 280 | /* |
| | 281 | * Calculate the scheduling order, returning the order value and |
| | 282 | * storing it in our property scheduleOrder. This is used to |
| | 283 | * calculate and cache the value prior to sorting a list of |
| | 284 | * schedulable items. We use this two-step approach (first |
| | 285 | * calculate, then sort) so that we avoid repeatedly evaluating a |
| | 286 | * complex calculation, if indeed there is a complex calculation to |
| | 287 | * perform. |
| | 288 | * |
| | 289 | * By default, we assume that the schedule order is static, so we |
| | 290 | * simply leave our scheduleOrder property unchanged and return its |
| | 291 | * present value. |
| | 292 | */ |
| | 293 | calcScheduleOrder() { return scheduleOrder; } |
| | 294 | |
| | 295 | /* my next running time, in game clock time */ |
| | 296 | nextRunTime = nil |
| | 297 | |
| | 298 | /* |
| | 299 | * A class variable giving the current game clock time. This is a |
| | 300 | * class variable because there's only one global game clock. The |
| | 301 | * game clock starts at zero and increments in game time units; a |
| | 302 | * game time unit is the arbitrary quantum of time for our event |
| | 303 | * scheduling system. |
| | 304 | */ |
| | 305 | gameClockTime = 0 |
| | 306 | |
| | 307 | /* |
| | 308 | * A list of all of the Schedulable objects in the game. We set this |
| | 309 | * up during pre-initialization; if any Schedulable instances are |
| | 310 | * created dynamically, they must be explicitly added to this list |
| | 311 | * after creation. |
| | 312 | */ |
| | 313 | allSchedulables = nil |
| | 314 | ; |
| | 315 | |
| | 316 | /* |
| | 317 | * Pre-initializer: build the master list of Schedulable instances |
| | 318 | */ |
| | 319 | PreinitObject |
| | 320 | /* |
| | 321 | * Execute preinitialization. Build a list of all of the schedulable |
| | 322 | * objects in the game, so that we can scan this list quickly during |
| | 323 | * play. |
| | 324 | */ |
| | 325 | execute() |
| | 326 | { |
| | 327 | local vec; |
| | 328 | |
| | 329 | /* set up an empty vector to hold the schedulable objects */ |
| | 330 | vec = new Vector(32); |
| | 331 | |
| | 332 | /* add all of the Schedulable instances to the vector */ |
| | 333 | forEachInstance(Schedulable, {s: vec.append(s)}); |
| | 334 | |
| | 335 | /* save the list of Schedulable instances as an ordinary list */ |
| | 336 | Schedulable.allSchedulables = vec.toList(); |
| | 337 | } |
| | 338 | ; |
| | 339 | |
| | 340 | /* ------------------------------------------------------------------------ */ |
| | 341 | /* |
| | 342 | * Basic Event Manager. This is a common base class for the game-time |
| | 343 | * and real-time event managers. This class handles the details of |
| | 344 | * managing the event queue; the subclasses must define the specifics of |
| | 345 | * event timing. |
| | 346 | */ |
| | 347 | class BasicEventManager: object |
| | 348 | /* add an event */ |
| | 349 | addEvent(event) |
| | 350 | { |
| | 351 | /* append the event to our list */ |
| | 352 | events_.append(event); |
| | 353 | } |
| | 354 | |
| | 355 | /* remove an event */ |
| | 356 | removeEvent(event) |
| | 357 | { |
| | 358 | /* remove the event from our list */ |
| | 359 | events_.removeElement(event); |
| | 360 | } |
| | 361 | |
| | 362 | /* |
| | 363 | * Remove events matching the given object and property combination. |
| | 364 | * We remove all events that match both the object and property |
| | 365 | * (events matching only the object or only the property are not |
| | 366 | * affected). |
| | 367 | * |
| | 368 | * This is provided mostly as a convenience for cases where an event |
| | 369 | * is known to be uniquely identifiable by its object and property |
| | 370 | * values; this saves the caller the trouble of keeping track of the |
| | 371 | * Event object created when the event was first registered. |
| | 372 | * |
| | 373 | * When a particular object/property combination might be used in |
| | 374 | * several different events, it's better to keep a reference to the |
| | 375 | * Event object representing each event, and use removeEvent() to |
| | 376 | * remove the specific Event object of interest. |
| | 377 | * |
| | 378 | * Returns true if we find any matching events, nil if not. |
| | 379 | */ |
| | 380 | removeMatchingEvents(obj, prop) |
| | 381 | { |
| | 382 | local found; |
| | 383 | |
| | 384 | /* |
| | 385 | * Scan our list, and remove each event matching the parameters. |
| | 386 | * Note that it's safe to remove things from a vector that we're |
| | 387 | * iterating with foreach(), since foreach() makes a safe copy |
| | 388 | * of the vector for the iteration. |
| | 389 | */ |
| | 390 | found = nil; |
| | 391 | foreach (local cur in events_) |
| | 392 | { |
| | 393 | /* if this one matches, remove it */ |
| | 394 | if (cur.eventMatches(obj, prop)) |
| | 395 | { |
| | 396 | /* remove the event */ |
| | 397 | removeEvent(cur); |
| | 398 | |
| | 399 | /* note that we found a match */ |
| | 400 | found = true; |
| | 401 | } |
| | 402 | } |
| | 403 | |
| | 404 | /* return our 'found' indication */ |
| | 405 | return found; |
| | 406 | } |
| | 407 | |
| | 408 | /* |
| | 409 | * Remove the current event - this is provided for convenience so |
| | 410 | * that an event can cancel itself in the course of its execution. |
| | 411 | * |
| | 412 | * Note that this has no effect on the current event execution - |
| | 413 | * this simply prevents the event from receiving additional |
| | 414 | * notifications in the future. |
| | 415 | */ |
| | 416 | removeCurrentEvent() |
| | 417 | { |
| | 418 | /* remove the currently active event from our list */ |
| | 419 | removeEvent(curEvent_); |
| | 420 | } |
| | 421 | |
| | 422 | /* event list - each instance must initialize this to a vector */ |
| | 423 | // events_ = nil |
| | 424 | ; |
| | 425 | |
| | 426 | /* |
| | 427 | * Event Manager. This is a schedulable object that keeps track of |
| | 428 | * fuses and daemons, and schedules their execution. |
| | 429 | */ |
| | 430 | eventManager: BasicEventManager, Schedulable |
| | 431 | /* |
| | 432 | * Use a scheduling order of 1000 to ensure we go after all actors. |
| | 433 | * By default, actors use scheduling orders in the range 100 to 400, |
| | 434 | * so our order of 1000 ensures that fuses and daemons run after all |
| | 435 | * characters on a given turn. |
| | 436 | */ |
| | 437 | scheduleOrder = 1000 |
| | 438 | |
| | 439 | /* |
| | 440 | * Get the next run time. We'll find the lowest run time of our |
| | 441 | * fuses and daemons and return that. |
| | 442 | */ |
| | 443 | getNextRunTime() |
| | 444 | { |
| | 445 | local minTime; |
| | 446 | |
| | 447 | /* |
| | 448 | * run through our list of events, and find the event that is |
| | 449 | * scheduled to run at the lowest game clock time |
| | 450 | */ |
| | 451 | minTime = nil; |
| | 452 | foreach (local cur in events_) |
| | 453 | { |
| | 454 | local curTime; |
| | 455 | |
| | 456 | /* get this item's scheduled run time */ |
| | 457 | curTime = cur.getNextRunTime(); |
| | 458 | |
| | 459 | /* if it's not nil and it's the lowest so far, remember it */ |
| | 460 | if (curTime != nil && (minTime == nil || curTime < minTime)) |
| | 461 | minTime = curTime; |
| | 462 | } |
| | 463 | |
| | 464 | /* return the minimum time we found */ |
| | 465 | return minTime; |
| | 466 | } |
| | 467 | |
| | 468 | /* |
| | 469 | * Execute a turn. We'll execute each fuse and each daemon that is |
| | 470 | * currently schedulable. |
| | 471 | */ |
| | 472 | executeTurn() |
| | 473 | { |
| | 474 | local lst; |
| | 475 | |
| | 476 | /* |
| | 477 | * build a list of all of our events with the current game clock |
| | 478 | * time - these are the events that are currently schedulable |
| | 479 | */ |
| | 480 | lst = events_.subset({x: x.getNextRunTime() |
| | 481 | == Schedulable.gameClockTime}); |
| | 482 | |
| | 483 | /* execute the items in this list */ |
| | 484 | executeList(lst); |
| | 485 | |
| | 486 | /* no change in scheduling priorities */ |
| | 487 | return true; |
| | 488 | } |
| | 489 | |
| | 490 | /* |
| | 491 | * Execute a command prompt turn. We'll execute each |
| | 492 | * per-command-prompt daemon. |
| | 493 | */ |
| | 494 | executePrompt() |
| | 495 | { |
| | 496 | /* execute all of the per-command-prompt daemons */ |
| | 497 | executeList(events_.subset({x: x.isPromptDaemon})); |
| | 498 | } |
| | 499 | |
| | 500 | /* |
| | 501 | * internal service routine - execute the fuses and daemons in the |
| | 502 | * given list, in eventOrder priority order |
| | 503 | */ |
| | 504 | executeList(lst) |
| | 505 | { |
| | 506 | /* sort the list in ascending event order */ |
| | 507 | lst = lst.toList() |
| | 508 | .sort(SortAsc, {a, b: a.eventOrder - b.eventOrder}); |
| | 509 | |
| | 510 | /* run through the list and execute each item ready to run */ |
| | 511 | foreach (local cur in lst) |
| | 512 | { |
| | 513 | /* remember our old active event, then establish the new one */ |
| | 514 | local oldEvent = curEvent_; |
| | 515 | curEvent_ = cur; |
| | 516 | |
| | 517 | /* make sure we restore things on the way out */ |
| | 518 | try |
| | 519 | { |
| | 520 | local pc; |
| | 521 | |
| | 522 | /* have the player character note the pre-event conditions */ |
| | 523 | pc = gPlayerChar; |
| | 524 | pc.noteConditionsBefore(); |
| | 525 | |
| | 526 | /* cancel any sense caching currently in effect */ |
| | 527 | libGlobal.disableSenseCache(); |
| | 528 | |
| | 529 | /* execute the event */ |
| | 530 | cur.executeEvent(); |
| | 531 | |
| | 532 | /* |
| | 533 | * if the player character is the same as it was, ask |
| | 534 | * the player character to note any change in conditions |
| | 535 | */ |
| | 536 | if (gPlayerChar == pc) |
| | 537 | pc.noteConditionsAfter(); |
| | 538 | } |
| | 539 | catch (Exception exc) |
| | 540 | { |
| | 541 | /* |
| | 542 | * If an event throws an exception out of its handler, |
| | 543 | * remove the event from the active list. If we were to |
| | 544 | * leave it active, we'd go back and execute the same |
| | 545 | * event again the next time we look for something to |
| | 546 | * schedule, and that would in turn probably just |
| | 547 | * encounter the same exception - so we'd be stuck in an |
| | 548 | * infinite loop executing this erroneous code. To |
| | 549 | * ensure that we don't get stuck, remove the event. |
| | 550 | */ |
| | 551 | removeCurrentEvent(); |
| | 552 | |
| | 553 | /* re-throw the exception */ |
| | 554 | throw exc; |
| | 555 | } |
| | 556 | finally |
| | 557 | { |
| | 558 | /* restore the enclosing current event */ |
| | 559 | curEvent_ = oldEvent; |
| | 560 | } |
| | 561 | } |
| | 562 | } |
| | 563 | |
| | 564 | /* our list of fuses and daemons */ |
| | 565 | events_ = static new Vector(20) |
| | 566 | |
| | 567 | /* the event currently being executed */ |
| | 568 | curEvent_ = nil |
| | 569 | ; |
| | 570 | |
| | 571 | /* |
| | 572 | * Pseudo-action subclass to represent the action environment while |
| | 573 | * processing a daemon, fuse, or other event. |
| | 574 | */ |
| | 575 | class EventAction: Action |
| | 576 | /* |
| | 577 | * event actions are internal system actions; they don't consume |
| | 578 | * additional turns themselves, since they run between player turns |
| | 579 | */ |
| | 580 | actionTime = 0; |
| | 581 | ; |
| | 582 | |
| | 583 | /* |
| | 584 | * A basic event, for game-time and real-time events. |
| | 585 | */ |
| | 586 | class BasicEvent: object |
| | 587 | /* construction */ |
| | 588 | construct(obj, prop) |
| | 589 | { |
| | 590 | /* remember the object and property to call at execution */ |
| | 591 | obj_ = obj; |
| | 592 | prop_ = prop; |
| | 593 | } |
| | 594 | |
| | 595 | /* |
| | 596 | * Execute the event. This must be overridden by the subclass to |
| | 597 | * perform the appropriate operation when executed. In particular, |
| | 598 | * the subclass must reschedule or unschedule the event, as |
| | 599 | * appropriate. |
| | 600 | */ |
| | 601 | executeEvent() { } |
| | 602 | |
| | 603 | /* does this event match the given object/property combination? */ |
| | 604 | eventMatches(obj, prop) { return obj == obj_ && prop == prop_; } |
| | 605 | |
| | 606 | /* |
| | 607 | * Call our underlying method. This is an internal routine intended |
| | 608 | * for use by the executeEvent() implementations. |
| | 609 | */ |
| | 610 | callMethod() |
| | 611 | { |
| | 612 | /* |
| | 613 | * invoke the method in our sensory context, and in a simulated |
| | 614 | * action environment |
| | 615 | */ |
| | 616 | withActionEnv(EventAction, gPlayerChar, |
| | 617 | {: callWithSenseContext(source_, sense_, |
| | 618 | {: obj_.(self.prop_)() }) }); |
| | 619 | } |
| | 620 | |
| | 621 | /* the object and property we invoke */ |
| | 622 | obj_ = nil |
| | 623 | prop_ = nil |
| | 624 | |
| | 625 | /* |
| | 626 | * The sensory context of the event. When the event fires, we'll |
| | 627 | * execute its method in this sensory context, so that any messages |
| | 628 | * generated will be displayed only if the player character can |
| | 629 | * sense the source object in the given sense. |
| | 630 | * |
| | 631 | * By default, these are nil, which means that the event's messages |
| | 632 | * will be displayed (or, at least, they won't be suppressed because |
| | 633 | * of the sensory context). |
| | 634 | */ |
| | 635 | source_ = nil |
| | 636 | sense_ = nil |
| | 637 | ; |
| | 638 | |
| | 639 | /* |
| | 640 | * Base class for fuses and daemons |
| | 641 | */ |
| | 642 | class Event: BasicEvent |
| | 643 | /* our next run time, in game clock time */ |
| | 644 | getNextRunTime() { return nextRunTime; } |
| | 645 | |
| | 646 | /* delay our scheduled run time by the given number of turns */ |
| | 647 | delayEvent(turns) { nextRunTime += turns; } |
| | 648 | |
| | 649 | /* remove this event from the event manager */ |
| | 650 | removeEvent() { eventManager.removeEvent(self); } |
| | 651 | |
| | 652 | /* |
| | 653 | * Event order - this establishes the order we run relative to other |
| | 654 | * events scheduled to run at the same game clock time. Lowest |
| | 655 | * number goes first. By default, we provide an event order of 100, |
| | 656 | * which should leave plenty of room for custom events before and |
| | 657 | * after default events. |
| | 658 | */ |
| | 659 | eventOrder = 100 |
| | 660 | |
| | 661 | /* creation */ |
| | 662 | construct(obj, prop) |
| | 663 | { |
| | 664 | /* inherit default handling */ |
| | 665 | inherited(obj, prop); |
| | 666 | |
| | 667 | /* add myself to the event manager's active event list */ |
| | 668 | eventManager.addEvent(self); |
| | 669 | } |
| | 670 | |
| | 671 | /* |
| | 672 | * our next execution time, expressed in game clock time; by |
| | 673 | * default, we'll set this to nil, which means that we are not |
| | 674 | * scheduled to execute at all |
| | 675 | */ |
| | 676 | nextRunTime = nil |
| | 677 | |
| | 678 | /* by default, we're not a per-command-prompt daemon */ |
| | 679 | isPromptDaemon = nil |
| | 680 | ; |
| | 681 | |
| | 682 | /* |
| | 683 | * Fuse. A fuse is an event that fires once at a given time in the |
| | 684 | * future. Once a fuse is executed, it is removed from further |
| | 685 | * scheduling. |
| | 686 | */ |
| | 687 | class Fuse: Event |
| | 688 | /* |
| | 689 | * Creation. 'turns' is the number of turns in the future at which |
| | 690 | * the fuse is executed; if turns is 0, the fuse will be executed on |
| | 691 | * the current turn. |
| | 692 | */ |
| | 693 | construct(obj, prop, turns) |
| | 694 | { |
| | 695 | /* inherit the base class constructor */ |
| | 696 | inherited(obj, prop); |
| | 697 | |
| | 698 | /* |
| | 699 | * set my scheduled time to the current game clock time plus the |
| | 700 | * number of turns into the future |
| | 701 | */ |
| | 702 | nextRunTime = Schedulable.gameClockTime + turns; |
| | 703 | } |
| | 704 | |
| | 705 | /* execute the fuse */ |
| | 706 | executeEvent() |
| | 707 | { |
| | 708 | /* call my method */ |
| | 709 | callMethod(); |
| | 710 | |
| | 711 | /* a fuse fires only once, so remove myself from further scheduling */ |
| | 712 | eventManager.removeEvent(self); |
| | 713 | } |
| | 714 | ; |
| | 715 | |
| | 716 | /* |
| | 717 | * Sensory-context-sensitive fuse - this is a fuse with an explicit |
| | 718 | * sensory context. We'll run the fuse in its sense context, so any |
| | 719 | * messages generated will be visible only if the given source object is |
| | 720 | * reachable by the player character in the given sense. |
| | 721 | * |
| | 722 | * Conceptually, the source object is considered the source of any |
| | 723 | * messages that the fuse generates, and the messages pertain to the |
| | 724 | * given sense; so if the player character cannot sense the source |
| | 725 | * object in the given sense, the messages should not be displayed. For |
| | 726 | * example, if the fuse will describe the noise made by an alarm clock |
| | 727 | * when the alarm goes off, the source object would be the alarm clock |
| | 728 | * and the sense would be sound; this way, if the player character isn't |
| | 729 | * in hearing range of the alarm clock when the alarm goes off, we won't |
| | 730 | * display messages about the alarm noise. |
| | 731 | */ |
| | 732 | class SenseFuse: Fuse |
| | 733 | construct(obj, prop, turns, source, sense) |
| | 734 | { |
| | 735 | /* inherit the base constructor */ |
| | 736 | inherited(obj, prop, turns); |
| | 737 | |
| | 738 | /* remember our sensory context */ |
| | 739 | source_ = source; |
| | 740 | sense_ = sense; |
| | 741 | } |
| | 742 | ; |
| | 743 | |
| | 744 | /* |
| | 745 | * Daemon. A daemon is an event that fires repeatedly at given |
| | 746 | * intervals. When a daemon is executed, it is scheduled again for |
| | 747 | * execution after its interval elapses again. |
| | 748 | */ |
| | 749 | class Daemon: Event |
| | 750 | /* |
| | 751 | * Creation. 'interval' is the number of turns between invocations |
| | 752 | * of the daemon; this should be at least 1, which causes the daemon |
| | 753 | * to be invoked on each turn. The first execution will be |
| | 754 | * (interval-1) turns in the future - so if interval is 1, the |
| | 755 | * daemon will first be executed on the current turn, and if |
| | 756 | * interval is 2, the daemon will be executed on the next turn. |
| | 757 | */ |
| | 758 | construct(obj, prop, interval) |
| | 759 | { |
| | 760 | /* inherit the base class constructor */ |
| | 761 | inherited(obj, prop); |
| | 762 | |
| | 763 | /* |
| | 764 | * an interval of less than 1 is meaningless, so make sure it's |
| | 765 | * at least 1 |
| | 766 | */ |
| | 767 | if (interval < 1) |
| | 768 | interval = 1; |
| | 769 | |
| | 770 | /* remember my interval */ |
| | 771 | interval_ = interval; |
| | 772 | |
| | 773 | /* |
| | 774 | * set my initial execution time, in game clock time - add one |
| | 775 | * less than the interval to the current game clock time, so |
| | 776 | * that we count the current turn as yet to elapse for the |
| | 777 | * purposes of the interval before the daemon's first execution |
| | 778 | */ |
| | 779 | nextRunTime = Schedulable.gameClockTime + interval - 1; |
| | 780 | } |
| | 781 | |
| | 782 | /* execute the daemon */ |
| | 783 | executeEvent() |
| | 784 | { |
| | 785 | /* call my method */ |
| | 786 | callMethod(); |
| | 787 | |
| | 788 | /* advance our next run time by our interval */ |
| | 789 | nextRunTime += interval_; |
| | 790 | } |
| | 791 | |
| | 792 | /* our execution interval, in turns */ |
| | 793 | interval_ = 1 |
| | 794 | ; |
| | 795 | |
| | 796 | /* |
| | 797 | * Sensory-context-sensitive daemon - this is a daemon with an explicit |
| | 798 | * sensory context. This is the daemon counterpart of SenseFuse. |
| | 799 | */ |
| | 800 | class SenseDaemon: Daemon |
| | 801 | construct(obj, prop, interval, source, sense) |
| | 802 | { |
| | 803 | /* inherit the base constructor */ |
| | 804 | inherited(obj, prop, interval); |
| | 805 | |
| | 806 | /* remember our sensory context */ |
| | 807 | source_ = source; |
| | 808 | sense_ = sense; |
| | 809 | } |
| | 810 | ; |
| | 811 | |
| | 812 | /* |
| | 813 | * Command Prompt Daemon. This is a special type of daemon that |
| | 814 | * executes not according to the game clock, but rather once per command |
| | 815 | * prompt. The system executes all of these daemons just before each |
| | 816 | * time it prompts for a command line. |
| | 817 | */ |
| | 818 | class PromptDaemon: Event |
| | 819 | /* execute the daemon */ |
| | 820 | executeEvent() |
| | 821 | { |
| | 822 | /* |
| | 823 | * call my method - there's nothing else to do for this type of |
| | 824 | * daemon, since our scheduling is not affected by the game |
| | 825 | * clock |
| | 826 | */ |
| | 827 | callMethod(); |
| | 828 | } |
| | 829 | |
| | 830 | /* flag: we are a special per-command-prompt daemon */ |
| | 831 | isPromptDaemon = true |
| | 832 | ; |
| | 833 | |
| | 834 | /* |
| | 835 | * A one-time-only prompt daemon is a regular command prompt daemon, |
| | 836 | * except that it fires only once. After it fires once, the daemon |
| | 837 | * automatically deactivates itself, so that it won't fire again. |
| | 838 | * |
| | 839 | * Prompt daemons are occasionally useful for non-recurring processing, |
| | 840 | * when you want to defer some bit of code until a "safe" time between |
| | 841 | * turns. In these cases, the regular PromptDaemon is inconvenient to |
| | 842 | * use because it automatically recurs. This subclass is handy for these |
| | 843 | * cases, since it lets you schedule some bit of processing for a single |
| | 844 | * deferred execution. |
| | 845 | * |
| | 846 | * One special situation where one-time prompt daemons can be handy is in |
| | 847 | * triggering conversational events - such as initiating a conversation - |
| | 848 | * at the very beginning of the game. Initiating a conversation can only |
| | 849 | * be done from within an action context, but no action context is in |
| | 850 | * effect during the game's initialization. An easy way to deal with |
| | 851 | * this is to create a one-time prompt daemon during initialization, and |
| | 852 | * then trigger the event from the daemon's callback method. The prompt |
| | 853 | * daemon will set up a daemon action environment just before the first |
| | 854 | * command prompt is displayed, at which point the callback will be able |
| | 855 | * to trigger the event as though it were in ordinary action handler |
| | 856 | * code. |
| | 857 | */ |
| | 858 | class OneTimePromptDaemon: PromptDaemon |
| | 859 | executeEvent() |
| | 860 | { |
| | 861 | /* execute as normal */ |
| | 862 | inherited(); |
| | 863 | |
| | 864 | /* remove myself from the event list, so that I don't fire again */ |
| | 865 | removeEvent(); |
| | 866 | } |
| | 867 | ; |
| | 868 | |
| | 869 | /* ------------------------------------------------------------------------ */ |
| | 870 | /* |
| | 871 | * Real-Time Event Manager. This object manages all of the game's |
| | 872 | * real-time events, which are events that occur according to elapsed |
| | 873 | * real-world time. |
| | 874 | */ |
| | 875 | realTimeManager: BasicEventManager, InitObject |
| | 876 | /* |
| | 877 | * Get the elapsed game time at which the next real-time event is |
| | 878 | * scheduled. This returns a value which can be compared to that |
| | 879 | * returned by getElapsedTime(): if this value is less than or equal |
| | 880 | * to the value from getElapsedTime(), then the next event is reay |
| | 881 | * for immediate execution; otherwise, the result of subtracting |
| | 882 | * getElapsedTime() from our return value gives the number of |
| | 883 | * milliseconds until the next event is schedulable. |
| | 884 | * |
| | 885 | * Note that we don't calculate the delta to the next event time, |
| | 886 | * but instead return the absolute time, because the caller might |
| | 887 | * need to perform extra processing before using our return value. |
| | 888 | * If we returned a delta, that extra processing time wouldn't be |
| | 889 | * figured into the caller's determination of event schedulability. |
| | 890 | * |
| | 891 | * If we return nil, it means that there are no scheduled real-time |
| | 892 | * events. |
| | 893 | */ |
| | 894 | getNextEventTime() |
| | 895 | { |
| | 896 | local tMin; |
| | 897 | |
| | 898 | /* |
| | 899 | * run through our event list and find the event with the lowest |
| | 900 | * scheduled run time |
| | 901 | */ |
| | 902 | tMin = nil; |
| | 903 | foreach (local cur in events_) |
| | 904 | { |
| | 905 | local tCur; |
| | 906 | |
| | 907 | /* get the current item's time */ |
| | 908 | tCur = cur.getEventTime(); |
| | 909 | |
| | 910 | /* |
| | 911 | * if this one has a valid time, and we don't have a valid |
| | 912 | * time yet or this one is sooner than the soonest one we've |
| | 913 | * seen so far, note this one as the soonest so far |
| | 914 | */ |
| | 915 | if (tMin == nil |
| | 916 | || (tCur != nil && tCur < tMin)) |
| | 917 | { |
| | 918 | /* this is the soonest so far */ |
| | 919 | tMin = tCur; |
| | 920 | } |
| | 921 | } |
| | 922 | |
| | 923 | /* return the soonest event so far */ |
| | 924 | return tMin; |
| | 925 | } |
| | 926 | |
| | 927 | /* |
| | 928 | * Run any real-time events that are ready to execute, then return |
| | 929 | * the next event time. The return value has the same meaning as |
| | 930 | * that of getNextEventTime(). |
| | 931 | */ |
| | 932 | executeEvents() |
| | 933 | { |
| | 934 | local tMin; |
| | 935 | |
| | 936 | /* |
| | 937 | * Keep checking as long as we find anything to execute. Each |
| | 938 | * time we execute an event, we might consume enough time that |
| | 939 | * an item earlier in our queue that we originally dismissed as |
| | 940 | * unready has become ready to run. |
| | 941 | */ |
| | 942 | for (;;) |
| | 943 | { |
| | 944 | local foundEvent; |
| | 945 | |
| | 946 | /* we haven't yet run anything on this pass */ |
| | 947 | foundEvent = nil; |
| | 948 | |
| | 949 | /* we haven't found anything schedulable on this pass yet */ |
| | 950 | tMin = nil; |
| | 951 | |
| | 952 | /* run each event whose time is already here */ |
| | 953 | foreach (local cur in events_) |
| | 954 | { |
| | 955 | local tCur; |
| | 956 | |
| | 957 | /* |
| | 958 | * If this event has a non-nil time, and its time is |
| | 959 | * less than or equal to the current system clock time, |
| | 960 | * run this event. All event times are in terms of the |
| | 961 | * game elapsed time. |
| | 962 | * |
| | 963 | * If this event isn't schedulable, at least check to |
| | 964 | * see if it's the soonest schedulable event so far. |
| | 965 | */ |
| | 966 | tCur = cur.getEventTime(); |
| | 967 | if (tCur != nil && tCur <= getElapsedTime()) |
| | 968 | { |
| | 969 | /* cancel any sense caching currently in effect */ |
| | 970 | libGlobal.disableSenseCache(); |
| | 971 | |
| | 972 | /* execute this event */ |
| | 973 | cur.executeEvent(); |
| | 974 | |
| | 975 | /* note that we executed something */ |
| | 976 | foundEvent = true; |
| | 977 | } |
| | 978 | else if (tMin == nil |
| | 979 | || (tCur != nil && tCur < tMin)) |
| | 980 | { |
| | 981 | /* it's the soonest event so far */ |
| | 982 | tMin = tCur; |
| | 983 | } |
| | 984 | } |
| | 985 | |
| | 986 | /* if we didn't execute anything on this pass, stop scanning */ |
| | 987 | if (!foundEvent) |
| | 988 | break; |
| | 989 | } |
| | 990 | |
| | 991 | /* return the time of the next event */ |
| | 992 | return tMin; |
| | 993 | } |
| | 994 | |
| | 995 | /* |
| | 996 | * Get the current game elapsed time. This is the number of |
| | 997 | * milliseconds that has elapsed since the game was started, |
| | 998 | * counting only the continuous execution time. When the game is |
| | 999 | * saved, we save the elapsed time at that point; when the game is |
| | 1000 | * later restored, we project that saved time backwards from the |
| | 1001 | * current real-world time at restoration to get the real-world time |
| | 1002 | * where the game would have started if it had actually been played |
| | 1003 | * continuously in one session. |
| | 1004 | */ |
| | 1005 | getElapsedTime() |
| | 1006 | { |
| | 1007 | /* |
| | 1008 | * return the current system real-time counter minus the virtual |
| | 1009 | * starting time |
| | 1010 | */ |
| | 1011 | return getTime(GetTimeTicks) - startingTime; |
| | 1012 | } |
| | 1013 | |
| | 1014 | /* |
| | 1015 | * Set the current game elapsed time. This can be used to freeze |
| | 1016 | * the real-time clock - a caller can note the elapsed game time at |
| | 1017 | * one point by calling getElapsedTime(), and then pass the same |
| | 1018 | * value to this routine to ensure that no real time can effectively |
| | 1019 | * pass between the two calls. |
| | 1020 | */ |
| | 1021 | setElapsedTime(t) |
| | 1022 | { |
| | 1023 | /* |
| | 1024 | * set the virtual starting time to the current system real-time |
| | 1025 | * counter minus the given game elapsed time |
| | 1026 | */ |
| | 1027 | startingTime = getTime(GetTimeTicks) - t; |
| | 1028 | } |
| | 1029 | |
| | 1030 | /* |
| | 1031 | * The imaginary real-world time of the starting point of the game, |
| | 1032 | * treating the game as having been played from the start in one |
| | 1033 | * continous session. Whenever we restore a saved game, we project |
| | 1034 | * backwards from the current real-world time at restoration by the |
| | 1035 | * amount of continuous elapsed time in the saved game to find the |
| | 1036 | * point at which the game would have started if it had been played |
| | 1037 | * continuously in one session up to the restored point. |
| | 1038 | * |
| | 1039 | * We set a static initial value for this, using the interpreter's |
| | 1040 | * real-time clock value at compilation time. This ensures that |
| | 1041 | * we'll have a meaningful time base if any real-time events are |
| | 1042 | * created during pre-initialization. This static value will only be |
| | 1043 | * in effect during preinit; we're an InitObject, so our execute() |
| | 1044 | * method will be invoked at run-time start-up, and at that point |
| | 1045 | * we'll reset the zero point to the actual run-time start time. |
| | 1046 | */ |
| | 1047 | startingTime = static getTime(GetTimeTicks) |
| | 1048 | |
| | 1049 | /* |
| | 1050 | * Initialize at run-time startup. We want to set the zero point as |
| | 1051 | * the time when the player actually started playing the game (any |
| | 1052 | * time we spent in pre-initialization doesn't count on the real-time |
| | 1053 | * clock, since it's not part of the game per se). |
| | 1054 | */ |
| | 1055 | execute() |
| | 1056 | { |
| | 1057 | /* |
| | 1058 | * note the real-time starting point of the game, so we can |
| | 1059 | * calculate the elapsed game time later |
| | 1060 | */ |
| | 1061 | startingTime = getTime(GetTimeTicks); |
| | 1062 | } |
| | 1063 | |
| | 1064 | /* |
| | 1065 | * save the elapsed time so far - this is called just before we save |
| | 1066 | * a game so that we can pick up where we left off on the elapsed |
| | 1067 | * time clock when we restore the saved game |
| | 1068 | */ |
| | 1069 | saveElapsedTime() |
| | 1070 | { |
| | 1071 | /* remember the elapsed time so far */ |
| | 1072 | elapsedTimeAtSave = getElapsedTime(); |
| | 1073 | } |
| | 1074 | |
| | 1075 | /* |
| | 1076 | * Restore the elapsed time - this is called just after we restore a |
| | 1077 | * game. We'll project the saved elapsed time backwards to figure |
| | 1078 | * the imaginary starting time the game would have had if it had |
| | 1079 | * been played in one continuous session rather than being saved and |
| | 1080 | * restored. |
| | 1081 | */ |
| | 1082 | restoreElapsedTime() |
| | 1083 | { |
| | 1084 | /* |
| | 1085 | * project backwards from the current time by the saved elapsed |
| | 1086 | * time to get the virtual starting point that will give us the |
| | 1087 | * same current elapsed time on the system real-time clock |
| | 1088 | */ |
| | 1089 | startingTime = getTime(GetTimeTicks) - elapsedTimeAtSave; |
| | 1090 | } |
| | 1091 | |
| | 1092 | /* our event list */ |
| | 1093 | events_ = static new Vector(20) |
| | 1094 | |
| | 1095 | /* the event currently being executed */ |
| | 1096 | curEvent_ = nil |
| | 1097 | |
| | 1098 | /* |
| | 1099 | * saved elapsed time - we use this to figure the virtual starting |
| | 1100 | * time when we restore a saved game |
| | 1101 | */ |
| | 1102 | elapsedTimeAtSave = 0 |
| | 1103 | ; |
| | 1104 | |
| | 1105 | /* |
| | 1106 | * Real-time manager: pre-save notification receiver. When we're about |
| | 1107 | * to save the game, we'll note the current elapsed game time, so that |
| | 1108 | * when we later restore the game, we can figure the virtual starting |
| | 1109 | * point that will give us the same effective elapsed time on the system |
| | 1110 | * real-time clock. |
| | 1111 | */ |
| | 1112 | PreSaveObject |
| | 1113 | execute() |
| | 1114 | { |
| | 1115 | /* |
| | 1116 | * remember the elapsed time at the point we saved the game, so |
| | 1117 | * that we can restore it later |
| | 1118 | */ |
| | 1119 | realTimeManager.saveElapsedTime(); |
| | 1120 | } |
| | 1121 | ; |
| | 1122 | |
| | 1123 | /* |
| | 1124 | * Real-time manager: post-restore notification receiver. Immediately |
| | 1125 | * after we restore a game, we'll tell the real-time manager to refigure |
| | 1126 | * the virtual starting point of the game based on the saved elapsed |
| | 1127 | * time. |
| | 1128 | */ |
| | 1129 | PostRestoreObject |
| | 1130 | execute() |
| | 1131 | { |
| | 1132 | /* figure the new virtual starting time */ |
| | 1133 | realTimeManager.restoreElapsedTime(); |
| | 1134 | } |
| | 1135 | ; |
| | 1136 | |
| | 1137 | /* |
| | 1138 | * Real-Time Event. This is an event that occurs according to elapsed |
| | 1139 | * wall-clock time in the real world. |
| | 1140 | */ |
| | 1141 | class RealTimeEvent: BasicEvent |
| | 1142 | /* |
| | 1143 | * Get the elapsed real time at which this event is triggered. This |
| | 1144 | * is a time value in terms of realTimeManager.getElapsedTime(). |
| | 1145 | */ |
| | 1146 | getEventTime() |
| | 1147 | { |
| | 1148 | /* by default, simply return our eventTime value */ |
| | 1149 | return eventTime; |
| | 1150 | } |
| | 1151 | |
| | 1152 | /* construction */ |
| | 1153 | construct(obj, prop) |
| | 1154 | { |
| | 1155 | /* inherit default handling */ |
| | 1156 | inherited(obj, prop); |
| | 1157 | |
| | 1158 | /* add myself to the real-time event manager's active list */ |
| | 1159 | realTimeManager.addEvent(self); |
| | 1160 | } |
| | 1161 | |
| | 1162 | /* remove this event from the real-time event manager */ |
| | 1163 | removeEvent() { realTimeManager.removeEvent(self); } |
| | 1164 | |
| | 1165 | /* our scheduled event time */ |
| | 1166 | eventTime = 0 |
| | 1167 | ; |
| | 1168 | |
| | 1169 | /* |
| | 1170 | * Real-time fuse. This is an event that fires once at a specified |
| | 1171 | * elapsed time into the game. |
| | 1172 | */ |
| | 1173 | class RealTimeFuse: RealTimeEvent |
| | 1174 | /* |
| | 1175 | * Creation. 'delta' is the amount of real time (in milliseconds) |
| | 1176 | * that should elapse before the fuse is executed. If 'delta' is |
| | 1177 | * zero or negative, the fuse will be schedulable immediately. |
| | 1178 | */ |
| | 1179 | construct(obj, prop, delta) |
| | 1180 | { |
| | 1181 | /* inherit default handling */ |
| | 1182 | inherited(obj, prop); |
| | 1183 | |
| | 1184 | /* |
| | 1185 | * set my scheduled time to the current game elapsed time plus |
| | 1186 | * the delta - this will give us the time in terms of elapsed |
| | 1187 | * game time at which we'll be executed |
| | 1188 | */ |
| | 1189 | eventTime = realTimeManager.getElapsedTime() + delta; |
| | 1190 | } |
| | 1191 | |
| | 1192 | /* execute the fuse */ |
| | 1193 | executeEvent() |
| | 1194 | { |
| | 1195 | /* call my method */ |
| | 1196 | callMethod(); |
| | 1197 | |
| | 1198 | /* a fuse fires only once, so remove myself from further scheduling */ |
| | 1199 | realTimeManager.removeEvent(self); |
| | 1200 | } |
| | 1201 | ; |
| | 1202 | |
| | 1203 | /* |
| | 1204 | * Sensory-context-sensitive real-time fuse. This is a real-time fuse |
| | 1205 | * with an explicit sensory context. |
| | 1206 | */ |
| | 1207 | class RealTimeSenseFuse: RealTimeFuse |
| | 1208 | construct(obj, prop, delta, source, sense) |
| | 1209 | { |
| | 1210 | /* inherit the base constructor */ |
| | 1211 | inherited(obj, prop, delta); |
| | 1212 | |
| | 1213 | /* remember our sensory context */ |
| | 1214 | source_ = source; |
| | 1215 | sense_ = sense; |
| | 1216 | } |
| | 1217 | ; |
| | 1218 | |
| | 1219 | /* |
| | 1220 | * Real-time daemon. This is an event that occurs repeatedly at given |
| | 1221 | * real-time intervals. When a daemon is executed, it is scheduled |
| | 1222 | * again for execution after its real-time interval elapses again. The |
| | 1223 | * daemon's first execution will occur one interval from the time at |
| | 1224 | * which the daemon is created. |
| | 1225 | * |
| | 1226 | * If a daemon is executed late (because other, more pressing tasks had |
| | 1227 | * to be completed first, or because the user was busy editing a command |
| | 1228 | * line and the local platform doesn't support real-time command |
| | 1229 | * interruptions), the interval is applied to the time the daemon |
| | 1230 | * actually executed, not to the originally scheduled execution time. |
| | 1231 | * For example, if the daemon is scheduled to run once every minute, but |
| | 1232 | * can't run at all for five minutes because of command editing on a |
| | 1233 | * non-interrupting platform, once it actually does run, it won't run |
| | 1234 | * again for (at least) another minute after that. This means that the |
| | 1235 | * daemon will not run five times all at once when it's finally allowed |
| | 1236 | * to run - there's no making up for lost time. |
| | 1237 | */ |
| | 1238 | class RealTimeDaemon: RealTimeEvent |
| | 1239 | /* |
| | 1240 | * Creation. 'interval' is the number of milliseconds between |
| | 1241 | * invocations. |
| | 1242 | */ |
| | 1243 | construct(obj, prop, interval) |
| | 1244 | { |
| | 1245 | /* inherit the base constructor */ |
| | 1246 | inherited(obj, prop); |
| | 1247 | |
| | 1248 | /* remember my interval */ |
| | 1249 | interval_ = interval; |
| | 1250 | |
| | 1251 | /* |
| | 1252 | * figure my initial execution time - wait for one complete |
| | 1253 | * interval from the current time |
| | 1254 | */ |
| | 1255 | eventTime = realTimeManager.getElapsedTime() + interval; |
| | 1256 | } |
| | 1257 | |
| | 1258 | /* execute the daemon */ |
| | 1259 | executeEvent() |
| | 1260 | { |
| | 1261 | /* call my method */ |
| | 1262 | callMethod(); |
| | 1263 | |
| | 1264 | /* |
| | 1265 | * Reschedule for next time. To ensure that we keep to our |
| | 1266 | * long-term schedule, reschedule based on our original schedule |
| | 1267 | * time rather than the current clock time; that way, if there |
| | 1268 | * was a delay after our original scheduled time in firing us, |
| | 1269 | * we'll make up for it by shortening the interval until the |
| | 1270 | * next firing. If that would make us already schedulable, then |
| | 1271 | * our interval must be so short we can't keep up with it; in |
| | 1272 | * that case, add the interval to the current clock time. |
| | 1273 | */ |
| | 1274 | eventTime += interval_; |
| | 1275 | if (realTimeManager.getElapsedTime() < eventTime) |
| | 1276 | eventTime = realTimeManager.getElapsedTime() + interval_; |
| | 1277 | } |
| | 1278 | |
| | 1279 | /* my execution interval, in milliseconds */ |
| | 1280 | interval_ = 1 |
| | 1281 | ; |
| | 1282 | |
| | 1283 | /* |
| | 1284 | * Sensory-context-sensitive real-time daemon - this is a real-time |
| | 1285 | * daemon with an explicit sensory context. This is the daemon |
| | 1286 | * counterpart of RealTimeSenseFuse. |
| | 1287 | */ |
| | 1288 | class RealTimeSenseDaemon: RealTimeDaemon |
| | 1289 | construct(obj, prop, interval, source, sense) |
| | 1290 | { |
| | 1291 | /* inherit the base constructor */ |
| | 1292 | inherited(obj, prop, interval); |
| | 1293 | |
| | 1294 | /* remember our sensory context */ |
| | 1295 | source_ = source; |
| | 1296 | sense_ = sense; |
| | 1297 | } |
| | 1298 | ; |
| | 1299 | |