12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205 |
- /*
- author:
- mikolalysenko (https://www.npmjs.org/~mikolalysenko)
- license:
- MIT
- source:
- https://github.com/mikolalysenko/double-bits
- https://github.com/mikolalysenko/nextafter
- */
- (function(){
- var hasTypedArrays = false;
- if(typeof Float64Array !== "undefined") {
- var DOUBLE_VIEW = new Float64Array(1);
- var UINT_VIEW = new Uint32Array(DOUBLE_VIEW.buffer);
- DOUBLE_VIEW[0] = 1.0;
- hasTypedArrays = true;
- if(UINT_VIEW[1] === 0x3ff00000) {
- //Use little endian
- var pack = function(lo, hi) {
- UINT_VIEW[0] = lo;
- UINT_VIEW[1] = hi;
- return DOUBLE_VIEW[0];
- };
- var lo = function(n) {
- DOUBLE_VIEW[0] = n;
- return UINT_VIEW[0];
- };
- var hi = function(n) {
- DOUBLE_VIEW[0] = n;
- return UINT_VIEW[1];
- };
- } else if(UINT_VIEW[0] === 0x3ff00000) {
- //Use big endian
- var pack = function(lo, hi) {
- UINT_VIEW[1] = lo;
- UINT_VIEW[0] = hi;
- return DOUBLE_VIEW[0];
- };
- var lo = function(n) {
- DOUBLE_VIEW[0] = n;
- return UINT_VIEW[1];
- };
- var hi = function(n) {
- DOUBLE_VIEW[0] = n;
- return UINT_VIEW[0];
- };
- } else {
- hasTypedArrays = false;
- }
- }
- if(!hasTypedArrays) {
- var buffer = new Buffer(8);
- var pack = function(lo, hi) {
- buffer.writeUInt32LE(lo, 0, true);
- buffer.writeUInt32LE(hi, 4, true);
- return buffer.readDoubleLE(0, true);
- };
- var lo = function(n) {
- buffer.writeDoubleLE(n, 0, true);
- return buffer.readUInt32LE(0, true);
- };
- var hi = function(n) {
- buffer.writeDoubleLE(n, 0, true);
- return buffer.readUInt32LE(4, true);
- };
- }
- var SMALLEST_DENORM = Math.pow(2, -1074);
- var UINT_MAX = (-1)>>>0;
- nextafter = function(x, y) {
- if(isNaN(x) || isNaN(y)) {
- return NaN;
- }
- if(x === y) {
- return x;
- }
- if(x === 0) {
- if(y < 0) {
- return -SMALLEST_DENORM;
- } else {
- return SMALLEST_DENORM;
- }
- }
- var h = hi(x);
- var l = lo(x);
- if((y > x) === (x > 0)) {
- if(l === UINT_MAX) {
- h += 1;
- l = 0;
- } else {
- l += 1;
- }
- } else {
- if(l === 0) {
- l = UINT_MAX;
- h -= 1;
- } else {
- l -= 1;
- }
- }
- return pack(l, h);
- }
- })();
- function time() {
- return (new Date).getTime()/1000.0;
- }
- // Exception
- function RuntimeException(msg) {
- this.msg = msg;
- }
- // InputException
- function InputException(msg) {
- RuntimeException.call(this, msg);
- }
- InputException.prototype = new RuntimeException();
- // AssociationException
- function AssociationException(msg) {
- RuntimeException.call(this, msg);
- }
- AssociationException.prototype = new RuntimeException();
- // AssociationReferenceException
- function AssociationReferenceException(msg) {
- RuntimeException.call(this, msg);
- }
- AssociationReferenceException.prototype = new RuntimeException();
- // ParameterException
- function ParameterException(msg) {
- RuntimeException.call(this, msg);
- }
- ParameterException.prototype = new RuntimeException();
- // InputException
- function InputException(msg) {
- RuntimeException.call(this, msg);
- }
- InputException.prototype = new RuntimeException();
- // EventQueueEntry
- function EventQueueEntry(event_list, time_offset) {
- this.event_list = event_list;
- this.time_offset = time_offset;
- }
- EventQueueEntry.prototype.decreaseTime = function(offset) {
- this.time_offset -= offset;
- };
- // EventQueue
- function EventQueue() {
- this.event_list = new Array();
- }
- EventQueue.prototype.add = function(event_list, time_offset) {
- var entry = new EventQueueEntry(event_list, time_offset);
- var insert_index = 0;
- var index = this.event_list.length - 1;
- while (index >= 0) {
- if (this.event_list[index].time_offset <= time_offset) {
- insert_index = index + 1;
- break;
- }
- index -= 1;
- }
- this.event_list.splice(insert_index, 0, entry);
- };
- EventQueue.prototype.decreaseTime = function(offset) {
- for (var event in this.event_list) {
- if (!this.event_list.hasOwnProperty(event)) continue;
- this.event_list[event].decreaseTime(offset);
- }
- };
- EventQueue.prototype.isEmpty = function() {
- return this.event_list.length === 0;
- };
- EventQueue.prototype.getEarliestTime = function() {
- if (this.isEmpty()) {
- return Infinity;
- } else {
- return this.event_list[0].time_offset;
- }
- };
- EventQueue.prototype.popDueEvents = function() {
- var result = new Array();
- if (this.isEmpty() || this.event_list[0].time_offset > 0.0) {
- return result;
- }
- var index = 0;
- while (index < this.event_list.length && this.event_list[index].time_offset <= 0.0) {
- result.push(this.event_list[index].event_list);
- index++;
- }
- this.event_list.splice(0, index);
- return result;
- };
- // Association
- function Association(to_class, min_card, max_card) {
- this.to_class = to_class;
- this.min_card = min_card;
- this.max_card = max_card;
- this.instances = new Object(); /* maps index (as string) to instance */
- this.size = 0;
- this.next_id = 0;
- }
- Association.prototype.allowedToAdd = function() {
- return (this.max_card === -1 || this.size < this.max_card);
- };
- Association.prototype.allowedToRemove = function() {
- return (this.min_card === -1 || this.size > this.min_card);
- };
- Association.prototype.addInstance = function(instance) {
- if (this.allowedToAdd()) {
- var id = this.next_id++;
- this.instances[id] = instance;
- return id;
- } else {
- throw new AssociationException("Not allowed to add the instance to the association.");
- }
- };
- /*Association.prototype.removeInstance = function(instance) {
- if (this.allowedToRemove()) {
- delete this.instances[this.instances_to_ids[instance]]
- } else {
- throw new AssociationException("Not allowed to remove the instance to the association.");
- }
- };*/
- Association.prototype.getInstance = function(index) {
- var instance = this.instances[index];
- if (instance === undefined) {
- throw new AssociationException("Invalid index for fetching instance(s) from association.");
- }
- return instance;
- };
- // ObjectManagerBase
- function ObjectManagerBase(controller) {
- this.controller = controller;
- this.events = new EventQueue();
- this.instances = new Array();
- }
- ObjectManagerBase.prototype.addEvent = function(event, time_offset) {
- if (!time_offset) time_offset = 0.0;
- this.events.add(event, time_offset);
- };
- ObjectManagerBase.prototype.broadcast = function(new_event) {
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- this.instances[i].addEvent(new_event);
- }
- };
- ObjectManagerBase.prototype.getWaitTime = function() {
- var smallest_time = this.events.getEarliestTime();
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- smallest_time = Math.min(smallest_time, this.instances[i].getEarliestEventTime());
- }
- return smallest_time;
- };
- ObjectManagerBase.prototype.stepAll = function(delta) {
- this.step(delta);
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- this.instances[i].step(delta);
- }
- };
- ObjectManagerBase.prototype.step = function(delta) {
- this.events.decreaseTime(delta);
- var due = this.events.popDueEvents();
- for (var e in due) {
- this.handleEvent(due[e]);
- }
- };
- ObjectManagerBase.prototype.start = function() {
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- this.instances[i].start();
- }
- };
- ObjectManagerBase.prototype.handleEvent = function(e) {
- if (e.name === "narrow_cast") {
- this.handleNarrowCastEvent(e.parameters);
- } else if (e.name === "broad_cast") {
- this.handleBroadcastEvent(e.parameters);
- } else if (e.name === "create_instance") {
- this.handleCreateEvent(e.parameters);
- } else if (e.name === "associate_instance") {
- this.handleAssociateEvent(e.parameters);
- } else if (e.name === "start_instance") {
- this.handleStartInstanceEvent(e.parameters);
- } else if (e.name === "delete_instance") {
- this.handleDeleteInstanceEvent(e.parameters);
- }
- };
- ObjectManagerBase.prototype.processAssociationReference = function(input_string) {
- //if (input_string === "") {
- //throw new AssociationReferenceException("Empty association reference.");
- //}
- var regex = /^([a-zA-Z_]\w*)(?:\[(\d+)\])?$/;
- var path_string = input_string.split('/');
- var result = new Array();
- if (input_string !== "") {
- for (var p in path_string) {
- if (!path_string.hasOwnProperty(p)) continue;
- var m = regex.exec(path_string[p]);
- if (m) {
- var name = m[1];
- var index = m[2];
- if (!index) {
- index = -1;
- }
- result.push({name:name,index:index});
- } else {
- throw new AssociationReferenceException("Invalid entry in association reference.");
- }
- }
- }
- return result;
- };
- ObjectManagerBase.prototype.handleStartInstanceEvent = function(parameters) {
- if (parameters.length !== 2) {
- throw new ParameterException("The start instance event needs 2 parameters.");
- }
- var source = parameters[0];
- var traversal_list = this.processAssociationReference(parameters[1]);
- var instances = this.getInstances(source, traversal_list);
- for (var i in instances) {
- if (!instances.hasOwnProperty(i)) continue;
- instances[i].instance.start();
- }
- };
- ObjectManagerBase.prototype.handleDeleteInstanceEvent = function(parameters) {
- if (parameters.length !== 2) {
- throw new ParameterException("The delete instance event needs 2 parameters.");
- }
- var source = parameters[0];
- var traversal_list = this.processAssociationReference(parameters[1]);
- var instances = this.getInstances(source, traversal_list);
- for (var i in instances) {
- if (!instances.hasOwnProperty(i)) continue;
- instances[i].instance.stop();
- instances[i].instance.user_defined_destructor();
- // delete association from source instance
- var association_to_remove = instances[i].ref.associations[instances[i].assoc_name];
- if (instances[i].assoc_index === -1) {
- /*for (var x in association_to_remove.instances) {
- if (!association_to_remove.instances.hasOwnProperty(x)) continue;
- association_to_remove.instances = new Object();
- //association_to_remove.instances[x] = null;
- }*/
- // empty instances object
- association_to_remove.instances = new Object();
- //association_to_remove.instances = new Array();
- } else {
- //association_to_remove.instances[instances[i].assoc_index] = null;
- // remove property from instances object
- delete association_to_remove.instances[instances[i].assoc_index];
- }
- // also remove instance from OM's list of instances
- index = this.instances.indexOf(instances[i].instance);
- this.instances.splice(index,1);
- }
- source.addEvent(new Event("instance_deleted", undefined, [parameters[1]]));
- };
- ObjectManagerBase.prototype.handleBroadcastEvent = function(parameters) {
- if (parameters.length !== 1) {
- throw new ParameterException("The broadcast event needs 1 parameter.");
- }
- this.broadcast(parameters[0]);
- };
- ObjectManagerBase.prototype.handleCreateEvent = function(parameters) {
- if (parameters.length < 2) {
- throw new ParameterException("The create event needs at least 2 parameters.");
- }
- var source = parameters[0];
- var association_name = parameters[1];
- var association = source.associations[association_name];
- if (!association) {
- throw new ParameterException("No such association: " + association_name);
- }
- if (association.allowedToAdd()) {
- // allow subclasses to be instantiated
- if (parameters.length === 2) {
- var class_name = association.to_class;
- var creation_parameters = [];
- } else /* 3 or more parameters*/ {
- // 3rd parameter is class name
- var class_name = parameters[2];
- // parameters after 3rd parameter are creation parameters
- var creation_parameters = parameters.slice(3);
- }
- var new_instance = this.createInstance(class_name, creation_parameters);
- if (new_instance === undefined) {
- throw new ParameterException("Creating instance: no such class: " + class_name);
- }
- var index = association.addInstance(new_instance);
- // add parent association to created instance
- // if a parent association is defined in the class diagram
- var parent_association = new_instance.associations["parent"];
- if (parent_association !== undefined) {
- parent_association.addInstance(source);
- }
- // TODO: maybe change order of Event constructor parameters such that we don't have to
- // explicitly set the port to 'undefined'?
- source.addEvent(new Event("instance_created", undefined, [association_name+"["+index+"]"]));
- } else {
- source.addEvent(new Event("instance_creation_error", undefined, [association_name]));
- }
- };
- ObjectManagerBase.prototype.handleAssociateEvent = function(parameters) {
- if (parameters.length !== 3) {
- throw new ParameterException("The associate_instance event needs 3 parameters.");
- }
- var source = parameters[0];
- var source_list = parameters[1];
- var traversal_list = this.processAssociationReference(source_list);
- var to_copy_list = this.getInstances(source, traversal_list);
- if (to_copy_list.length !== 1) {
- throw new AssociationReferenceException("Invalid source association reference.");
- }
- var wrapped_to_copy_instance = to_copy_list[0].instance;
- var dest_list = this.processAssociationReference(parameters[2]);
- if (dest_list.length === 0) {
- throw new AssociationReferenceException("Invalid destination association reference.");
- }
- var last = dest_list.pop();
- if (last.index !== -1) {
- throw new AssociationReferenceException("Last association name in association reference could not be accompanied by an index.");
- }
- var instances = this.getInstances(source, dest_list);
- for (var i in instances) {
- if (!instances.hasOwnProperty(i)) continue;
- instances[i].instance.associations[last.name].addInstance(wrapped_to_copy_instance);
- }
- };
- ObjectManagerBase.prototype.handleNarrowCastEvent = function(parameters) {
- if (parameters.length !== 3) {
- throw new ParameterException("The narrow_cast event needs 3 parameters.");
- }
- var source = parameters[0];
- var traversal_list = this.processAssociationReference(parameters[1]);
- var cast_event = parameters[2];
- var instances = this.getInstances(source, traversal_list);
- for (var i in instances) {
- if (!instances.hasOwnProperty(i)) continue;
- instances[i].instance.addEvent(cast_event);
- }
- };
- ObjectManagerBase.prototype.getInstances = function(source, traversal_list) {
- var currents = [{
- instance : source,
- ref : null,
- assoc_name : null,
- assoc_index : null
- }];
- for (var t in traversal_list) {
- if (!traversal_list.hasOwnProperty(t)) continue;
- var name = traversal_list[t].name;
- var index = traversal_list[t].index;
- nexts = new Array();
- for (var c in currents) {
- if (!currents.hasOwnProperty(c)) continue;
- var association = currents[c].instance.associations[name];
- if (index >= 0) {
- nexts.push({
- instance : association.getInstance(index),
- ref : currents[c].instance,
- assoc_name : name,
- assoc_index : index
- });
- } else if (index === -1) {
- for (var i in association.instances) {
- if (!association.instances.hasOwnProperty(i)) continue;
- nexts.push({
- instance: association.instances[i],
- ref: currents[c].instance,
- assoc_name : name,
- assoc_index : index
- });
- }
- //nexts = nexts.concat(association.instances);
- } else {
- throw new AssociationReferenceException("Incorrect index in association reference.");
- }
- }
- currents = nexts;
- }
- return currents;
- };
- ObjectManagerBase.prototype.instantiate = function(to_class, construct_params) {
- // pure virtual
- };
- ObjectManagerBase.prototype.createInstance = function(to_class, construct_params) {
- var instance = this.instantiate(to_class, construct_params);
- this.instances.push(instance);
- return instance;
- };
- // Event
- function Event(name, port, parameters) {
- this.name = name;
- this.port = port;
- this.parameters = parameters;
- }
- // ControllerBase
- function ControllerBase(object_manager) {
- this.object_manager = object_manager;
- this.input_ports = new Object(); /* maps port name to pair of (private name, instance) */
- this.private_port_counter = 0;
- this.input_queue = new EventQueue();
- this.output_ports = new Array();
- this.output_listeners = new Array();
- }
- ControllerBase.prototype.addInputPort = function(virtual_name, instance) {
- if (instance === undefined) {
- var port_name = virtual_name; // "public" port
- } else {
- var port_name = "private_" + (this.private_port_counter++) + /*"_" + instance.class_name +*/ "_" + virtual_name;
- }
- this.input_ports[port_name] = {
- virtual_name: virtual_name,
- instance: instance
- };
- return port_name;
- };
- ControllerBase.prototype.removeInputPort = function(name) {
- delete this.input_ports[name];
- };
- ControllerBase.prototype.addOutputPort = function(port_name) {
- this.output_ports.push(port_name);
- };
- ControllerBase.prototype.broadcast = function(new_event) {
- this.object_manager.broadcast(new_event);
- };
- ControllerBase.prototype.start = function() {
- this.object_manager.start();
- };
- ControllerBase.prototype.stop = function() {
- };
- ControllerBase.prototype.addInput = function(input_event_list, time_offset) {
- if (!(input_event_list instanceof Array))
- input_event_list = [input_event_list];
- for (var e in input_event_list) {
- if (!input_event_list.hasOwnProperty(e)) continue;
- if (input_event_list[e].name === "") {
- throw new InputException("Input event can't have an empty name.");
- }
- var input_port = this.input_ports[input_event_list[e].port];
- if (input_port === undefined) {
- throw new InputException("Input port mismatch, no such port: " + input_event_list[e].port + ".");
- }
- }
- this.input_queue.add(input_event_list, time_offset);
- };
- ControllerBase.prototype.getWaitTime = function() {
- return Math.min(this.object_manager.getWaitTime(), this.input_queue.getEarliestTime());
- };
- ControllerBase.prototype.handleInput = function(delta) {
- this.input_queue.decreaseTime(delta);
- var due = this.input_queue.popDueEvents();
- for (var entry in due) {
- if (!due.hasOwnProperty(entry)) continue;
- for (var e in due[entry]) {
- if (!due[entry].hasOwnProperty(e)) continue;
- var event = due[entry][e];
- input_port = this.input_ports[event.port];
- event.port = input_port.virtual_name
- var target_instance = input_port.instance;
- if (!target_instance) {
- this.broadcast(event);
- } else {
- target_instance.addEvent(event);
- }
- }
-
- }
- };
- ControllerBase.prototype.outputEvent = function(event) {
- for (var l in this.output_listeners) {
- if (!this.output_listeners.hasOwnProperty(l)) continue;
- this.output_listeners[l].add(event);
- }
- };
- ControllerBase.prototype.addOutputListener = function(ports) {
- var listener = new OutputListener(ports);
- this.output_listeners.push(listener);
- return listener;
- };
- ControllerBase.prototype.addMyOwnOutputListener = function(listener) {
- this.output_listeners.push(listener);
- };
- ControllerBase.prototype.addEventList = function(event_list) {
- for (var e in event_list) {
- if (!event_list.hasOwnProperty(e)) continue;
- var entry = event_list[e];
- this.addInput(entry.event, entry.time_offset);
- }
- };
- // GameLoopControllerBase
- function GameLoopControllerBase(object_manager) {
- ControllerBase.call(this, object_manager);
- }
- GameLoopControllerBase.prototype = new ControllerBase();
- GameLoopControllerBase.prototype.update = function(delta) {
- this.handleInput(delta);
- this.object_manager.stepAll(delta);
- };
- function TimeoutId(id, delay) {
- this.id = id;
- this.delay = delay;
- }
- // EventLoop
- // parameters:
- // schedule - a callback scheduling another callback in the event loop
- // this callback should take 2 parameters: (callback, timeout) and return an ID
- // clear - a callback that clears a scheduled callback
- // this callback should take an ID that was returned by 'schedule'
- function EventLoop(schedule, clear) {
- this.schedule_callback = schedule;
- this.clear_callback = clear;
- this.scheduled_id = null;
- this.last_time = null;
- this.next_wakeup = null;
- this.last_print = 0.0;
- }
- EventLoop.prototype.getScheduledTimeout = function() {
- if (this.last_time && this.next_wakeup) {
- return this.next_wakeup - this.last_time;
- } else {
- return Infinity;
- }
- };
- EventLoop.prototype.schedule = function(f, wait_time) {
- if (this.scheduled_id) {
- // if the following error occurs, it is probably due to a flaw in the logic of EventLoopControllerBase
- throw new RuntimeException("EventLoop class intended to maintain at most 1 scheduled callback.");
- }
- if (wait_time === Infinity) {
- this.last_time = null;
- this.next_wakeup = null;
- } else {
- var now = time();
- if (this.last_time === null) {
- this.last_time = now;
- }
- this.next_wakeup = this.last_time + wait_time;
- if (this.next_wakeup - this.last_time < wait_time) { // compensate floating-point imprecision
- this.next_wakeup = nextafter(this.next_wakeup, Infinity);
- }
- var remaining = Math.max(this.next_wakeup - now, 0.0);
-
- this.scheduled_id = this.schedule_callback(f, remaining*1000.0);
- }
- };
- EventLoop.prototype.clear = function() {
- if (this.scheduled_id) {
- this.clear_callback(this.scheduled_id);
- this.scheduled_id = null;
- }
- };
- EventLoop.prototype.nextDelta = function() {
- var now = time();
- if (this.next_wakeup) {
- var simulated_now = this.next_wakeup;
- } else {
- var simulated_now = now;
- }
- if (now - this.last_print > 1.0) {
- var behind_schedule = now - simulated_now;
- if (behind_schedule > 0.1) {
- console.log("Warning: running " + behind_schedule*1000.0 + " ms behind schedule");
- this.last_print = now
- }
- }
- if (this.last_time) {
- var delta = simulated_now - this.last_time;
- } else {
- var delta = 0.0;
- }
- this.last_time = simulated_now;
- this.next_wakeup = null;
- return delta;
- };
- EventLoop.prototype.elapsed = function() {
- if (this.last_time) {
- return time() - this.last_time;
- } else {
- return 0.0;
- }
- };
- // JsEventLoop
- function JsEventLoop() {
- EventLoop.call(this, window.setTimeout.bind(window), window.clearTimeout.bind(window));
- }
- JsEventLoop.prototype = new EventLoop();
- // EventLoopControllerBase
- function EventLoopControllerBase(object_manager, event_loop, finished_callback) {
- ControllerBase.call(this, object_manager);
- this.event_loop = event_loop;
- this.finished_callback = finished_callback;
- this.last_simulation_time = null;
- 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.
- }
- EventLoopControllerBase.prototype = new ControllerBase();
- EventLoopControllerBase.prototype.addInput = function(input_event, time_offset) {
- var elapsed = this.event_loop.elapsed();
- var controller_timeout = time_offset + elapsed;
- ControllerBase.prototype.addInput.call(this, input_event, controller_timeout);
- if (controller_timeout < this.event_loop.getScheduledTimeout()) {
- // added event's timeout is sooner than existing timeout -> re-schedule
- this.event_loop.clear();
- this.event_loop.schedule(this.run.bind(this), controller_timeout);
- }
- };
- EventLoopControllerBase.prototype.start = function() {
- ControllerBase.prototype.start.call(this);
- this.run();
- };
- EventLoopControllerBase.prototype.stop = function() {
- this.event_loop.clear();
- ControllerBase.prototype.stop.call(this);
- };
- EventLoopControllerBase.prototype.run = function() {
- // clear existing timeout
- this.event_loop.clear();
- // calculate last time since simulation
- var delta = this.event_loop.nextDelta();
- // simulate
- this.handleInput(delta);
- this.object_manager.stepAll(delta);
- // set next timeout
- var wait_time = this.getWaitTime();
- this.event_loop.schedule(this.run.bind(this), wait_time);
- if (wait_time === Infinity) {
- if (this.finished_callback) {
- this.finished_callback();
- }
- }
- };
- // OutputListener
- function OutputListener(port_names) {
- this.port_names = port_names;
- this.queue = new Array(); // TODO: optimize!
- }
- OutputListener.prototype.add = function(event) {
- if (this.port_names.length === 0
- || this.port_names.indexOf(event.port) !== -1)
- {
- this.queue.push(event);
- }
- };
- OutputListener.prototype.fetch = function(timeout) {
- return this.queue.shift();
- };
- // Enum-like construct holding statechart semantic options.
- StatechartSemantics = {
- // Big Step Maximality
- TakeOne : 0,
- TakeMany : 1,
- // Concurrency
- Single : 0,
- Many : 1, // not yet implemented
- // Preemption (unsupported)
- NonPreemptive : 0,
- Preemptive : 1,
- // Internal Event Lifeline
- Queue : 0,
- NextSmallStep : 1,
- NextComboStep : 2,
- // Input Event Lifeline
- Whole : 0,
- FirstSmallStep : 1,
- FirstComboStep : 2,
- // Priority
- SourceParent : 0,
- SourceChild : 1
- };
- var DefaultStatechartSemantics = function() {
- this.big_step_maximality = this.TakeMany;
- this.concurrency = this.Single
- this.internal_event_lifeline = this.Queue;
- this.input_event_lifeline = this.FirstComboStep;
- this.priority = this.SourceParent;
- };
- // RuntimeClassBase
- function RuntimeClassBase(controller) {
- this.active = false;
- this.is_stable = true;
- this.events = new EventQueue();
- this.controller = controller;
- this.timers = null;
- this.inports = new Object();
- this.semantics = new DefaultStatechartSemantics();
- }
- RuntimeClassBase.prototype.start = function() {
- this.current_state = new Object();
- this.history_state = new Object();
- this.timers = new Object();
- this.big_step = new BigStepState();
- this.combo_step = new ComboStepState();
- this.small_step = new SmallStepState();
- this.active = true;
- this.is_stable = false;
- this.initializeStatechart();
- this.processBigStepOutput();
- };
- RuntimeClassBase.prototype.stop = function() {
- this.active = false;
- };
- RuntimeClassBase.prototype.addEvent = function(event_list, time_offset) {
- if (!time_offset) time_offset = 0.0;
- if (!(event_list instanceof Array)) {
- event_list = [event_list];
- }
- this.events.add(event_list, time_offset);
- };
- RuntimeClassBase.prototype.getEarliestEventTime = function() {
- if (!this.active) {
- return Infinity;
- }
- if (!this.is_stable) {
- return 0.0;
- }
- var min_timers = Infinity;
- for (var t in this.timers) {
- if (!this.timers.hasOwnProperty(t)) continue;
- min_timers = Math.min(min_timers, this.timers[t]);
- }
- return Math.min(this.events.getEarliestTime(), min_timers);
- };
- RuntimeClassBase.prototype.processBigStepOutput = function() {
- var o = this.big_step.getOutputEvents();
- var om = this.big_step.getOutputEventsOM();
- for (var e in o) {
- if (!o.hasOwnProperty(e)) continue;
- this.controller.outputEvent(o[e]);
- }
- for (var e in om) {
- if (!om.hasOwnProperty(e)) continue;
- this.controller.object_manager.addEvent(om[e]);
- }
- };
- RuntimeClassBase.prototype.step = function(delta) {
- if (!this.active) {
- return;
- }
- // decrease event queue time
- this.events.decreaseTime(delta);
- // decrease timers time
- var next_timers = new Object();
- for (var t in this.timers) {
- if (!this.timers.hasOwnProperty(t)) continue;
- var time_left = this.timers[t] - delta;
- if (time_left <= 0.0) {
- this.addEvent(new Event("_" + t + "after"), time_left);
- } else {
- next_timers[t] = time_left;
- }
- }
- this.timers = next_timers;
- // execute big step(s)
- var due = this.events.popDueEvents();
- if (due.length === 0 && !this.is_stable) {
- due = [[]]; // object was not stable after last big step -> execute 1 big step with no input events
- }
- for (var e in due) {
- if (!due.hasOwnProperty(e)) continue;
- this.is_stable = !this.bigStep(due[e]);
- this.processBigStepOutput();
- }
- };
- RuntimeClassBase.prototype.inState = function(nodes) {
- for (var c in this.current_state) {
- if (!this.current_state.hasOwnProperty(c)) continue;
- var new_nodes = new Array();
- for (var n in nodes) {
- if (!nodes.hasOwnProperty(n)) continue;
- if (this.current_state[c].indexOf(nodes[n]) === -1) {
- new_nodes.push(nodes[n]);
- }
- }
- nodes = new_nodes;
- if (nodes.length === 0) {
- return true;
- }
- }
- return false;
- };
- RuntimeClassBase.prototype.bigStep = function(input_events) {
- //console.log("new big step");
- //console.log("input events = " + JSON.stringify(input_events));
- this.big_step.next(input_events);
- this.small_step.reset();
- this.combo_step.reset();
- while (this.comboStep()) {
- this.big_step.setStepped();
- if (this.semantics.big_step_maximality === StatechartSemantics.TakeOne)
- break;
- }
- return this.big_step.hasStepped();
- };
- RuntimeClassBase.prototype.comboStep = function() {
- //console.log("new combo step");
- this.combo_step.next();
- while (this.smallStep()) {
- this.combo_step.setStepped();
- if (++this.big_step.transitions > 1000) {
- console.log("maximum number of big step transitions exceded");
- return false;
- }
- }
- return this.combo_step.hasStepped();
- };
- RuntimeClassBase.prototype.smallStep = function() {
- if (this.small_step.hasStepped()) {
- this.small_step.next();
- }
- this.generateCandidates();
- if (this.small_step.hasCandidates()) {
- //console.log("new small step");
- if (this.semantics.concurrency === StatechartSemantics.Single) {
- var cand = this.small_step.getCandidates()[0];
- cand.transition.call(this, cand.parameters);
- } else if (this.semantics.concurrency === StatechartSemantics.Many) {
- // TODO: implement
- }
- this.small_step.setStepped();
- }
- return this.small_step.hasStepped();
- };
- RuntimeClassBase.prototype.getEnabledEvents = function() {
- var result = this.small_step.getCurrentEvents().concat(this.combo_step.getCurrentEvents());
- if (this.semantics.input_event_lifeline === StatechartSemantics.Whole ||
- (!this.big_step.hasStepped() &&
- (this.semantics.input_event_lifeline === StatechartSemantics.FirstComboStep ||
- (!this.combo_step.hasStepped() &&
- this.semantics.input_event_lifeline === StatechartSemantics.FirstSmallStep)))) {
- result = result.concat(this.big_step.getInputEvents());
- }
- return result;
- };
- RuntimeClassBase.prototype.raiseInternalEvent = function(event) {
- if (this.semantics.internal_event_lifeline === StatechartSemantics.NextSmallStep) {
- this.small_step.addNextEvent(event);
- } else if (this.semantics.internal_event_lifeline === StatechartSemantics.NextComboStep) {
- this.combo_step.addNextEvent(event);
- } else if (this.semantics.internal_event_lifeline === StatechartSemantics.Queue) {
- this.events.add([event], 0.0);
- }
- };
- RuntimeClassBase.prototype.initializeStatechart = function() {
- // pure virtual
- };
- RuntimeClassBase.prototype.generateCandidates = function() {
- // pure virtual
- };
- // BigStepState
- var BigStepState = function() {
- this.input_events = new Array();
- this.output_events_port = new Array();
- this.output_events_om = new Array();
- this.has_stepped = true;
- this.transitions = 0;
- };
- BigStepState.prototype.next = function(input_events) {
- this.input_events = input_events;
- this.output_events_port = new Array();
- this.output_events_om = new Array();
- this.has_stepped = false;
- this.transitions = 0;
- };
- BigStepState.prototype.getInputEvents = function() {
- return this.input_events;
- };
- BigStepState.prototype.getOutputEvents = function() {
- return this.output_events_port;
- };
- BigStepState.prototype.getOutputEventsOM = function() {
- return this.output_events_om;
- };
- BigStepState.prototype.outputEvent = function(event) {
- this.output_events_port.push(event);
- };
- BigStepState.prototype.outputEventOM = function(event) {
- this.output_events_om.push(event);
- };
- BigStepState.prototype.setStepped = function() {
- this.has_stepped = true;
- };
- BigStepState.prototype.hasStepped = function() {
- return this.has_stepped;
- };
- // ComboStepState
- var ComboStepState = function() {
- this.current_events = new Array();
- this.next_events = new Array();
- this.changed = new Array();
- this.has_stepped = true;
- };
- ComboStepState.prototype.reset = function() {
- this.current_events = new Array();
- this.next_events = new Array();
- };
- ComboStepState.prototype.next = function() {
- this.current_events = this.next_events;
- this.next_events = new Array();
- this.changed = new Array();
- this.has_stepped = false;
- };
- ComboStepState.prototype.addNextEvent = function(event) {
- this.next_events.push(event);
- };
- ComboStepState.prototype.getCurrentEvents = function(event) {
- return this.current_events;
- };
- ComboStepState.prototype.setArenaChanged = function(arena) {
- this.changed.push(arena);
- };
- ComboStepState.prototype.isArenaChanged = function(arena) {
- return (this.changed.indexOf(arena) !== -1);
- };
- ComboStepState.prototype.isStable = function() {
- return (this.changed.length === 0);
- };
- ComboStepState.prototype.setStepped = function() {
- this.has_stepped = true;
- };
- ComboStepState.prototype.hasStepped = function() {
- return this.has_stepped;
- };
- // SmallStepState
- var SmallStepState = function() {
- this.current_events = new Array();
- this.next_events = new Array();
- this.candidates = new Array();
- this.has_stepped = true;
- };
- SmallStepState.prototype.reset = function() {
- this.current_events = new Array();
- this.next_events = new Array();
- };
- SmallStepState.prototype.next = function() {
- this.current_events = this.next_events;
- this.next_events = new Array();
- this.candidates = new Array();
- this.has_stepped = false;
- };
- SmallStepState.prototype.addNextEvent = function(event) {
- this.next_events.push(event);
- };
- SmallStepState.prototype.getCurrentEvents = function() {
- return this.current_events;
- };
- SmallStepState.prototype.addCandidate = function(t, p) {
- this.candidates.push({transition: t, parameters: p});
- };
- SmallStepState.prototype.getCandidates = function() {
- return this.candidates;
- };
- SmallStepState.prototype.hasCandidates = function() {
- return (this.candidates.length > 0);
- };
- SmallStepState.prototype.setStepped = function() {
- this.has_stepped = true;
- };
- SmallStepState.prototype.hasStepped = function() {
- return this.has_stepped;
- };
|