Following yesterday’s work I had three tasks to complete. The first task was to fix the reporting for cases where a Process or Bag component is not the last item in a component group. The fix was to only run the reporting process for exiting a group when the entity leaving a Process or Bag component is going to a different non-Path component that’s in a different group. This involved embedding the report function at the end of the forwardEntity
function in the if
statement shown below.
1 2 3 4 |
//record stats if (this.nextComponentList[dest].getComponentGroup() != entity.getComponentGroup()) { //truly leaving a component group along the current connection (specified by dest) recordGroupStatsWrapper(this.componentGroup, entity.getComponentGroupEntryTime(), entity); } |
The getComponentGroup
function in Path components had to be modified so it actually returns the component group of its next component, so it always returns the component group of the next non-Path component.
The second task was to experiment with different ways of specifying the process time for certain components in a modular way. I originally passed in an array of values and called a fixed, external function to determine the array index to use base on the relevant entity’s user-defined properties. Later I added an additional index to the process so different Process or Bag components could generate lookup indices based on different properties or combinations of properties. I then realized that I could simply pass a custom function in for each case. The code for one way of doing this is here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function processTimeBag1(entity) { var processTime; if (entity.getPropertyValue("Residency") == "Citizen") { processTime = 30; } else if (entity.getPropertyValue("Residency") == "LPR") { processTime = 50; } else if (entity.getPropertyValue("Residency") == "Visitor") { processTime = 80; } return processTime; } var routingTableB1 = [[1.0],[1.0],[1.0]]; var bag1 = new BagComponent(processTimeBag1, 12, routingTableB1); ... var pTime = processTime(entity); //processTime is the internal name for the processTimeBag1 parameter advance(pTime, this, "processComplete",entity); |
If we want to retain some additional form of external modularity of parameters we could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var globalProcessTimeSettings = [[5,7,9],[30,50,80]]; function processTimeBag1(entity) { var processTime; var globalIndex = 1; if (entity.getPropertyValue("Residency") == "Citizen") { processTime = globalProcessTimeSettings[globalIndex][0]; } else if (entity.getPropertyValue("Residency") == "LPR") { processTime = globalProcessTimeSettings[globalIndex][1]; } else if (entity.getPropertyValue("Residency") == "Visitor") { processTime = globalProcessTimeSettings[globalIndex][2]; } return processTime; } var routingTableB1 = [[1.0],[1.0],[1.0]]; var bag1 = new BagComponent(processTimeBag1, 12, routingTableB1); ... var pTime = processTime(entity); //processTime is the internal name for the processTimeBag1 parameter advance(pTime, this, "processComplete",entity); |
The possibilities are many.
The third and final task was to allow the simulation to run until it is empty if a specific flag is set. I defined a global variable called runToEmptyFlag
and modified two tests that control when to stop the simulation and when to stop recording data. The logic says to run to at least the minimum time and then stop if runToEmptyFlag
is false, or keep going until there are no entities left in the system if runToEmptyFlag
is true. Today’s version of the model has the flag set to true, so it will run beyond 630 time units as long as there are entities still in the system. If the first run doesn’t run past that time because it clears out then reset and rerun it until you see the desired behavior. The runtimes can vary quite a bit depending on how the type and routing dice fall.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
this.increment = function() { //function in the DisplayControlComponent object //update the displays displayControlUpdate = true; //this.displayEnd is set by the global value for when to stop running, globalEndTime if ((globalSimClock >= this.displayEnd) && (!runToEmptyFlag || (setOfEntities.length == 0))) { keepRunning = false; //tells the main event loop to stop pulling events, if any are avialable } else { //calculate next update time var nextTime = this.displayIncrement * this.displayMultiplier; while ((this.displayClock + nextTime) < globalSimClock) { nextTime += this.displayIncrement * this.displayMultiplier; } this.displayClock += nextTime; advance(nextTime,this,"increment"); //currently set to run in 30-unit time intervals } }; |
1 2 3 4 5 6 7 8 9 10 |
this.increment = function() { //function in the StatsTimerComponent object incrementStatsTimeIndexWrapper(); //this.timeEnd is set by the global value for when to stop running, globalEndTime if ((globalSimClock < this.timeEnd) || (!runToEmptyFlag) || ((globalSimClock >= this.timeEnd) && runToEmptyFlag && (setOfEntities.length > 0)) ) { advance(statsInterval, this, "increment"); //currently set to run in 30-unit time intervals } }; |
I noticed that the final time increment would not be reported properly if the systems runs to empty beyond its allotted time. The fix was to add in an extra reporting increment if the global clock is greater than the number of reporting cycles run times the number of time units per cycle.
1 2 3 4 5 6 7 8 9 10 |
function displayEndMessage() { globalCTX.font = "12px Arial"; globalCTX.fillStyle = "#FF00FF"; globalCTX.textAlign = "left"; globalCTX.fillText("Simulation has ended", 10.0, canvas.height - 2); if (globalSimClock > (statsTimeIndex * statsUpdateInterval)) { //over allotted time test and increment added here incrementStatsTimeIndexWrapper(); } reportStatsWrapper(); } //displayEndMessage |