statecharts_core.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  1. __next_objid=1;
  2. function objectId(obj) {
  3. if (obj==null) return null;
  4. if (obj.__obj_id==null) obj.__obj_id=__next_objid++;
  5. return obj.__obj_id;
  6. }
  7. simulated_time = 0.0
  8. function get_simulated_time() {
  9. return simulated_time
  10. }
  11. start_time = undefined
  12. function time() {
  13. if (start_time == undefined) {
  14. start_time = (new Date).getTime() / 1000
  15. }
  16. return (new Date).getTime() / 1000 - start_time;
  17. }
  18. // Exception
  19. function RuntimeException(msg) {
  20. this.msg = msg;
  21. }
  22. // InputException
  23. function InputException(msg) {
  24. RuntimeException.call(this, msg);
  25. }
  26. InputException.prototype = new RuntimeException();
  27. // AssociationException
  28. function AssociationException(msg) {
  29. RuntimeException.call(this, msg);
  30. }
  31. AssociationException.prototype = new RuntimeException();
  32. // AssociationReferenceException
  33. function AssociationReferenceException(msg) {
  34. RuntimeException.call(this, msg);
  35. }
  36. AssociationReferenceException.prototype = new RuntimeException();
  37. // ParameterException
  38. function ParameterException(msg) {
  39. RuntimeException.call(this, msg);
  40. }
  41. ParameterException.prototype = new RuntimeException();
  42. // InputException
  43. function InputException(msg) {
  44. RuntimeException.call(this, msg);
  45. }
  46. InputException.prototype = new RuntimeException();
  47. // EventQueueEntry
  48. function EventQueueEntry(scheduled_time, the_event) {
  49. this.scheduled_time = scheduled_time;
  50. this.the_event = the_event;
  51. }
  52. // EventQueue
  53. function EventQueue() {
  54. this.event_list = new Array();
  55. this.sort_function = function(a, b) {
  56. return a.scheduled_time - b.scheduled_time
  57. }
  58. }
  59. EventQueue.prototype.isEmpty = function() {
  60. return this.event_list.length == 0;
  61. };
  62. EventQueue.prototype.getEarliestTime = function() {
  63. if (this.isEmpty()) {
  64. return Infinity;
  65. } else {
  66. return this.event_list[0].scheduled_time;
  67. }
  68. };
  69. EventQueue.prototype.add = function(the_event) {
  70. this.event_list.push(the_event)
  71. this.event_list.sort(this.sort_function)
  72. return objectId(the_event)
  73. };
  74. EventQueue.prototype.remove = function(event_id) {
  75. this.event_list = this.event_list.filter(function(el) {objectId(el) != event_id}).sort()
  76. }
  77. EventQueue.prototype.pop = function() {
  78. return this.event_list.shift().the_event
  79. };
  80. // Association
  81. function Association(to_class, min_card, max_card) {
  82. this.to_class = to_class;
  83. this.min_card = min_card;
  84. this.max_card = max_card;
  85. this.instances = new Object(); /* maps index (as string) to instance */
  86. this.size = 0;
  87. this.next_id = 0;
  88. }
  89. Association.prototype.allowedToAdd = function() {
  90. return (this.max_card === -1 || this.size < this.max_card);
  91. };
  92. Association.prototype.allowedToRemove = function() {
  93. return (this.min_card === -1 || this.size > this.min_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.removeInstance = function(instance) {
  105. if (this.allowedToRemove()) {
  106. delete this.instances[this.instances_to_ids[instance]]
  107. } else {
  108. throw new AssociationException("Not allowed to remove the instance to the association.");
  109. }
  110. };
  111. Association.prototype.getInstance = function(index) {
  112. var instance = this.instances[index];
  113. if (instance === undefined) {
  114. throw new AssociationException("Invalid index for fetching instance(s) from association.");
  115. }
  116. return instance;
  117. };
  118. // ObjectManagerBase
  119. function ObjectManagerBase(controller) {
  120. this.controller = controller;
  121. this.events = new EventQueue();
  122. this.instances = new Array();
  123. }
  124. ObjectManagerBase.prototype.addEvent = function(the_event, time_offset) {
  125. if (time_offset === undefined) time_offset = 0.0;
  126. this.events.add(new EventQueueEntry(simulated_time + time_offset, the_event));
  127. };
  128. ObjectManagerBase.prototype.broadcast = function(new_event, time_offset) {
  129. if (time_offset === undefined) time_offset = 0.0;
  130. for (var i in this.instances) {
  131. if (!this.instances.hasOwnProperty(i)) continue;
  132. this.instances[i].addEvent(new_event, time_offset);
  133. }
  134. };
  135. ObjectManagerBase.prototype.getEarliestEventTime = function() {
  136. var earliest_time = this.events.getEarliestTime();
  137. for (var i in this.instances) {
  138. if (!this.instances.hasOwnProperty(i)) continue;
  139. earliest_time = Math.min(earliest_time, this.instances[i].earliest_event_time);
  140. }
  141. return earliest_time;
  142. };
  143. ObjectManagerBase.prototype.stepAll = function() {
  144. this.step();
  145. for (var i in this.instances) {
  146. if (!this.instances.hasOwnProperty(i)) continue;
  147. var instance = this.instances[i];
  148. if (instance.active && instance.earliest_event_time <= simulated_time) {
  149. instance.step();
  150. }
  151. }
  152. };
  153. ObjectManagerBase.prototype.step = function() {
  154. while (this.events.getEarliestTime() <= time()) {
  155. this.handleEvent(this.events.pop());
  156. }
  157. };
  158. ObjectManagerBase.prototype.start = function() {
  159. for (var i in this.instances) {
  160. if (!this.instances.hasOwnProperty(i)) continue;
  161. this.instances[i].start();
  162. }
  163. };
  164. ObjectManagerBase.prototype.handleEvent = function(e) {
  165. if (e.name === "narrow_cast") {
  166. this.handleNarrowCastEvent(e.parameters);
  167. } else if (e.name === "broad_cast") {
  168. this.handleBroadcastEvent(e.parameters);
  169. } else if (e.name === "create_instance") {
  170. this.handleCreateEvent(e.parameters);
  171. } else if (e.name === "associate_instance") {
  172. this.handleAssociateEvent(e.parameters);
  173. } else if (e.name === "start_instance") {
  174. this.handleStartInstanceEvent(e.parameters);
  175. } else if (e.name === "delete_instance") {
  176. this.handleDeleteInstanceEvent(e.parameters);
  177. }
  178. };
  179. ObjectManagerBase.prototype.processAssociationReference = function(input_string) {
  180. if (input_string.length == 0) {
  181. throw new AssociationReferenceException("Empty association reference.");
  182. }
  183. var regex = /^([a-zA-Z_]\w*)(?:\[(\d+)\])?$/;
  184. var path_string = input_string.split('/');
  185. var result = new Array();
  186. if (input_string !== "") {
  187. for (var p in path_string) {
  188. if (!path_string.hasOwnProperty(p)) continue;
  189. var m = regex.exec(path_string[p]);
  190. if (m) {
  191. var name = m[1];
  192. var index = m[2];
  193. if (!index) {
  194. index = -1;
  195. }
  196. result.push({name:name,index:index});
  197. } else {
  198. throw new AssociationReferenceException("Invalid entry in association reference.");
  199. }
  200. }
  201. }
  202. return result;
  203. };
  204. ObjectManagerBase.prototype.handleStartInstanceEvent = function(parameters) {
  205. if (parameters.length !== 2) {
  206. throw new ParameterException("The start instance event needs 2 parameters.");
  207. }
  208. var source = parameters[0];
  209. var traversal_list = this.processAssociationReference(parameters[1]);
  210. var instances = this.getInstances(source, traversal_list);
  211. for (var i in instances) {
  212. if (!instances.hasOwnProperty(i)) continue;
  213. instances[i].instance.start();
  214. }
  215. };
  216. ObjectManagerBase.prototype.handleDeleteInstanceEvent = function(parameters) {
  217. if (parameters.length !== 2) {
  218. throw new ParameterException("The delete instance event needs 2 parameters.");
  219. }
  220. var source = parameters[0];
  221. var traversal_list = this.processAssociationReference(parameters[1]);
  222. var instances = this.getInstances(source, traversal_list);
  223. for (var i in instances) {
  224. if (!instances.hasOwnProperty(i)) continue;
  225. instances[i].instance.stop();
  226. instances[i].instance.user_defined_destructor();
  227. // delete association from source instance
  228. var association_to_remove = instances[i].ref.associations[instances[i].assoc_name];
  229. if (instances[i].assoc_index === -1) {
  230. /*for (var x in association_to_remove.instances) {
  231. if (!association_to_remove.instances.hasOwnProperty(x)) continue;
  232. association_to_remove.instances = new Object();
  233. //association_to_remove.instances[x] = null;
  234. }*/
  235. // empty instances object
  236. association_to_remove.instances = new Object();
  237. //association_to_remove.instances = new Array();
  238. } else {
  239. //association_to_remove.instances[instances[i].assoc_index] = null;
  240. // remove property from instances object
  241. delete association_to_remove.instances[instances[i].assoc_index];
  242. }
  243. // also remove instance from OM's list of instances
  244. index = this.instances.indexOf(instances[i].instance);
  245. this.instances.splice(index,1);
  246. }
  247. source.addEvent(new Event("instance_deleted", undefined, [parameters[1]]));
  248. };
  249. ObjectManagerBase.prototype.handleBroadcastEvent = function(parameters) {
  250. if (parameters.length !== 1) {
  251. throw new ParameterException("The broadcast event needs 1 parameter.");
  252. }
  253. this.broadcast(parameters[0]);
  254. };
  255. ObjectManagerBase.prototype.handleCreateEvent = function(parameters) {
  256. if (parameters.length < 2) {
  257. throw new ParameterException("The create event needs at least 2 parameters.");
  258. }
  259. var source = parameters[0];
  260. var association_name = parameters[1];
  261. var association = source.associations[association_name];
  262. if (!association) {
  263. throw new ParameterException("No such association: " + association_name);
  264. }
  265. if (association.allowedToAdd()) {
  266. // allow subclasses to be instantiated
  267. if (parameters.length === 2) {
  268. var class_name = association.to_class;
  269. var creation_parameters = [];
  270. } else /* 3 or more parameters*/ {
  271. // 3rd parameter is class name
  272. var class_name = parameters[2];
  273. // parameters after 3rd parameter are creation parameters
  274. var creation_parameters = parameters.slice(3);
  275. }
  276. var new_instance = this.createInstance(class_name, creation_parameters);
  277. if (new_instance === undefined) {
  278. throw new ParameterException("Creating instance: no such class: " + class_name);
  279. }
  280. var index = association.addInstance(new_instance);
  281. // add parent association to created instance
  282. // if a parent association is defined in the class diagram
  283. var parent_association = new_instance.associations["parent"];
  284. if (parent_association !== undefined) {
  285. parent_association.addInstance(source);
  286. }
  287. // TODO: maybe change order of Event constructor parameters such that we don't have to
  288. // explicitly set the port to 'undefined'?
  289. source.addEvent(new Event("instance_created", undefined, [association_name+"["+index+"]"]));
  290. } else {
  291. source.addEvent(new Event("instance_creation_error", undefined, [association_name]));
  292. }
  293. };
  294. ObjectManagerBase.prototype.handleAssociateEvent = function(parameters) {
  295. if (parameters.length !== 3) {
  296. throw new ParameterException("The associate_instance event needs 3 parameters.");
  297. }
  298. var source = parameters[0];
  299. var source_list = parameters[1];
  300. var traversal_list = this.processAssociationReference(source_list);
  301. var to_copy_list = this.getInstances(source, traversal_list);
  302. if (to_copy_list.length !== 1) {
  303. throw new AssociationReferenceException("Invalid source association reference.");
  304. }
  305. var wrapped_to_copy_instance = to_copy_list[0].instance;
  306. var dest_list = this.processAssociationReference(parameters[2]);
  307. if (dest_list.length === 0) {
  308. throw new AssociationReferenceException("Invalid destination association reference.");
  309. }
  310. var last = dest_list.pop();
  311. if (last.index !== -1) {
  312. throw new AssociationReferenceException("Last association name in association reference could not be accompanied by an index.");
  313. }
  314. var instances = this.getInstances(source, dest_list);
  315. for (var i in instances) {
  316. if (!instances.hasOwnProperty(i)) continue;
  317. instances[i].instance.associations[last.name].addInstance(wrapped_to_copy_instance);
  318. }
  319. };
  320. ObjectManagerBase.prototype.handleNarrowCastEvent = function(parameters) {
  321. if (parameters.length !== 3) {
  322. throw new ParameterException("The narrow_cast event needs 3 parameters.");
  323. }
  324. var source = parameters[0];
  325. var traversal_list = this.processAssociationReference(parameters[1]);
  326. var cast_event = parameters[2];
  327. var instances = this.getInstances(source, traversal_list);
  328. for (var i in instances) {
  329. if (!instances.hasOwnProperty(i)) continue;
  330. instances[i].instance.addEvent(cast_event);
  331. }
  332. };
  333. ObjectManagerBase.prototype.getInstances = function(source, traversal_list) {
  334. var currents = [{
  335. instance : source,
  336. ref : null,
  337. assoc_name : null,
  338. assoc_index : null
  339. }];
  340. for (var t in traversal_list) {
  341. if (!traversal_list.hasOwnProperty(t)) continue;
  342. var name = traversal_list[t].name;
  343. var index = traversal_list[t].index;
  344. nexts = new Array();
  345. for (var c in currents) {
  346. if (!currents.hasOwnProperty(c)) continue;
  347. var association = currents[c].instance.associations[name];
  348. if (index >= 0) {
  349. nexts.push({
  350. instance : association.getInstance(index),
  351. ref : currents[c].instance,
  352. assoc_name : name,
  353. assoc_index : index
  354. });
  355. } else if (index === -1) {
  356. for (var i in association.instances) {
  357. if (!association.instances.hasOwnProperty(i)) continue;
  358. nexts.push({
  359. instance: association.instances[i],
  360. ref: currents[c].instance,
  361. assoc_name : name,
  362. assoc_index : index
  363. });
  364. }
  365. //nexts = nexts.concat(association.instances);
  366. } else {
  367. throw new AssociationReferenceException("Incorrect index in association reference.");
  368. }
  369. }
  370. currents = nexts;
  371. }
  372. return currents;
  373. };
  374. ObjectManagerBase.prototype.instantiate = function(to_class, construct_params) {
  375. // pure virtual
  376. };
  377. ObjectManagerBase.prototype.createInstance = function(to_class, construct_params) {
  378. var instance = this.instantiate(to_class, construct_params);
  379. this.instances.push(instance);
  380. return instance;
  381. };
  382. // Event
  383. function Event(name, port, parameters) {
  384. this.name = name;
  385. this.port = port;
  386. this.parameters = parameters;
  387. }
  388. // OutputListener
  389. function OutputListener(port_names) {
  390. this.port_names = port_names;
  391. this.queue = new Array(); // TODO: optimize!
  392. }
  393. OutputListener.prototype.add = function(the_event) {
  394. if (this.port_names.length == 0
  395. || this.port_names.indexOf(the_event.port) != -1)
  396. {
  397. this.queue.push(the_event);
  398. }
  399. };
  400. OutputListener.prototype.fetch = function(timeout) {
  401. if (this.queue.length > 0) {
  402. return this.queue.shift();
  403. }
  404. };
  405. // ControllerBase
  406. function ControllerBase(object_manager) {
  407. this.object_manager = object_manager;
  408. this.private_port_counter = 0;
  409. // keep track of input ports
  410. this.input_ports = new Object();
  411. this.input_queue = new EventQueue();
  412. // keep track of output ports
  413. this.output_ports = new Array();
  414. this.output_listeners = new Array();
  415. }
  416. ControllerBase.prototype.addInputPort = function(virtual_name, instance) {
  417. if (instance === undefined) {
  418. var port_name = virtual_name; // "global" port
  419. } else {
  420. var port_name = "private_" + (this.private_port_counter++) + "_" + virtual_name;
  421. }
  422. this.input_ports[port_name] = {
  423. virtual_name: virtual_name,
  424. instance: instance
  425. };
  426. return port_name;
  427. };
  428. ControllerBase.prototype.addOutputPort = function(port_name) {
  429. this.output_ports.push(port_name);
  430. };
  431. ControllerBase.prototype.broadcast = function(new_event, time_offset) {
  432. if (time_offset === undefined) time_offset = 0.0
  433. this.object_manager.broadcast(new_event, time_offset);
  434. };
  435. ControllerBase.prototype.start = function() {
  436. this.object_manager.start();
  437. };
  438. ControllerBase.prototype.stop = function() {
  439. };
  440. ControllerBase.prototype.addInput = function(input_event_list, time_offset) {
  441. if (time_offset === undefined) time_offset = 0.0
  442. if (!(input_event_list instanceof Array)) {
  443. input_event_list = [input_event_list];
  444. }
  445. for (var e in input_event_list) {
  446. if (!input_event_list.hasOwnProperty(e)) continue;
  447. if (input_event_list[e].name === "") {
  448. throw new InputException("Input event can't have an empty name.");
  449. }
  450. var input_port = this.input_ports[input_event_list[e].port];
  451. if (input_port === undefined) {
  452. throw new InputException("Input port mismatch, no such port: " + input_event_list[e].port + ".");
  453. }
  454. this.input_queue.add(new EventQueueEntry(time() + time_offset, input_event_list[e]));
  455. }
  456. };
  457. ControllerBase.prototype.getEarliestEventTime = function() {
  458. return Math.min(this.object_manager.getEarliestEventTime(), this.input_queue.getEarliestTime());
  459. };
  460. ControllerBase.prototype.handleInput = function() {
  461. var event_time = this.input_queue.getEarliestTime();
  462. while (event_time <= simulated_time) {
  463. var e = this.input_queue.pop();
  464. input_port = this.input_ports[e.port];
  465. e.port = input_port.virtual_name
  466. var target_instance = input_port.instance;
  467. if (target_instance === undefined) {
  468. this.broadcast(e, event_time - simulated_time);
  469. } else {
  470. target_instance.addEvent(e, event_time - simulated_time);
  471. }
  472. event_time = this.input_queue.getEarliestTime();
  473. }
  474. };
  475. ControllerBase.prototype.outputEvent = function(the_event) {
  476. for (var l in this.output_listeners) {
  477. if (!this.output_listeners.hasOwnProperty(l)) continue;
  478. this.output_listeners[l].add(the_event);
  479. }
  480. };
  481. ControllerBase.prototype.addOutputListener = function(ports) {
  482. var listener = new OutputListener(ports);
  483. this.output_listeners.push(listener);
  484. return listener;
  485. };
  486. ControllerBase.prototype.addMyOwnOutputListener = function(listener) {
  487. this.output_listeners.push(listener);
  488. };
  489. // GameLoopControllerBase
  490. function GameLoopControllerBase(object_manager) {
  491. ControllerBase.call(this, object_manager);
  492. }
  493. GameLoopControllerBase.prototype = new ControllerBase();
  494. GameLoopControllerBase.prototype.update = function(delta) {
  495. this.handleInput();
  496. earliest_event_time = this.getEarliestEventTime();
  497. if (earliest_event_time > time()) {
  498. simulated_time = earliest_event_time;
  499. this.object_manager.stepAll();
  500. }
  501. };
  502. // EventLoop
  503. // parameters:
  504. // schedule - a callback scheduling another callback in the event loop
  505. // this callback should take 2 parameters: (callback, timeout) and return an ID
  506. // clear - a callback that clears a scheduled callback
  507. // this callback should take an ID that was returned by 'schedule'
  508. function EventLoop(schedule, clear) {
  509. this.schedule_callback = schedule;
  510. this.clear_callback = clear;
  511. this.scheduled_id = null;
  512. this.last_print = 0.0;
  513. }
  514. EventLoop.prototype.schedule = function(f, wait_time, behind) {
  515. if (behind === undefined) behind = false
  516. if (this.scheduled_id) {
  517. // if the following error occurs, it is probably due to a flaw in the logic of EventLoopControllerBase
  518. throw new RuntimeException("EventLoop class intended to maintain at most 1 scheduled callback.");
  519. }
  520. if (wait_time != Infinity) {
  521. this.scheduled_id = this.schedule_callback(f, wait_time, behind)
  522. }
  523. };
  524. EventLoop.prototype.clear = function() {
  525. if (this.scheduled_id) {
  526. this.clear_callback(this.scheduled_id);
  527. this.scheduled_id = null;
  528. }
  529. };
  530. // EventLoopControllerBase
  531. function EventLoopControllerBase(object_manager, event_loop, finished_callback) {
  532. ControllerBase.call(this, object_manager);
  533. this.event_loop = event_loop;
  534. this.finished_callback = finished_callback;
  535. this.last_print_time = 0.0;
  536. }
  537. EventLoopControllerBase.prototype = new ControllerBase();
  538. EventLoopControllerBase.prototype.addInput = function(input_event, time_offset) {
  539. ControllerBase.prototype.addInput.call(this, input_event, time_offset);
  540. this.event_loop.clear();
  541. this.set_time_and_run();
  542. };
  543. EventLoopControllerBase.prototype.start = function() {
  544. ControllerBase.prototype.start.call(this);
  545. this.run();
  546. };
  547. EventLoopControllerBase.prototype.stop = function() {
  548. this.event_loop.clear();
  549. ControllerBase.prototype.stop.call(this);
  550. };
  551. EventLoopControllerBase.prototype.set_time_and_run = function() {
  552. simulated_time = ControllerBase.prototype.getEarliestEventTime.call(this);
  553. this.run();
  554. }
  555. EventLoopControllerBase.prototype.run = function() {
  556. var start_time = time();
  557. while (true) {
  558. // clear existing timeout
  559. this.event_loop.clear();
  560. // simulate
  561. this.handleInput();
  562. this.object_manager.stepAll();
  563. // set next timeout
  564. earliest_event_time = ControllerBase.prototype.getEarliestEventTime.call(this);
  565. if (earliest_event_time == Infinity) {
  566. if (this.finished_callback != undefined) this.finished_callback(); // TODO: This is not necessarily correct (keep_running necessary?)
  567. return;
  568. }
  569. var now = time();
  570. if (now - start_time > 0.01 || earliest_event_time - now > 0.0) {
  571. this.event_loop.schedule(this.set_time_and_run.bind(this), (earliest_event_time - now) * 1000, now - start_time > 0.05);
  572. if (now - earliest_event_time > 0.1 && now - this.last_print_time >= 1) {
  573. console.log('running ' + ((now - earliest_event_time) * 1000) + ' ms behind schedule');
  574. this.last_print_time = now;
  575. }
  576. return
  577. } else {
  578. simulated_time = ControllerBase.prototype.getEarliestEventTime.call(this);
  579. }
  580. }
  581. };
  582. // JsEventLoop
  583. function JsEventLoop() {
  584. EventLoop.call(this, window.setTimeout.bind(window), window.clearTimeout.bind(window));
  585. }
  586. JsEventLoop.prototype = new EventLoop();
  587. // Enum-like construct holding statechart semantic options.
  588. StatechartSemantics = {
  589. // Big Step Maximality
  590. TakeOne : 0,
  591. TakeMany : 1,
  592. // Concurrency
  593. Single : 0,
  594. Many : 1, // not yet implemented
  595. // Preemption (unsupported)
  596. NonPreemptive : 0,
  597. Preemptive : 1,
  598. // Internal Event Lifeline
  599. Queue : 0,
  600. NextSmallStep : 1,
  601. NextComboStep : 2,
  602. // Input Event Lifeline
  603. Whole : 0,
  604. FirstSmallStep : 1,
  605. FirstComboStep : 2,
  606. // Priority
  607. SourceParent : 0,
  608. SourceChild : 1
  609. };
  610. var DefaultStatechartSemantics = function() {
  611. this.big_step_maximality = this.TakeMany;
  612. this.concurrency = this.Single
  613. this.internal_event_lifeline = this.Queue;
  614. this.input_event_lifeline = this.FirstComboStep;
  615. this.priority = this.SourceParent;
  616. };
  617. // State
  618. function State(state_id, obj) {
  619. this.state_id = state_id;
  620. this.obj = obj;
  621. this.ancestors = new Array();
  622. this.descendants = new Array();
  623. this.children = new Array();
  624. this.my_parent = null;
  625. this.enter = null;
  626. this.exit = null;
  627. this.default_state = null;
  628. this.transitions = new Array();
  629. this.my_history = new Array();
  630. }
  631. State.prototype.getEffectiveTargetStates = function() {
  632. var targets = [this];
  633. if (this.default_state != null) {
  634. Array.prototype.push.apply(targets, this.default_state.getEffectiveTargetStates());
  635. }
  636. return targets;
  637. }
  638. State.prototype.fixTree = function() {
  639. for (let c of this.children) {
  640. if (c instanceof HistoryState) {
  641. this.my_history.push(c);
  642. }
  643. c.my_parent = this;
  644. c.ancestors.push(this);
  645. Array.prototype.push.apply(c.ancestors, this.ancestors);
  646. c.fixTree();
  647. }
  648. Array.prototype.push.apply(this.descendants, this.children)
  649. for (let c of this.children) {
  650. Array.prototype.push.apply(this.descendants, c.descendants)
  651. }
  652. }
  653. State.prototype.addChild = function(child) {
  654. this.children.push(child);
  655. }
  656. State.prototype.addTransition = function(transition) {
  657. this.transitions.push(transition);
  658. }
  659. State.prototype.setEnter = function(enter) {
  660. this.enter = enter.bind(this.obj);
  661. }
  662. State.prototype.setExit = function(exit) {
  663. this.exit = exit.bind(this.obj);
  664. }
  665. // HistoryState
  666. function HistoryState(state_id, obj) {
  667. State.call(this, state_id, obj);
  668. }
  669. HistoryState.prototype = new State();
  670. // ShallowHistoryState
  671. function ShallowHistoryState(state_id, obj) {
  672. HistoryState.call(this, state_id, obj);
  673. }
  674. ShallowHistoryState.prototype = new HistoryState();
  675. ShallowHistoryState.prototype.getEffectiveTargetStates = function() {
  676. if (this.state_id in this.obj.history_values) {
  677. var targets = [];
  678. for (let hv of this.obj.history_values[this.state_id]) {
  679. Array.prototype.push.apply(targets, hv.getEffectiveTargetStates());
  680. }
  681. return targets;
  682. } else {
  683. // TODO: is it correct that in this case, the parent itself is also entered?
  684. return this.my_parent.getEffectiveTargetStates();
  685. }
  686. }
  687. // DeepHistoryState
  688. function DeepHistoryState(state_id, obj) {
  689. HistoryState.call(this, state_id, obj);
  690. }
  691. DeepHistoryState.prototype = new HistoryState();
  692. DeepHistoryState.prototype.getEffectiveTargetStates = function() {
  693. if (this.state_id in this.obj.history_values) {
  694. return this.obj.history_values[this.state_id];
  695. } else {
  696. // TODO: is it correct that in this case, the parent itself is also entered?
  697. return this.my_parent.getEffectiveTargetStates();
  698. }
  699. }
  700. // ParallelState
  701. function ParallelState(state_id, obj) {
  702. State.call(this, state_id, obj);
  703. }
  704. ParallelState.prototype = new State();
  705. ParallelState.prototype.getEffectiveTargetStates = function() {
  706. var targets = [this];
  707. for (let c of this.children) {
  708. if (!(c instanceof HistoryState)) {
  709. Array.prototype.push.apply(targets, c.getEffectiveTargetStates());
  710. }
  711. }
  712. return targets;
  713. }
  714. // Transition
  715. function Transition(obj, source, targets) {
  716. this.guard = null;
  717. this.action = null;
  718. this.trigger = null;
  719. this.source = source;
  720. this.targets = targets;
  721. this.obj = obj;
  722. this.enabled_event = null; // the event that enabled this transition
  723. }
  724. Transition.prototype.isEnabled = function(events) {
  725. if (this.trigger === null) {
  726. this.enabled_event = null;
  727. return (this.guard === null || this.guard([]));
  728. } else {
  729. for (var i in events) {
  730. the_event = events[i];
  731. if ((this.trigger === null || (this.trigger.name == the_event.name && this.trigger.port == the_event.port)) && (this.guard === null || this.guard(the_event.parameters))) {
  732. this.enabled_event = the_event;
  733. return true;
  734. }
  735. }
  736. }
  737. }
  738. Transition.prototype.fire = function() {
  739. // exit states...
  740. var targets = this.__getEffectiveTargetStates();
  741. var exit_set = this.__exitSet(targets);
  742. for (let s of exit_set) {
  743. for (let h of s.my_history) {
  744. var f = function(s0) {return s0.ancestors.length > 0 && s0.my_parent == s;}
  745. if (h instanceof DeepHistoryState) {
  746. f = function(s0) {return s0.descendants.length == 0 && s.descendants.indexOf(s0) >= 0;}
  747. }
  748. this.obj.history_values[h.state_id] = this.obj.configuration.filter(f);
  749. }
  750. }
  751. for (let s of exit_set) {
  752. if (s.exit != null) {
  753. s.exit();
  754. }
  755. }
  756. this.obj.configuration = this.obj.configuration.filter(function(el) {return exit_set.indexOf(el) < 0;})
  757. // combo state changed area
  758. this.obj.combo_step.changed.push(this.lca);
  759. for (let d of this.lca.descendants) {
  760. this.obj.combo_step.changed.push(d);
  761. }
  762. // execute transition action(s)
  763. if (this.action != null) {
  764. var params = [];
  765. if (this.enabled_event != null) params = this.enabled_event.parameters;
  766. this.action(params);
  767. }
  768. // enter states...
  769. for (let s of this.__enterSet(targets)) {
  770. this.obj.configuration.push(s);
  771. if (s.enter != null) {
  772. s.enter();
  773. }
  774. }
  775. this.obj.configuration = this.obj.configuration.sort(function(a, b) {return a.state_id - b.state_id})
  776. this.enabled_event = null;
  777. }
  778. Transition.prototype.__getEffectiveTargetStates = function() {
  779. var targets = []
  780. for (let target of this.targets) {
  781. for (let e_t of target.getEffectiveTargetStates()) {
  782. if (targets.indexOf(e_t) < 0) {
  783. targets.push(e_t);
  784. }
  785. }
  786. }
  787. return targets;
  788. }
  789. Transition.prototype.__exitSet = function(targets) {
  790. var target = targets[0];
  791. this.lca = this.source.my_parent;
  792. if (this.source.my_parent != target.my_parent) { // external
  793. for (let a of this.source.ancestors) {
  794. if (target.ancestors.indexOf(a) >= 0) {
  795. this.lca = a;
  796. break;
  797. }
  798. }
  799. }
  800. var exit_set = this.lca.descendants.slice(0).filter(function(obj) {
  801. return function(s) {
  802. return obj.obj.configuration.indexOf(s) >= 0;
  803. }
  804. }(this));
  805. exit_set.reverse();
  806. return exit_set;
  807. }
  808. Transition.prototype.__enterSet = function*(targets) {
  809. var target = targets[0];
  810. var reversed_ancestors = target.ancestors.slice(0).reverse();
  811. for (let a of reversed_ancestors) {
  812. if (this.source.ancestors.indexOf(a) >= 0) {
  813. continue;
  814. } else {
  815. yield a;
  816. }
  817. }
  818. for (let target of targets) {
  819. yield target;
  820. }
  821. }
  822. Transition.prototype.conflicts = function(transition) {
  823. // TODO: implement
  824. throw new Exception("NOT IMPLEMENTED");
  825. }
  826. Transition.prototype.setGuard = function(guard) {
  827. this.guard = guard.bind(this.obj);
  828. }
  829. Transition.prototype.setAction = function(action) {
  830. this.action = action.bind(this.obj);
  831. }
  832. // RuntimeClassBase
  833. function RuntimeClassBase(controller) {
  834. this.active = false;
  835. this.__set_stable(true);
  836. this.events = new EventQueue();
  837. this.controller = controller;
  838. this.inports = new Object();
  839. this.timers = new Object();
  840. this.states = new Object();
  841. this.semantics = new DefaultStatechartSemantics();
  842. }
  843. RuntimeClassBase.prototype.start = function() {
  844. this.configuration = new Array();
  845. this.current_state = new Object();
  846. this.history_values = new Object();
  847. this.timers = new Object();
  848. this.big_step = new BigStepState();
  849. this.combo_step = new ComboStepState();
  850. this.small_step = new SmallStepState();
  851. this.active = true;
  852. this.__set_stable(false);
  853. this.initializeStatechart();
  854. this.processBigStepOutput();
  855. };
  856. RuntimeClassBase.prototype.updateConfiguration = function(states) {
  857. this.configuration = states.slice(0);
  858. };
  859. RuntimeClassBase.prototype.stop = function() {
  860. this.active = false;
  861. this.__set_stable(false);
  862. };
  863. RuntimeClassBase.prototype.addTimer = function(index, timeout) {
  864. this.timers[index] = this.events.add(new EventQueueEntry(simulated_time + timeout, new Event("_" + index + "after")));
  865. };
  866. RuntimeClassBase.prototype.removeTimer = function(index) {
  867. this.events.remove(this.timers[index]);
  868. delete this.timers[index];
  869. };
  870. RuntimeClassBase.prototype.addEvent = function(event_list, time_offset) {
  871. if (time_offset == undefined) time_offset = 0.0;
  872. var event_time = simulated_time + time_offset;
  873. if (event_time < this.earliest_event_time) {
  874. this.earliest_event_time = event_time;
  875. }
  876. if (!(event_list instanceof Array)) {
  877. event_list = [event_list];
  878. }
  879. for (i in event_list) {
  880. if (!event_list.hasOwnProperty(i)) continue;
  881. this.events.add(new EventQueueEntry(simulated_time + time_offset, event_list[i]));
  882. }
  883. };
  884. RuntimeClassBase.prototype.getEarliestEventTime = function() {
  885. return this.earliest_event_time;
  886. };
  887. RuntimeClassBase.prototype.processBigStepOutput = function() {
  888. var o = this.big_step.output_events_port;
  889. var om = this.big_step.output_events_om;
  890. for (var e in o) {
  891. if (!o.hasOwnProperty(e)) continue;
  892. this.controller.outputEvent(o[e]);
  893. }
  894. for (var e in om) {
  895. if (!om.hasOwnProperty(e)) continue;
  896. this.controller.object_manager.addEvent(om[e]);
  897. }
  898. };
  899. RuntimeClassBase.prototype.__set_stable = function(is_stable) {
  900. this.is_stable = is_stable;
  901. // self.earliest_event_time keeps track of the earliest time this instance will execute a transition
  902. if (!this.is_stable) {
  903. this.earliest_event_time = 0.0;
  904. } else if (!this.active) {
  905. this.earliest_event_time = Infinity;
  906. } else {
  907. this.earliest_event_time = this.events.getEarliestTime();
  908. }
  909. }
  910. RuntimeClassBase.prototype.step = function(delta) {
  911. var is_stable = false;
  912. while (!is_stable) {
  913. var due = [];
  914. if (this.events.getEarliestTime() <= simulated_time) {
  915. due = [this.events.pop()];
  916. }
  917. is_stable = !this.bigStep(due);
  918. this.processBigStepOutput();
  919. }
  920. this.__set_stable(true);
  921. };
  922. RuntimeClassBase.prototype.inState = function(state_strings) {
  923. var state_ids = [];
  924. for (let state_string of state_strings) {
  925. state_ids.push(this.states[state_string].state_id);
  926. }
  927. for (let state_id of state_ids) {
  928. var found = false;
  929. for (let s of this.configuration) {
  930. if (s.state_id == state_id) {
  931. found = true;
  932. break;
  933. }
  934. }
  935. if (!found) return false;
  936. }
  937. return true;
  938. };
  939. RuntimeClassBase.prototype.bigStep = function(input_events) {
  940. this.big_step.next(input_events);
  941. this.small_step.reset();
  942. this.combo_step.reset();
  943. while (this.comboStep()) {
  944. this.big_step.has_stepped = true;
  945. if (this.semantics.big_step_maximality === StatechartSemantics.TakeOne)
  946. break;
  947. }
  948. return this.big_step.has_stepped;
  949. };
  950. RuntimeClassBase.prototype.comboStep = function() {
  951. this.combo_step.next();
  952. while (this.smallStep()) {
  953. this.combo_step.has_stepped = true;
  954. }
  955. return this.combo_step.has_stepped;
  956. };
  957. // generate transition candidates for current small step
  958. RuntimeClassBase.prototype.generateCandidates = function() {
  959. var enabledEvents = this.getEnabledEvents();
  960. var enabledTransitions = new Array();
  961. for (let s of this.configuration) {
  962. if (this.combo_step.changed.indexOf(s) < 0) {
  963. for (let t of s.transitions) {
  964. if (t.isEnabled(enabledEvents)) {
  965. enabledTransitions.push(t);
  966. }
  967. }
  968. }
  969. }
  970. return enabledTransitions
  971. };
  972. RuntimeClassBase.prototype.smallStep = function() {
  973. var __younger_than =
  974. function(x, y) {
  975. if (y.source.ancestors.indexOf(x.source) >= 0) {
  976. return 1;
  977. } else if (x.source.ancestors.indexOf(y.source) >= 0) {
  978. return -1;
  979. } else {
  980. throw new Exception("These items have no relation with each other.");
  981. }
  982. };
  983. if (this.small_step.has_stepped) {
  984. this.small_step.next();
  985. }
  986. var candidates = this.generateCandidates();
  987. if (candidates.length > 0) {
  988. to_skip = new Set();
  989. conflicting = new Array();
  990. for (var i in candidates) {
  991. c1 = candidates[i];
  992. if (!to_skip.has(c1)) {
  993. conflict = [c1];
  994. for (var j in candidates.slice(candidates.indexOf(c1))) {
  995. c2 = candidates[j];
  996. if (c1.source.ancestors.indexOf(c2.source) >= 0 || c2.source.ancestors.indexOf(c1.source) >= 0) {
  997. conflict.push(c2);
  998. to_skip.add(c2);
  999. }
  1000. }
  1001. conflicting.push(conflict.sort(__younger_than));
  1002. }
  1003. }
  1004. if (this.semantics.concurrency === StatechartSemantics.Single) {
  1005. var candidate = conflicting[0];
  1006. if (this.semantics.priority === StatechartSemantics.SourceParent) {
  1007. candidate[candidate.length - 1].fire();
  1008. } else {
  1009. candidate[0].fire();
  1010. }
  1011. } else if (this.semantics.concurrency === StatechartSemantics.Many) {
  1012. // TODO: Implement
  1013. }
  1014. this.small_step.has_stepped = true;
  1015. }
  1016. return this.small_step.has_stepped;
  1017. };
  1018. RuntimeClassBase.prototype.getEnabledEvents = function() {
  1019. var result = this.small_step.current_events.concat(this.combo_step.current_events);
  1020. if (this.semantics.input_event_lifeline === StatechartSemantics.Whole ||
  1021. (!this.big_step.has_stepped &&
  1022. (this.semantics.input_event_lifeline === StatechartSemantics.FirstComboStep ||
  1023. (!this.combo_step.has_stepped &&
  1024. this.semantics.input_event_lifeline === StatechartSemantics.FirstSmallStep)))) {
  1025. result = result.concat(this.big_step.input_events);
  1026. }
  1027. return result;
  1028. };
  1029. RuntimeClassBase.prototype.raiseInternalEvent = function(the_event) {
  1030. if (this.semantics.internal_event_lifeline === StatechartSemantics.NextSmallStep) {
  1031. this.small_step.addNextEvent(the_event);
  1032. } else if (this.semantics.internal_event_lifeline === StatechartSemantics.NextComboStep) {
  1033. this.combo_step.addNextEvent(the_event);
  1034. } else if (this.semantics.internal_event_lifeline === StatechartSemantics.Queue) {
  1035. this.events.add(new EventQueueEntry(0.0, the_event));
  1036. }
  1037. };
  1038. RuntimeClassBase.prototype.initializeStatechart = function() {
  1039. // pure virtual
  1040. };
  1041. // BigStepState
  1042. var BigStepState = function() {
  1043. this.input_events = new Array();
  1044. this.output_events_port = new Array();
  1045. this.output_events_om = new Array();
  1046. this.has_stepped = true;
  1047. this.transitions = 0;
  1048. };
  1049. BigStepState.prototype.next = function(input_events) {
  1050. this.input_events = input_events;
  1051. this.output_events_port = new Array();
  1052. this.output_events_om = new Array();
  1053. this.has_stepped = false;
  1054. this.transitions = 0;
  1055. };
  1056. BigStepState.prototype.outputEvent = function(event) {
  1057. this.output_events_port.push(event);
  1058. };
  1059. BigStepState.prototype.outputEventOM = function(event) {
  1060. this.output_events_om.push(event);
  1061. };
  1062. // ComboStepState
  1063. var ComboStepState = function() {
  1064. this.current_events = new Array();
  1065. this.next_events = new Array();
  1066. this.changed = new Array();
  1067. this.has_stepped = true;
  1068. };
  1069. ComboStepState.prototype.reset = function() {
  1070. this.current_events = new Array();
  1071. this.next_events = new Array();
  1072. };
  1073. ComboStepState.prototype.next = function() {
  1074. this.current_events = this.next_events;
  1075. this.next_events = new Array();
  1076. this.changed = new Array();
  1077. this.has_stepped = false;
  1078. };
  1079. ComboStepState.prototype.addNextEvent = function(event) {
  1080. this.next_events.push(event);
  1081. };
  1082. ComboStepState.prototype.setArenaChanged = function(arena) {
  1083. this.changed.push(arena);
  1084. };
  1085. ComboStepState.prototype.isArenaChanged = function(arena) {
  1086. return (this.changed.indexOf(arena) !== -1);
  1087. };
  1088. ComboStepState.prototype.isStable = function() {
  1089. return (this.changed.length === 0);
  1090. };
  1091. // SmallStepState
  1092. var SmallStepState = function() {
  1093. this.current_events = new Array();
  1094. this.next_events = new Array();
  1095. this.candidates = new Array();
  1096. this.has_stepped = true;
  1097. };
  1098. SmallStepState.prototype.reset = function() {
  1099. this.current_events = new Array();
  1100. this.next_events = new Array();
  1101. };
  1102. SmallStepState.prototype.next = function() {
  1103. this.current_events = this.next_events;
  1104. this.next_events = new Array();
  1105. this.candidates = new Array();
  1106. this.has_stepped = false;
  1107. };
  1108. SmallStepState.prototype.addNextEvent = function(event) {
  1109. this.next_events.push(event);
  1110. };
  1111. SmallStepState.prototype.addCandidate = function(t, p) {
  1112. this.candidates.push({transition: t, parameters: p});
  1113. };
  1114. SmallStepState.prototype.hasCandidates = function() {
  1115. return (this.candidates.length > 0);
  1116. };