123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720 |
- // 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, time_offset) {
- this.event = event;
- 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, time_offset) {
- var entry = new EventQueueEntry(event, 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() {
- if (this.isEmpty() || this.event_list[0].time_offset > 0.0) {
- return new Array();
- }
- var index = 0;
- while (index < this.event_list.length &&
- this.event_list[index].time_offset <= 0.0)
- {
- index++;
- }
- return this.event_list.splice(0, index);
- };
- // 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.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.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].event);
- }
- };
- 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();
- if (instances[i].instance.destructor)
- instances[i].instance.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, keep_running, finished_callback) {
- this.object_manager = object_manager;
- this.keep_running = keep_running;
- this.finished_callback = finished_callback;
- 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, time_offset) {
- if (input_event.name === "") {
- throw new InputException("Input event can't have an empty name.");
- }
- var input_port = this.input_ports[input_event.port];
- if (input_port === undefined) {
- throw new InputException("Input port mismatch.");
- }
- this.input_queue.add(input_event, time_offset);
- };
- 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, keep_running, finished_callback) {
- ControllerBase.call(this, object_manager, keep_running, finished_callback);
- }
- GameLoopControllerBase.prototype = new ControllerBase();
- GameLoopControllerBase.prototype.update = function(delta) {
- this.input_queue.decreaseTime(delta);
- var due = this.input_queue.popDueEvents();
- for (var e in due) {
- if (!due.hasOwnProperty(e)) continue;
- this.broadcast(due[e].event);
- }
- this.object_manager.stepAll(delta);
- };
- function TimeoutId(id, delay) {
- this.id = id;
- this.delay = delay;
- }
- // JsEventLoopControllerBase
- function JsEventLoopControllerBase(object_manager, keep_running, finished_callback) {
- ControllerBase.call(this, object_manager, keep_running, finished_callback);
- this.running = false;
- this.next_timeout = null;
- this.last_simulation_time = null;
- }
- JsEventLoopControllerBase.prototype = new ControllerBase();
- JsEventLoopControllerBase.prototype.handleInput = function(delta) {
- this.input_queue.decreaseTime(delta);
- var due = this.input_queue.popDueEvents();
- for (var e in due) {
- if (!due.hasOwnProperty(e)) continue;
- var input_port = this.input_ports[due[e].event.port];
- // rename input port
- due[e].event.port = input_port.virtual_name;
- var target_instance = input_port.instance;
- if (target_instance === undefined) {
- this.broadcast(due[e].event);
- } else {
- target_instance.addEvent(due[e].event);
- }
- }
- };
- JsEventLoopControllerBase.prototype.addInput = function(input_event, time_offset) {
- if (this.last_simulation_time && this.next_timeout) {
- var waited = (new Date).getTime() - this.last_simulation_time;
- var remaining = this.next_timeout.delay - waited;
- } else {
- var waited = 0.0;
- var remaining = 0.0;
- }
- var interleave = time_offset < remaining;
- if (this.next_timeout) {
- var additional_offset = waited;
- } else {
- var additional_offset = 0.0;
- }
- ControllerBase.prototype.addInput.call(this, input_event, time_offset + additional_offset);
- if (this.running && (interleave || !this.next_timeout)) {
- this.run(); // adjust timeout
- }
- };
- JsEventLoopControllerBase.prototype.start = function() {
- ControllerBase.prototype.start.call(this);
- this.running = true;
- this.run();
- };
- JsEventLoopControllerBase.prototype.stop = function() {
- this.run(); // update timeouts
- if (this.next_timeout) {
- window.clearTimeout(this.next_timeout.id);
- }
- this.running = false;
- ControllerBase.prototype.stop.call(this);
- };
- JsEventLoopControllerBase.prototype.getWaitTime = function() {
- var wait_time = Math.min(this.object_manager.getWaitTime(), this.input_queue.getEarliestTime());
- return wait_time;
- };
- JsEventLoopControllerBase.prototype.run = function() {
- // clear previous timeout
- if (this.next_timeout) {
- window.clearTimeout(this.next_timeout.id);
- this.next_timeout = null;
- }
- // calculate last time since simulation
- if (this.last_simulation_time) {
- var simulation_duration = (new Date).getTime() - this.last_simulation_time;
- } else {
- var simulation_duration = 0.0;
- }
- // simulate
- this.handleInput(simulation_duration);
- this.object_manager.stepAll(simulation_duration);
- // keep time
- this.last_simulation_time = (new Date).getTime();
- // set next timeout
- var wait_time = this.getWaitTime();
- if (wait_time !== Infinity) {
- var actual_wait_time = wait_time - ((new Date).getTime() - this.last_simulation_time);
- if (actual_wait_time < 0.0)
- actual_wait_time = 0.0;
- // wait actual_wait_time
- //console.log("waiting " + actual_wait_time + " ms");
- this.next_timeout = new TimeoutId(window.setTimeout(this.run.bind(this), actual_wait_time), actual_wait_time);
- } else {
- // wait forever
- //console.log("waiting forever");
- this.last_simulation_time = null;
- 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();
- };
- // RuntimeClassBase
- function RuntimeClassBase() {
- this.active = false;
- this.state_changed = false;
- this.events = new EventQueue();
- this.timers = null;
- }
- RuntimeClassBase.prototype.addEvent = function(event, time_offset) {
- if (!time_offset) time_offset = 0.0;
- this.events.add(event, time_offset);
- };
- RuntimeClassBase.prototype.getEarliestEventTime = function() {
- if (this.timers) {
- var minimum = Infinity;
- for (var t in this.timers) {
- if (!this.timers.hasOwnProperty(t)) continue;
- minimum = Math.min(minimum, this.timers[t]);
- }
- return Math.min(this.events.getEarliestTime(), minimum);
- }
- return this.events.getEarliestTime();
- };
- RuntimeClassBase.prototype.step = function(delta) {
- if (!this.active) {
- return;
- }
- this.events.decreaseTime(delta);
- if (this.timers) {
- 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;
- }
- this.microstep();
- while (this.state_changed) {
- this.microstep();
- }
- };
- RuntimeClassBase.prototype.microstep = function() {
- var due = this.events.popDueEvents();
- if (due.length === 0) {
- this.transition();
- } else {
- for (var e in due) {
- if (!due.hasOwnProperty(e)) continue;
- this.transition(due[e].event);
- }
- }
- };
- RuntimeClassBase.prototype.transition = function(event) {
- // pure virtual
- };
- RuntimeClassBase.prototype.start = function() {
- this.active = true;
- };
- RuntimeClassBase.prototype.stop = function() {
- this.active = false;
- };
|