12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337 |
- __next_objid=1;
- function objectId(obj) {
- if (obj==null) return null;
- if (obj.__obj_id==null) obj.__obj_id=__next_objid++;
- return obj.__obj_id;
- }
- start_time = undefined
- function time() {
- return (new Date).getTime() - start_time;
- }
- // 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(scheduled_time, the_event) {
- this.scheduled_time = scheduled_time;
- this.the_event = the_event;
- }
- // EventQueue
- function EventQueue() {
- this.event_list = new Array();
- this.sort_function = function(a, b) {
- return a.scheduled_time - b.scheduled_time
- }
- }
- 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].scheduled_time;
- }
- };
- EventQueue.prototype.add = function(the_event) {
- this.event_list.push(the_event)
- this.event_list.sort(this.sort_function)
- return objectId(the_event)
- };
- EventQueue.prototype.remove = function(event_id) {
- this.event_list = this.event_list.filter(function(el) {objectId(el) != event_id}).sort()
- }
- EventQueue.prototype.pop = function() {
- return this.event_list.shift().the_event
- };
- // 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();
- this.regex = /^([a-zA-Z_]\w*)(?:\[(\d+)\])?$/;
- }
- ObjectManagerBase.prototype.addEvent = function(the_event, time_offset) {
- if (time_offset === undefined) time_offset = 0;
- this.events.add(new EventQueueEntry(this.controller.simulated_time + time_offset, the_event));
- };
- ObjectManagerBase.prototype.broadcast = function(source, new_event, time_offset) {
- if (time_offset === undefined) time_offset = 0;
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i) || i == source) continue;
- this.instances[i].addEvent(new_event, time_offset);
- }
- };
- ObjectManagerBase.prototype.getEarliestEventTime = function() {
- var earliest_time = this.events.getEarliestTime();
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- earliest_time = Math.min(earliest_time, this.instances[i].earliest_event_time);
- }
- return earliest_time;
- };
- ObjectManagerBase.prototype.stepAll = function() {
- this.step();
- for (var i in this.instances) {
- if (!this.instances.hasOwnProperty(i)) continue;
- var instance = this.instances[i];
- if (instance.active && (instance.getEarliestEventTime() <= this.controller.simulated_time || instance.eventlessTransitions())) {
- instance.step();
- }
- }
- };
- ObjectManagerBase.prototype.step = function() {
- while (this.events.getEarliestTime() <= this.controller.simulated_time) {
- this.handleEvent(this.events.pop());
- }
- };
- 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 === "disassociate_instance") {
- this.handleDisassociateEvent(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.length == 0) {
- throw new AssociationReferenceException("Empty association reference.");
- }
- 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 = this.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();
- }
- source.addEvent(new Event("instance_started", undefined, [parameters[1]]))
- };
- ObjectManagerBase.prototype.handleBroadcastEvent = function(parameters) {
- if (parameters.length !== 2) {
- throw new ParameterException("The broadcast event needs 2 parameters (source of event and event name).");
- }
- this.broadcast(parameters[0], parameters[1]);
- };
- 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.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.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);
- }
- source.addEvent(new Event("instance_associated", undefined, [parameters[1], parameters[2]]))
- };
- ObjectManagerBase.prototype.handleDisassociateEvent = function(parameters) {
- if (parameters.length !== 2) {
- throw new ParameterException("The disassociate 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;
- // delete association from source instance
- var association_to_remove = instances[i].ref.associations[instances[i].assoc_name];
- if (instances[i].assoc_index === -1) {
- // empty instances object
- association_to_remove.instances = new Object();
- } else {
- // remove property from instances object
- delete association_to_remove.instances[instances[i].assoc_index];
- }
- }
- source.addEvent(new Event("instance_disassociated", undefined, [parameters[1]]));
- };
- 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) {
- if (port === undefined) port = "";
- if (parameters === undefined) parameters = [];
- this.name = name;
- this.port = port;
- this.parameters = parameters;
- }
- // OutputListener
- function OutputListener(port_names) {
- this.port_names = port_names;
- this.queue = new Array(); // TODO: optimize!
- }
- OutputListener.prototype.add = function(the_event) {
- if (this.port_names.length == 0
- || this.port_names.indexOf(the_event.port) != -1)
- {
- this.queue.push(the_event);
- }
- };
- OutputListener.prototype.fetch = function(timeout) {
- if (this.queue.length > 0) {
- return this.queue.shift();
- }
- };
- // ControllerBase
- function ControllerBase(object_manager) {
- this.object_manager = object_manager;
-
- this.private_port_counter = 0;
-
- // keep track of input ports
- this.input_ports = new Object();
- this.input_queue = new EventQueue();
-
- // keep track of output ports
- this.output_ports = new Array();
- this.output_listeners = new Array();
- this.simulated_time = null;
- }
- ControllerBase.prototype.getSimulatedTime = function() {
- return this.simulated_time
- }
- ControllerBase.prototype.addInputPort = function(virtual_name, instance) {
- if (instance === undefined) {
- var port_name = virtual_name; // "global" port
- } else {
- var port_name = "private_" + (this.private_port_counter++) + "_" + virtual_name;
- }
- this.input_ports[port_name] = {
- virtual_name: virtual_name,
- instance: instance
- };
- return port_name;
- };
- ControllerBase.prototype.addOutputPort = function(port_name) {
- this.output_ports.push(port_name);
- };
- ControllerBase.prototype.broadcast = function(new_event, time_offset) {
- if (time_offset === undefined) time_offset = 0
- this.object_manager.broadcast(undefined, new_event, time_offset);
- };
- ControllerBase.prototype.start = function() {
- start_time = (new Date()).getTime();
- this.simulated_time = 0;
- this.object_manager.start();
- };
- ControllerBase.prototype.stop = function() {
- };
- ControllerBase.prototype.addInput = function(input_event_list, time_offset) {
- if (time_offset === undefined) time_offset = 0
- 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(new EventQueueEntry((this.simulated_time === null ? 0 : time()) + time_offset, input_event_list[e]));
- }
- };
- ControllerBase.prototype.getEarliestEventTime = function() {
- return Math.min(this.object_manager.getEarliestEventTime(), this.input_queue.getEarliestTime());
- };
- ControllerBase.prototype.handleInput = function() {
- var event_time = this.input_queue.getEarliestTime();
- while (event_time <= this.simulated_time) {
- var e = this.input_queue.pop();
- input_port = this.input_ports[e.port];
- e.port = input_port.virtual_name
- var target_instance = input_port.instance;
- if (target_instance === undefined) {
- this.broadcast(e, event_time - this.simulated_time);
- } else {
- target_instance.addEvent(e, event_time - this.simulated_time);
- }
- event_time = this.input_queue.getEarliestTime();
- }
- };
- ControllerBase.prototype.outputEvent = function(the_event) {
- for (var l in this.output_listeners) {
- if (!this.output_listeners.hasOwnProperty(l)) continue;
- this.output_listeners[l].add(the_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);
- };
- // GameLoopControllerBase
- function GameLoopControllerBase(object_manager) {
- ControllerBase.call(this, object_manager);
- }
- GameLoopControllerBase.prototype = new ControllerBase();
- GameLoopControllerBase.prototype.update = function(delta) {
- this.handleInput();
- earliest_event_time = this.getEarliestEventTime();
- if (earliest_event_time > time()) {
- this.simulated_time = earliest_event_time;
- this.object_manager.stepAll();
- }
- };
- // 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_print = 0.0;
- }
- EventLoop.prototype.schedule = function(f, wait_time, behind) {
- if (behind === undefined) behind = false
- 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.scheduled_id = this.schedule_callback(f, wait_time, behind)
- }
- };
- EventLoop.prototype.clear = function() {
- if (this.scheduled_id) {
- this.clear_callback(this.scheduled_id);
- this.scheduled_id = null;
- }
- };
- // EventLoopControllerBase
- function EventLoopControllerBase(object_manager, event_loop, finished_callback, behind_schedule_callback) {
- ControllerBase.call(this, object_manager);
- this.event_loop = event_loop;
- this.finished_callback = finished_callback;
- this.behind_schedule_callback = behind_schedule_callback;
- this.last_print_time = 0;
- }
- EventLoopControllerBase.prototype = new ControllerBase();
- EventLoopControllerBase.prototype.addInput = function(input_event, time_offset) {
- ControllerBase.prototype.addInput.call(this, input_event, time_offset);
- this.event_loop.clear();
- this.simulated_time = ControllerBase.prototype.getEarliestEventTime.call(this);
- this.run();
- };
- 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() {
- var start_time = time();
- while (true) {
- // clear existing timeout
- this.event_loop.clear();
- // simulate
- this.handleInput();
- this.object_manager.stepAll();
- // schedule next timeout
- var earliest_event_time = ControllerBase.prototype.getEarliestEventTime.call(this);
- if (earliest_event_time == Infinity) {
- if (this.finished_callback != undefined) this.finished_callback(); // TODO: This is not necessarily correct (keep_running necessary?)
- return;
- }
- var now = time();
- if (now - start_time > 10 || earliest_event_time - now > 0) {
- this.event_loop.schedule(this.run.bind(this), earliest_event_time - now, now - start_time > 10);
- if (now - earliest_event_time > 10 && now - this.last_print_time >= 1000) {
- console.log('running ' + (now - earliest_event_time) + ' ms behind schedule');
- this.last_print_time = now;
- }
- this.simulated_time = earliest_event_time;
- return;
- } else {
- this.simulated_time = earliest_event_time;
- }
- }
- };
- // JsEventLoop
- function JsEventLoop() {
- EventLoop.call(this, window.setTimeout.bind(window), window.clearTimeout.bind(window));
- }
- JsEventLoop.prototype = new EventLoop();
- // 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;
- };
- // State
- function State(state_id, name, obj) {
- this.state_id = state_id;
- this.name = name;
- this.obj = obj;
-
- this.ancestors = new Array();
- this.descendants = new Array();
- this.children = new Array();
- this.my_parent = null;
- this.enter = null;
- this.exit = null;
- this.default_state = null;
- this.transitions = new Array();
- this.my_history = new Array();
- this.has_eventless_transitions = false;
- }
- State.prototype.getEffectiveTargetStates = function() {
- var targets = [this];
- if (this.default_state != null) {
- Array.prototype.push.apply(targets, this.default_state.getEffectiveTargetStates());
- }
- return targets;
- }
- State.prototype.fixTree = function() {
- for (let c of this.children) {
- if (c instanceof HistoryState) {
- this.my_history.push(c);
- }
- c.my_parent = this;
- c.ancestors.push(this);
- Array.prototype.push.apply(c.ancestors, this.ancestors);
- c.fixTree();
- }
- Array.prototype.push.apply(this.descendants, this.children)
- for (let c of this.children) {
- Array.prototype.push.apply(this.descendants, c.descendants)
- }
- }
- State.prototype.addChild = function(child) {
- this.children.push(child);
- }
- State.prototype.addTransition = function(transition) {
- this.transitions.push(transition);
- }
- State.prototype.setEnter = function(enter) {
- this.enter = enter.bind(this.obj);
- }
- State.prototype.setExit = function(exit) {
- this.exit = exit.bind(this.obj);
- }
- // HistoryState
- function HistoryState(state_id, name, obj) {
- State.call(this, state_id, name, obj);
- }
- HistoryState.prototype = new State();
- // ShallowHistoryState
- function ShallowHistoryState(state_id, name, obj) {
- HistoryState.call(this, state_id, name, obj);
- }
- ShallowHistoryState.prototype = new HistoryState();
- ShallowHistoryState.prototype.getEffectiveTargetStates = function() {
- if (this.state_id in this.obj.history_values) {
- var targets = [];
- for (let hv of this.obj.history_values[this.state_id]) {
- Array.prototype.push.apply(targets, hv.getEffectiveTargetStates());
- }
- return targets;
- } else {
- // TODO: is it correct that in this case, the parent itself is also entered?
- return this.my_parent.getEffectiveTargetStates();
- }
- }
- // DeepHistoryState
- function DeepHistoryState(state_id, name, obj) {
- HistoryState.call(this, state_id, name, obj);
- }
- DeepHistoryState.prototype = new HistoryState();
- DeepHistoryState.prototype.getEffectiveTargetStates = function() {
- if (this.state_id in this.obj.history_values) {
- return this.obj.history_values[this.state_id];
- } else {
- // TODO: is it correct that in this case, the parent itself is also entered?
- return this.my_parent.getEffectiveTargetStates();
- }
- }
- // ParallelState
- function ParallelState(state_id, obj) {
- State.call(this, state_id, obj);
- }
- ParallelState.prototype = new State();
- ParallelState.prototype.getEffectiveTargetStates = function() {
- var targets = [this];
- for (let c of this.children) {
- if (!(c instanceof HistoryState)) {
- Array.prototype.push.apply(targets, c.getEffectiveTargetStates());
- }
- }
- return targets;
- }
- // Transition
- function Transition(obj, source, targets) {
- this.guard = null;
- this.action = null;
- this.trigger = null;
- this.source = source;
- this.targets = targets;
- this.obj = obj;
- this.enabled_event = null; // the event that enabled this transition
- this.optimize();
- }
- Transition.prototype.isEnabled = function(events) {
- if (this.trigger === null) {
- this.enabled_event = null;
- return (this.guard === null || this.guard([]));
- } else {
- for (var i in events) {
- the_event = events[i];
- if ((this.trigger.name == the_event.name && (!this.trigger.port || this.trigger.port == the_event.port)) && (this.guard === null || this.guard(the_event.parameters))) {
- this.enabled_event = the_event;
- return true;
- }
- }
- }
- }
- Transition.prototype.fire = function() {
- // exit states...
- var targets = this.__getEffectiveTargetStates();
- var exit_set = this.__exitSet(targets);
- for (let s of exit_set) {
- for (let h of s.my_history) {
- var f = function(s0) {return s0.ancestors.length > 0 && s0.my_parent == s;}
- if (h instanceof DeepHistoryState) {
- f = function(s0) {return s0.descendants.length == 0 && s.descendants.indexOf(s0) >= 0;}
- }
- this.obj.history_values[h.state_id] = this.obj.configuration.filter(f);
- }
- }
- for (let s of exit_set) {
- if (s.exit != null) {
- s.exit();
- }
- }
- this.obj.configuration = this.obj.configuration.filter(function(el) {return exit_set.indexOf(el) < 0;})
-
- // combo state changed area
- this.obj.combo_step.changed.push(this.lca);
- for (let d of this.lca.descendants) {
- this.obj.combo_step.changed.push(d);
- }
-
- // execute transition action(s)
- if (this.action != null) {
- var params = [];
- if (this.enabled_event != null) params = this.enabled_event.parameters;
- this.action(params);
- }
-
- // enter states...
- for (let s of this.__enterSet(targets)) {
- this.obj.configuration.push(s);
- if (s.enter != null) {
- s.enter();
- }
- }
-
- this.obj.configuration = this.obj.configuration.sort(function(a, b) {return a.state_id - b.state_id})
- this.enabled_event = null;
- }
- Transition.prototype.__getEffectiveTargetStates = function() {
- var targets = []
- for (let target of this.targets) {
- for (let e_t of target.getEffectiveTargetStates()) {
- if (targets.indexOf(e_t) < 0) {
- targets.push(e_t);
- }
- }
- }
- return targets;
- }
- Transition.prototype.__exitSet = function(targets) {
- var exit_set = this.lca.descendants.slice(0).filter(function(obj) {
- return function(s) {
- return obj.obj.configuration.indexOf(s) >= 0;
- }
- }(this));
- exit_set.reverse();
- return exit_set;
- }
- Transition.prototype.__enterSet = function*(targets) {
- var target = targets[0];
- var reversed_ancestors = target.ancestors.slice(0).reverse();
- for (let a of reversed_ancestors) {
- if (this.source.ancestors.indexOf(a) >= 0) {
- continue;
- } else {
- yield a;
- }
- }
- for (let target of targets) {
- yield target;
- }
- }
- Transition.prototype.setGuard = function(guard) {
- this.guard = guard.bind(this.obj);
- }
- Transition.prototype.setAction = function(action) {
- this.action = action.bind(this.obj);
- }
- Transition.prototype.setTrigger = function(trigger) {
- this.trigger = trigger;
- if (this.trigger === null) {
- this.source.has_eventless_transitions = true;
- }
- }
- Transition.prototype.optimize = function() {
- // the least-common ancestor can be computed statically
- this.lca = this.source.my_parent;
- var target = this.targets[0];
- if (this.source.my_parent != target.my_parent) { // external
- for (let a of this.source.ancestors) {
- if (target.ancestors.indexOf(a) >= 0) {
- this.lca = a;
- break;
- }
- }
- }
- }
- // RuntimeClassBase
- function RuntimeClassBase(controller) {
- this.active = false;
- this.__set_stable(true);
- this.events = new EventQueue();
- this.controller = controller;
- this.inports = new Object();
- this.timers = new Object();
- this.states = new Object();
- this.semantics = new DefaultStatechartSemantics();
- }
- RuntimeClassBase.prototype.eventlessTransitions = function() {
- for (let s of this.configuration) {
- if (s.has_eventless_transitions) return true;
- }
- return false;
- }
- RuntimeClassBase.prototype.start = function() {
- this.configuration = new Array();
-
- this.current_state = new Object();
- this.history_values = new Object();
- this.timers = new Object();
- this.timers_to_add = new Object();
- this.big_step = new BigStepState();
- this.combo_step = new ComboStepState();
- this.small_step = new SmallStepState();
- this.active = true;
- this.__set_stable(false);
- this.initializeStatechart();
- this.processBigStepOutput();
- };
- RuntimeClassBase.prototype.sccd_yield = function() {
- return Math.max(0, (time() - this.controller.simulated_time) / 1000.0);
- }
- RuntimeClassBase.prototype.getSimulatedTime = function() {
- return this.controller.simulated_time;
- }
- RuntimeClassBase.prototype.updateConfiguration = function(states) {
- this.configuration = states.slice(0);
- };
- RuntimeClassBase.prototype.stop = function() {
- this.active = false;
- this.__set_stable(false);
- };
- RuntimeClassBase.prototype.addTimer = function(index, timeout) {
- this.timers_to_add[index] = new EventQueueEntry(this.controller.simulated_time + Math.trunc(timeout * 1000), new Event("_" + index + "after"));
- };
- RuntimeClassBase.prototype.removeTimer = function(index) {
- if (index in this.timers_to_add) delete this.timers_to_add[index];
- this.events.remove(this.timers[index]);
- delete this.timers[index];
- };
- RuntimeClassBase.prototype.addEvent = function(event_list, time_offset) {
- if (time_offset == undefined) time_offset = 0;
- var event_time = this.controller.simulated_time + time_offset;
- if (event_time < this.earliest_event_time) {
- this.earliest_event_time = event_time;
- }
- if (!(event_list instanceof Array)) {
- event_list = [event_list];
- }
- for (i in event_list) {
- if (!event_list.hasOwnProperty(i)) continue;
- this.events.add(new EventQueueEntry(this.controller.simulated_time + time_offset, event_list[i]));
- }
- };
- RuntimeClassBase.prototype.getEarliestEventTime = function() {
- return this.earliest_event_time;
- };
- RuntimeClassBase.prototype.processBigStepOutput = function() {
- var o = this.big_step.output_events_port;
- var om = this.big_step.output_events_om;
- 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.__set_stable = function(is_stable) {
- this.is_stable = is_stable;
- // self.earliest_event_time keeps track of the earliest time this instance will execute a transition
- if (!this.is_stable) {
- this.earliest_event_time = 0.0;
- } else if (!this.active) {
- this.earliest_event_time = Infinity;
- } else {
- this.earliest_event_time = this.events.getEarliestTime();
- }
- }
- RuntimeClassBase.prototype.step = function(delta) {
- var is_stable = false;
- while (!is_stable) {
- var due = [];
- if (this.events.getEarliestTime() <= this.controller.simulated_time) {
- due = [this.events.pop()];
- }
- is_stable = !this.bigStep(due);
- this.processBigStepOutput();
- }
- for (key in this.timers_to_add) {
- if (!this.timers_to_add.hasOwnProperty(key)) continue;
- this.timers[key] = this.events.add(this.timers_to_add[key]);
- }
- this.timers_to_add = new Object();
- this.__set_stable(true);
- };
- RuntimeClassBase.prototype.inState = function(state_strings) {
- var state_ids = [];
- for (let state_string of state_strings) {
- state_ids.push(this.states[state_string].state_id);
- }
- for (let state_id of state_ids) {
- var found = false;
- for (let s of this.configuration) {
- if (s.state_id == state_id) {
- found = true;
- break;
- }
- }
- if (!found) return false;
- }
- return true;
- };
- RuntimeClassBase.prototype.bigStep = function(input_events) {
- this.big_step.next(input_events);
- this.small_step.reset();
- this.combo_step.reset();
- while (this.comboStep()) {
- this.big_step.has_stepped = true;
- if (this.semantics.big_step_maximality === StatechartSemantics.TakeOne)
- break;
- }
- return this.big_step.has_stepped;
- };
- RuntimeClassBase.prototype.comboStep = function() {
- this.combo_step.next();
- while (this.smallStep()) {
- this.combo_step.has_stepped = true;
- }
- return this.combo_step.has_stepped;
- };
- // generate transition candidates for current small step
- RuntimeClassBase.prototype.generateCandidates = function() {
- var enabledEvents = this.getEnabledEvents();
- var enabledTransitions = new Array();
- for (let s of this.configuration) {
- if (this.combo_step.changed.indexOf(s) < 0) {
- for (let t of s.transitions) {
- if (t.isEnabled(enabledEvents)) {
- enabledTransitions.push(t);
- }
- }
- }
- }
- return enabledTransitions
- };
- RuntimeClassBase.prototype.smallStep = function() {
- var __younger_than =
- function(x, y) {
- if (y.source.ancestors.indexOf(x.source) >= 0) {
- return 1;
- } else if (x.source.ancestors.indexOf(y.source) >= 0) {
- return -1;
- } else {
- throw new Exception("These items have no relation with each other.");
- }
- };
-
- if (this.small_step.has_stepped) {
- this.small_step.next();
- }
- var candidates = this.generateCandidates();
- if (candidates.length > 0) {
- to_skip = new Set();
- conflicting = new Array();
- for (var i in candidates) {
- c1 = candidates[i];
- if (!to_skip.has(c1)) {
- conflict = [c1];
- for (var j in candidates.slice(candidates.indexOf(c1))) {
- c2 = candidates[j];
- if (c1.source.ancestors.indexOf(c2.source) >= 0 || c2.source.ancestors.indexOf(c1.source) >= 0) {
- conflict.push(c2);
- to_skip.add(c2);
- }
- }
- conflicting.push(conflict.sort(__younger_than));
- }
- }
- if (this.semantics.concurrency === StatechartSemantics.Single) {
- var candidate = conflicting[0];
- if (this.semantics.priority === StatechartSemantics.SourceParent) {
- candidate[candidate.length - 1].fire();
- } else {
- candidate[0].fire();
- }
- } else if (this.semantics.concurrency === StatechartSemantics.Many) {
- // TODO: Implement
- }
- this.small_step.has_stepped = true;
- }
- return this.small_step.has_stepped;
- };
- RuntimeClassBase.prototype.getEnabledEvents = function() {
- var result = this.small_step.current_events.concat(this.combo_step.current_events);
- if (this.semantics.input_event_lifeline === StatechartSemantics.Whole ||
- (!this.big_step.has_stepped &&
- (this.semantics.input_event_lifeline === StatechartSemantics.FirstComboStep ||
- (!this.combo_step.has_stepped &&
- this.semantics.input_event_lifeline === StatechartSemantics.FirstSmallStep)))) {
- result = result.concat(this.big_step.input_events);
- }
- return result;
- };
- RuntimeClassBase.prototype.raiseInternalEvent = function(the_event) {
- if (this.semantics.internal_event_lifeline === StatechartSemantics.NextSmallStep) {
- this.small_step.addNextEvent(the_event);
- } else if (this.semantics.internal_event_lifeline === StatechartSemantics.NextComboStep) {
- this.combo_step.addNextEvent(the_event);
- } else if (this.semantics.internal_event_lifeline === StatechartSemantics.Queue) {
- this.events.add(new EventQueueEntry(0.0, the_event));
- }
- };
- RuntimeClassBase.prototype.initializeStatechart = function() {
- this.updateConfiguration(this.default_targets);
- for (let state of this.default_targets) {
- if (state.enter != null) {
- state.enter();
- }
- }
- };
- // 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.outputEvent = function(event) {
- this.output_events_port.push(event);
- };
- BigStepState.prototype.outputEventOM = function(event) {
- this.output_events_om.push(event);
- };
- // 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.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);
- };
- // 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.addCandidate = function(t, p) {
- this.candidates.push({transition: t, parameters: p});
- };
- SmallStepState.prototype.hasCandidates = function() {
- return (this.candidates.length > 0);
- };
|