statecharts_core.js 40 KB

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