Today I reworked the future events mechanism to streamline it in a big way. I knew it needed this and the essential insight about how to do it occurred to me in the shower about ten days ago. I decided to do this now because I was going cross-eyed looking at the connection logic, which itself needs one more extension.
The main insight involved the need to recycle the reference to the most recent future event item or to create a new future event item. New items are generated as needed and placed in the future events queue. Items are then pulled off the queue in time order and processed one by one. The items are stored in the feqCurrent
variable, which can be updated and recycled back into the queue. The variable should then be set to null
, which signals that a new event item will have to be created so it can be placed into the future events queue.
I also created standalone functions to process the reuse or generation of new future event items. The advance
function defines and event that takes place by a specified offset from the current time, whenever that is. That’s the most commonly used function and models a fixed delay, process time, or event duration in general. The jump
function works the same way except that the absolute time (from time zero, the beginning of the simulation) is used. This function is more likely to be used to specify a schedule of known events as the simulation is initialized.
The code is shown below in its now much abbreviated form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
//future events queue item function FutureEventItem(time,entity,nextState,entity2) { this.activationTime = 0.0; this.entity = entity; this.nextState = nextState; this.entity2 = entity2; if (time >= globalSimClock) { this.activationTime = time; //activate at specified absolute time, if in future } else { displayProgressText("Invalid time past time supplied A. Element ID: "+entity.entityID+" Current Time: "+globalSimClock+" Specified Time: "+time.toFixed(6)); //alert on invalid time } this.getActivationTime = function() { return this.activationTime; }; this.getEntity = function() { return this.entity; }; this.getNextState = function() { return this.nextState; }; this.getEntity2 = function() { return this.entity2; }; this.update = function(time,entity,nextState,entity2) { this.activationTime = time; this.entity = entity; this.nextState = nextState; this.entity2 = entity2; }; } //FutureEventItem var feqCurrent; //global feqItem function FutureEventsQueue() { this.feq = []; this.feqSize = 0; this.insertTime = 0.0; this.getFirstItem = function() { var feqItem = this.feq.splice(0,1); if (feqItem.length) { var t = feqItem[0].getActivationTime(); updateSimClock(t); this.feqSize--; return feqItem; } else { //alert("no items in FEQ to retrieve "+this.feqSize); //flag end of sim } }; this.findLaterTime = function(item) { return item.getActivationTime() > globalInsertTime; }; this.insertItem = function(time,type,entity,nextState,entity2) { if (type == "advance") { time += globalSimClock; } if (feqCurrent) { feqCurrent.update(time,entity,nextState,entity2); } else { feqCurrent = new FutureEventItem(time,entity,nextState,entity2); } globalInsertTime = time; //always uses absolute time if (this.feqSize == 0) { this.feq[0] = feqCurrent; this.feqSize++; } else { //find index of feq item to insert before //findIndex is a native JavaScript method of the Array object var insertIndex = this.feq.findIndex(this.findLaterTime); //insert the element if (insertIndex < 0) { insertIndex = this.feq.length; } this.feq.splice(insertIndex,0,feqCurrent); this.feqSize++; } feqCurrent = null; }; } //FutureEventsQueue //initialize future events queue var feq = new FutureEventsQueue(); function advance(byTime,entity,nextState,entity2) { feq.insertItem(byTime,"advance",entity,nextState,entity2); } //advance function jump(toTime,entity,nextState,entity2) { feq.insertItem(toTime,"absolute",entity,nextState,entity2); } //jump |
Here are a couple of examples of the advance
function in use. The second example shows the extra parameter being used to reference an entity that needs to be processed within a component.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
this.receiveEntity = function(entity) { //EntryComponent //display what's going to happen this.entryTime = globalSimClock; this.entryEntityID = entity.entityID; this.activity = "receive entity"; displayProgressText("Entry comp. "+this.componentID+" injects entity: "+entity.entityID+" at time "+globalSimClock.toFixed(6)); //set timer to clear the display after a bit this.endEntryDisplayTime = globalSimClock+this.displayDelay; this.entryCount++; advance(this.displayDelay,this,"clearEntryDisplay"); //send it someplace this.forwardEntity(entity); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
this.receiveEntity = function(entity) { //PathComponent //receive the entity entity.setLocalEntryTime(); //record time entity entered queue if (this.exclusive) { entity.setPermission(true); } entity.setLocation(this.x1,this.y1); advance(this.maxRefreshTime,this,"move",entity); this.traverseQueue.unshift(0.0); this.entityQueue.unshift(entity); this.countInQueue++; //display what was done this.entryTime = globalSimClock; this.entryEntityID = entity.entityID; displayProgressText("Path comp. "+this.componentID+" receives entity: "+this.entryEntityID+" at time "+globalSimClock.toFixed(6)); if (this.exclusive) { this.isOpen(); } }; |