statecharts_core.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. // Exception
  2. function RuntimeException(msg) {
  3. this.msg = msg;
  4. }
  5. // InputException
  6. function InputException(msg) {
  7. RuntimeException.call(this, msg);
  8. }
  9. InputException.prototype = new RuntimeException();
  10. // AssociationException
  11. function AssociationException(msg) {
  12. RuntimeException.call(this, msg);
  13. }
  14. AssociationException.prototype = new RuntimeException();
  15. // AssociationReferenceException
  16. function AssociationReferenceException(msg) {
  17. RuntimeException.call(this, msg);
  18. }
  19. AssociationReferenceException.prototype = new RuntimeException();
  20. // ParameterException
  21. function ParameterException(msg) {
  22. RuntimeException.call(this, msg);
  23. }
  24. ParameterException.prototype = new RuntimeException();
  25. // InputException
  26. function InputException(msg) {
  27. RuntimeException.call(this, msg);
  28. }
  29. InputException.prototype = new RuntimeException();
  30. // EventQueueEntry
  31. function EventQueueEntry(event, time_offset) {
  32. this.event = event;
  33. this.time_offset = time_offset;
  34. }
  35. EventQueueEntry.prototype.decreaseTime = function(offset) {
  36. this.time_offset -= offset;
  37. };
  38. // EventQueue
  39. function EventQueue() {
  40. this.event_list = new Array();
  41. }
  42. EventQueue.prototype.add = function(event, time_offset) {
  43. var entry = new EventQueueEntry(event, time_offset);
  44. var insert_index = 0;
  45. var index = this.event_list.length - 1;
  46. while (index >= 0) {
  47. if (this.event_list[index].time_offset <= time_offset) {
  48. insert_index = index + 1;
  49. break;
  50. }
  51. index -= 1;
  52. }
  53. this.event_list.splice(insert_index, 0, entry);
  54. };
  55. EventQueue.prototype.decreaseTime = function(offset) {
  56. for (var event in this.event_list) {
  57. if (!this.event_list.hasOwnProperty(event)) continue;
  58. this.event_list[event].decreaseTime(offset);
  59. }
  60. };
  61. EventQueue.prototype.isEmpty = function() {
  62. return this.event_list.length === 0;
  63. };
  64. EventQueue.prototype.getEarliestTime = function() {
  65. if (this.isEmpty()) {
  66. return Infinity;
  67. } else {
  68. return this.event_list[0].time_offset;
  69. }
  70. };
  71. EventQueue.prototype.popDueEvents = function() {
  72. if (this.isEmpty() || this.event_list[0].time_offset > 0.0) {
  73. return new Array();
  74. }
  75. var index = 0;
  76. while (index < this.event_list.length &&
  77. this.event_list[index].time_offset <= 0.0)
  78. {
  79. index++;
  80. }
  81. return this.event_list.splice(0, index);
  82. };
  83. // Association
  84. function Association(to_class, min_card, max_card) {
  85. this.to_class = to_class;
  86. this.min_card = min_card;
  87. this.max_card = max_card;
  88. this.instances = new Object(); /* maps index (as string) to instance */
  89. this.size = 0;
  90. this.next_id = 0;
  91. }
  92. Association.prototype.allowedToAdd = function() {
  93. return (this.max_card === -1 || this.size < this.max_card);
  94. };
  95. Association.prototype.addInstance = function(instance) {
  96. if (this.allowedToAdd()) {
  97. var id = this.next_id++;
  98. this.instances[id] = instance;
  99. return id;
  100. } else {
  101. throw new AssociationException("Not allowed to add the instance to the association.");
  102. }
  103. };
  104. Association.prototype.getInstance = function(index) {
  105. var instance = this.instances[index];
  106. if (instance === undefined) {
  107. throw new AssociationException("Invalid index for fetching instance(s) from association.");
  108. }
  109. return instance;
  110. };
  111. // ObjectManagerBase
  112. function ObjectManagerBase(controller) {
  113. this.controller = controller;
  114. this.events = new EventQueue();
  115. this.instances = new Array();
  116. }
  117. ObjectManagerBase.prototype.addEvent = function(event, time_offset) {
  118. if (!time_offset) time_offset = 0.0;
  119. this.events.add(event, time_offset);
  120. };
  121. ObjectManagerBase.prototype.broadcast = function(new_event) {
  122. for (var i in this.instances) {
  123. if (!this.instances.hasOwnProperty(i)) continue;
  124. this.instances[i].addEvent(new_event);
  125. }
  126. };
  127. ObjectManagerBase.prototype.getWaitTime = function() {
  128. var smallest_time = this.events.getEarliestTime();
  129. for (var i in this.instances) {
  130. if (!this.instances.hasOwnProperty(i)) continue;
  131. smallest_time = Math.min(smallest_time, this.instances[i].getEarliestEventTime());
  132. }
  133. return smallest_time;
  134. };
  135. ObjectManagerBase.prototype.stepAll = function(delta) {
  136. this.step(delta);
  137. for (var i in this.instances) {
  138. if (!this.instances.hasOwnProperty(i)) continue;
  139. this.instances[i].step(delta);
  140. }
  141. };
  142. ObjectManagerBase.prototype.step = function(delta) {
  143. this.events.decreaseTime(delta);
  144. var due = this.events.popDueEvents();
  145. for (var e in due) {
  146. this.handleEvent(due[e].event);
  147. }
  148. };
  149. ObjectManagerBase.prototype.start = function() {
  150. for (var i in this.instances) {
  151. if (!this.instances.hasOwnProperty(i)) continue;
  152. this.instances[i].start();
  153. }
  154. };
  155. ObjectManagerBase.prototype.handleEvent = function(e) {
  156. if (e.name === "narrow_cast") {
  157. this.handleNarrowCastEvent(e.parameters);
  158. } else if (e.name === "broad_cast") {
  159. this.handleBroadcastEvent(e.parameters);
  160. } else if (e.name === "create_instance") {
  161. this.handleCreateEvent(e.parameters);
  162. } else if (e.name === "associate_instance") {
  163. this.handleAssociateEvent(e.parameters);
  164. } else if (e.name === "start_instance") {
  165. this.handleStartInstanceEvent(e.parameters);
  166. } else if (e.name === "delete_instance") {
  167. this.handleDeleteInstanceEvent(e.parameters);
  168. }
  169. };
  170. ObjectManagerBase.prototype.processAssociationReference = function(input_string) {
  171. //if (input_string === "") {
  172. //throw new AssociationReferenceException("Empty association reference.");
  173. //}
  174. var regex = /^([a-zA-Z_]\w*)(?:\[(\d+)\])?$/;
  175. var path_string = input_string.split('/');
  176. var result = new Array();
  177. if (input_string !== "") {
  178. for (var p in path_string) {
  179. if (!path_string.hasOwnProperty(p)) continue;
  180. var m = regex.exec(path_string[p]);
  181. if (m) {
  182. var name = m[1];
  183. var index = m[2];
  184. if (!index) {
  185. index = -1;
  186. }
  187. result.push({name:name,index:index});
  188. } else {
  189. throw new AssociationReferenceException("Invalid entry in association reference.");
  190. }
  191. }
  192. }
  193. return result;
  194. };
  195. ObjectManagerBase.prototype.handleStartInstanceEvent = function(parameters) {
  196. if (parameters.length !== 2) {
  197. throw new ParameterException("The start instance event needs 2 parameters.");
  198. }
  199. var source = parameters[0];
  200. var traversal_list = this.processAssociationReference(parameters[1]);
  201. var instances = this.getInstances(source, traversal_list);
  202. for (var i in instances) {
  203. if (!instances.hasOwnProperty(i)) continue;
  204. instances[i].instance.start();
  205. }
  206. };
  207. ObjectManagerBase.prototype.handleDeleteInstanceEvent = function(parameters) {
  208. if (parameters.length !== 2) {
  209. throw new ParameterException("The delete instance event needs 2 parameters.");
  210. }
  211. var source = parameters[0];
  212. var traversal_list = this.processAssociationReference(parameters[1]);
  213. var instances = this.getInstances(source, traversal_list);
  214. for (var i in instances) {
  215. if (!instances.hasOwnProperty(i)) continue;
  216. instances[i].instance.stop();
  217. if (instances[i].instance.destructor)
  218. instances[i].instance.destructor();
  219. // delete association from source instance
  220. var association_to_remove = instances[i].ref.associations[instances[i].assoc_name];
  221. if (instances[i].assoc_index === -1) {
  222. /*for (var x in association_to_remove.instances) {
  223. if (!association_to_remove.instances.hasOwnProperty(x)) continue;
  224. association_to_remove.instances = new Object();
  225. //association_to_remove.instances[x] = null;
  226. }*/
  227. // empty instances object
  228. association_to_remove.instances = new Object();
  229. //association_to_remove.instances = new Array();
  230. } else {
  231. //association_to_remove.instances[instances[i].assoc_index] = null;
  232. // remove property from instances object
  233. delete association_to_remove.instances[instances[i].assoc_index];
  234. }
  235. // also remove instance from OM's list of instances
  236. index = this.instances.indexOf(instances[i].instance);
  237. this.instances.splice(index,1);
  238. source.addEvent(new Event("instance_deleted", undefined, [parameters[1]]));
  239. }
  240. };
  241. ObjectManagerBase.prototype.handleBroadcastEvent = function(parameters) {
  242. if (parameters.length !== 1) {
  243. throw new ParameterException("The broadcast event needs 1 parameter.");
  244. }
  245. this.broadcast(parameters[0]);
  246. };
  247. ObjectManagerBase.prototype.handleCreateEvent = function(parameters) {
  248. if (parameters.length < 2) {
  249. throw new ParameterException("The create event needs at least 2 parameters.");
  250. }
  251. var source = parameters[0];
  252. var association_name = parameters[1];
  253. var association = source.associations[association_name];
  254. if (!association) {
  255. throw new ParameterException("No such association: " + association_name);
  256. }
  257. if (association.allowedToAdd()) {
  258. // allow subclasses to be instantiated
  259. if (parameters.length === 2) {
  260. var class_name = association.to_class;
  261. var creation_parameters = [];
  262. } else /* 3 or more parameters*/ {
  263. // 3rd parameter is class name
  264. var class_name = parameters[2];
  265. // parameters after 3rd parameter are creation parameters
  266. var creation_parameters = parameters.slice(3);
  267. }
  268. var new_instance = this.createInstance(class_name, creation_parameters);
  269. if (new_instance === undefined) {
  270. throw new ParameterException("Creating instance: no such class: " + class_name);
  271. }
  272. var index = association.addInstance(new_instance);
  273. // add parent association to created instance
  274. // if a parent association is defined in the class diagram
  275. var parent_association = new_instance.associations["parent"];
  276. if (parent_association !== undefined) {
  277. parent_association.addInstance(source);
  278. }
  279. // TODO: maybe change order of Event constructor parameters such that we don't have to
  280. // explicitly set the port to 'undefined'?
  281. source.addEvent(new Event("instance_created", undefined, [association_name+"["+index+"]"]));
  282. } else {
  283. source.addEvent(new Event("instance_creation_error", undefined, [association_name]));
  284. }
  285. };
  286. ObjectManagerBase.prototype.handleAssociateEvent = function(parameters) {
  287. if (parameters.length !== 3) {
  288. throw new ParameterException("The associate_instance event needs 3 parameters.");
  289. }
  290. var source = parameters[0];
  291. var source_list = parameters[1];
  292. var traversal_list = this.processAssociationReference(source_list);
  293. var to_copy_list = this.getInstances(source, traversal_list);
  294. if (to_copy_list.length !== 1) {
  295. throw new AssociationReferenceException("Invalid source association reference.");
  296. }
  297. var wrapped_to_copy_instance = to_copy_list[0].instance;
  298. var dest_list = this.processAssociationReference(parameters[2]);
  299. if (dest_list.length === 0) {
  300. throw new AssociationReferenceException("Invalid destination association reference.");
  301. }
  302. var last = dest_list.pop();
  303. if (last.index !== -1) {
  304. throw new AssociationReferenceException("Last association name in association reference could not be accompanied by an index.");
  305. }
  306. var instances = this.getInstances(source, dest_list);
  307. for (var i in instances) {
  308. if (!instances.hasOwnProperty(i)) continue;
  309. instances[i].instance.associations[last.name].addInstance(wrapped_to_copy_instance);
  310. }
  311. };
  312. ObjectManagerBase.prototype.handleNarrowCastEvent = function(parameters) {
  313. if (parameters.length !== 3) {
  314. throw new ParameterException("The narrow_cast event needs 3 parameters.");
  315. }
  316. var source = parameters[0];
  317. var traversal_list = this.processAssociationReference(parameters[1]);
  318. var cast_event = parameters[2];
  319. var instances = this.getInstances(source, traversal_list);
  320. for (var i in instances) {
  321. if (!instances.hasOwnProperty(i)) continue;
  322. instances[i].instance.addEvent(cast_event);
  323. }
  324. };
  325. ObjectManagerBase.prototype.getInstances = function(source, traversal_list) {
  326. var currents = [{
  327. instance : source,
  328. ref : null,
  329. assoc_name : null,
  330. assoc_index : null
  331. }];
  332. for (var t in traversal_list) {
  333. if (!traversal_list.hasOwnProperty(t)) continue;
  334. var name = traversal_list[t].name;
  335. var index = traversal_list[t].index;
  336. nexts = new Array();
  337. for (var c in currents) {
  338. if (!currents.hasOwnProperty(c)) continue;
  339. var association = currents[c].instance.associations[name];
  340. if (index >= 0) {
  341. nexts.push({
  342. instance : association.getInstance(index),
  343. ref : currents[c].instance,
  344. assoc_name : name,
  345. assoc_index : index
  346. });
  347. } else if (index === -1) {
  348. for (var i in association.instances) {
  349. if (!association.instances.hasOwnProperty(i)) continue;
  350. nexts.push({
  351. instance: association.instances[i],
  352. ref: currents[c].instance,
  353. assoc_name : name,
  354. assoc_index : index
  355. });
  356. }
  357. //nexts = nexts.concat(association.instances);
  358. } else {
  359. throw new AssociationReferenceException("Incorrect index in association reference.");
  360. }
  361. }
  362. currents = nexts;
  363. }
  364. return currents;
  365. };
  366. ObjectManagerBase.prototype.instantiate = function(to_class, construct_params) {
  367. // pure virtual
  368. };
  369. ObjectManagerBase.prototype.createInstance = function(to_class, construct_params) {
  370. var instance = this.instantiate(to_class, construct_params);
  371. this.instances.push(instance);
  372. return instance;
  373. };
  374. // Event
  375. function Event(name, port, parameters) {
  376. this.name = name;
  377. this.port = port;
  378. this.parameters = parameters;
  379. }
  380. // ControllerBase
  381. function ControllerBase(object_manager, keep_running, finished_callback) {
  382. this.object_manager = object_manager;
  383. this.keep_running = keep_running;
  384. this.finished_callback = finished_callback;
  385. this.input_ports = new Object(); /* maps port name to pair of (private name, instance) */
  386. this.private_port_counter = 0;
  387. this.input_queue = new EventQueue();
  388. this.output_ports = new Array();
  389. this.output_listeners = new Array();
  390. }
  391. ControllerBase.prototype.addInputPort = function(virtual_name, instance) {
  392. if (instance === undefined) {
  393. var port_name = virtual_name; // "public" port
  394. } else {
  395. var port_name = "private_" + (this.private_port_counter++) + /*"_" + instance.class_name +*/ "_" + virtual_name;
  396. }
  397. this.input_ports[port_name] = {
  398. virtual_name: virtual_name,
  399. instance: instance
  400. };
  401. return port_name;
  402. };
  403. ControllerBase.prototype.removeInputPort = function(name) {
  404. delete this.input_ports[name];
  405. };
  406. ControllerBase.prototype.addOutputPort = function(port_name) {
  407. this.output_ports.push(port_name);
  408. };
  409. ControllerBase.prototype.broadcast = function(new_event) {
  410. this.object_manager.broadcast(new_event);
  411. };
  412. ControllerBase.prototype.start = function() {
  413. this.object_manager.start();
  414. };
  415. ControllerBase.prototype.stop = function() {
  416. };
  417. ControllerBase.prototype.addInput = function(input_event, time_offset) {
  418. if (input_event.name === "") {
  419. throw new InputException("Input event can't have an empty name.");
  420. }
  421. var input_port = this.input_ports[input_event.port];
  422. if (input_port === undefined) {
  423. throw new InputException("Input port mismatch.");
  424. }
  425. this.input_queue.add(input_event, time_offset);
  426. };
  427. ControllerBase.prototype.outputEvent = function(event) {
  428. for (var l in this.output_listeners) {
  429. if (!this.output_listeners.hasOwnProperty(l)) continue;
  430. this.output_listeners[l].add(event);
  431. }
  432. };
  433. ControllerBase.prototype.addOutputListener = function(ports) {
  434. var listener = new OutputListener(ports);
  435. this.output_listeners.push(listener);
  436. return listener;
  437. };
  438. ControllerBase.prototype.addMyOwnOutputListener = function(listener) {
  439. this.output_listeners.push(listener);
  440. };
  441. ControllerBase.prototype.addEventList = function(event_list) {
  442. for (var e in event_list) {
  443. if (!event_list.hasOwnProperty(e)) continue;
  444. var entry = event_list[e];
  445. this.addInput(entry.event, entry.time_offset);
  446. }
  447. };
  448. // GameLoopControllerBase
  449. function GameLoopControllerBase(object_manager, keep_running, finished_callback) {
  450. ControllerBase.call(this, object_manager, keep_running, finished_callback);
  451. }
  452. GameLoopControllerBase.prototype = new ControllerBase();
  453. GameLoopControllerBase.prototype.update = function(delta) {
  454. this.input_queue.decreaseTime(delta);
  455. var due = this.input_queue.popDueEvents();
  456. for (var e in due) {
  457. if (!due.hasOwnProperty(e)) continue;
  458. this.broadcast(due[e].event);
  459. }
  460. this.object_manager.stepAll(delta);
  461. };
  462. function TimeoutId(id, delay) {
  463. this.id = id;
  464. this.delay = delay;
  465. }
  466. // JsEventLoopControllerBase
  467. function JsEventLoopControllerBase(object_manager, keep_running, finished_callback) {
  468. ControllerBase.call(this, object_manager, keep_running, finished_callback);
  469. this.running = false;
  470. this.next_timeout = null;
  471. this.last_simulation_time = null;
  472. }
  473. JsEventLoopControllerBase.prototype = new ControllerBase();
  474. JsEventLoopControllerBase.prototype.handleInput = function(delta) {
  475. this.input_queue.decreaseTime(delta);
  476. var due = this.input_queue.popDueEvents();
  477. for (var e in due) {
  478. if (!due.hasOwnProperty(e)) continue;
  479. var input_port = this.input_ports[due[e].event.port];
  480. // rename input port
  481. due[e].event.port = input_port.virtual_name;
  482. var target_instance = input_port.instance;
  483. if (target_instance === undefined) {
  484. this.broadcast(due[e].event);
  485. } else {
  486. target_instance.addEvent(due[e].event);
  487. }
  488. }
  489. };
  490. JsEventLoopControllerBase.prototype.addInput = function(input_event, time_offset) {
  491. if (this.last_simulation_time && this.next_timeout) {
  492. var waited = (new Date).getTime() - this.last_simulation_time;
  493. var remaining = this.next_timeout.delay - waited;
  494. } else {
  495. var waited = 0.0;
  496. var remaining = 0.0;
  497. }
  498. var interleave = time_offset < remaining;
  499. if (this.next_timeout) {
  500. var additional_offset = waited;
  501. } else {
  502. var additional_offset = 0.0;
  503. }
  504. ControllerBase.prototype.addInput.call(this, input_event, time_offset + additional_offset);
  505. if (this.running && (interleave || !this.next_timeout)) {
  506. this.run(); // adjust timeout
  507. }
  508. };
  509. JsEventLoopControllerBase.prototype.start = function() {
  510. ControllerBase.prototype.start.call(this);
  511. this.running = true;
  512. this.run();
  513. };
  514. JsEventLoopControllerBase.prototype.stop = function() {
  515. this.run(); // update timeouts
  516. if (this.next_timeout) {
  517. window.clearTimeout(this.next_timeout.id);
  518. }
  519. this.running = false;
  520. ControllerBase.prototype.stop.call(this);
  521. };
  522. JsEventLoopControllerBase.prototype.getWaitTime = function() {
  523. var wait_time = Math.min(this.object_manager.getWaitTime(), this.input_queue.getEarliestTime());
  524. return wait_time;
  525. };
  526. JsEventLoopControllerBase.prototype.run = function() {
  527. // clear previous timeout
  528. if (this.next_timeout) {
  529. window.clearTimeout(this.next_timeout.id);
  530. this.next_timeout = null;
  531. }
  532. // calculate last time since simulation
  533. if (this.last_simulation_time) {
  534. var simulation_duration = (new Date).getTime() - this.last_simulation_time;
  535. } else {
  536. var simulation_duration = 0.0;
  537. }
  538. // simulate
  539. this.handleInput(simulation_duration);
  540. this.object_manager.stepAll(simulation_duration);
  541. // keep time
  542. this.last_simulation_time = (new Date).getTime();
  543. // set next timeout
  544. var wait_time = this.getWaitTime();
  545. if (wait_time !== Infinity) {
  546. var actual_wait_time = wait_time - ((new Date).getTime() - this.last_simulation_time);
  547. if (actual_wait_time < 0.0)
  548. actual_wait_time = 0.0;
  549. // wait actual_wait_time
  550. //console.log("waiting " + actual_wait_time + " ms");
  551. this.next_timeout = new TimeoutId(window.setTimeout(this.run.bind(this), actual_wait_time), actual_wait_time);
  552. } else {
  553. // wait forever
  554. //console.log("waiting forever");
  555. this.last_simulation_time = null;
  556. if (this.finished_callback) {
  557. this.finished_callback();
  558. }
  559. }
  560. };
  561. // OutputListener
  562. function OutputListener(port_names) {
  563. this.port_names = port_names;
  564. this.queue = new Array(); // TODO: optimize!
  565. }
  566. OutputListener.prototype.add = function(event) {
  567. if (this.port_names.length === 0
  568. || this.port_names.indexOf(event.port) !== -1)
  569. {
  570. this.queue.push(event);
  571. }
  572. };
  573. OutputListener.prototype.fetch = function(timeout) {
  574. return this.queue.shift();
  575. };
  576. // RuntimeClassBase
  577. function RuntimeClassBase() {
  578. this.active = false;
  579. this.state_changed = false;
  580. this.events = new EventQueue();
  581. this.timers = null;
  582. }
  583. RuntimeClassBase.prototype.addEvent = function(event, time_offset) {
  584. if (!time_offset) time_offset = 0.0;
  585. this.events.add(event, time_offset);
  586. };
  587. RuntimeClassBase.prototype.getEarliestEventTime = function() {
  588. if (this.timers) {
  589. var minimum = Infinity;
  590. for (var t in this.timers) {
  591. if (!this.timers.hasOwnProperty(t)) continue;
  592. minimum = Math.min(minimum, this.timers[t]);
  593. }
  594. return Math.min(this.events.getEarliestTime(), minimum);
  595. }
  596. return this.events.getEarliestTime();
  597. };
  598. RuntimeClassBase.prototype.step = function(delta) {
  599. if (!this.active) {
  600. return;
  601. }
  602. this.events.decreaseTime(delta);
  603. if (this.timers) {
  604. var next_timers = new Object();
  605. for (var t in this.timers) {
  606. if (!this.timers.hasOwnProperty(t)) continue;
  607. var time_left = this.timers[t] - delta;
  608. if (time_left <= 0.0) {
  609. this.addEvent(new Event("_" + t + "after"), time_left);
  610. } else {
  611. next_timers[t] = time_left;
  612. }
  613. }
  614. this.timers = next_timers;
  615. }
  616. this.microstep();
  617. while (this.state_changed) {
  618. this.microstep();
  619. }
  620. };
  621. RuntimeClassBase.prototype.microstep = function() {
  622. var due = this.events.popDueEvents();
  623. if (due.length === 0) {
  624. this.transition();
  625. } else {
  626. for (var e in due) {
  627. if (!due.hasOwnProperty(e)) continue;
  628. this.transition(due[e].event);
  629. }
  630. }
  631. };
  632. RuntimeClassBase.prototype.transition = function(event) {
  633. // pure virtual
  634. };
  635. RuntimeClassBase.prototype.start = function() {
  636. this.active = true;
  637. };
  638. RuntimeClassBase.prototype.stop = function() {
  639. this.active = false;
  640. };