statecharts_core.js 33 KB

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