I got an awful lot done over the weekend and today toward making the various behaviors for each type of component as similar as possible. This really helped me replicate the different controls and interlocks I had come up with for individual components across all of them in a consistent way. The process of continually revisiting everything also makes everything more clear over time.
I tried a new configuration to test different combinations of components and permissions. Specifically, I had a main queue that was non-exclusive feed a pair of exclusive local queues that each feed an exclusive process. The main queue is of infinite size while the local queues have a maximum capacity of three. I increased the number of entities generated and shortened up some of the queue traverse and process times, which allows us to observe the behavior of the local queues when they fill up completely. I see a couple of things I still need to fix. One is that the Path components feeding each of the local Queue components should not be flagged as exclusive unless the number of entities on the path and in the local queue are equal to the maximum capacity. I also need to review the code for the distribution logic (in this case from Queue 4 to local Queues 7 and 8). The code that calculates where the next entity is going to go gets called more often than an entity is available to be forwarded, so the distribution method does not operate as smoothly as it should.
Here’s the code that initializes the current configuration. It’s a lot of code so it won’t be long before I start trying to streamline and automate these declarations.
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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
//schedule for seven hours of arrivals in half-hour blocks //var arrivalSchedule = [0,0,1,3,4,4,5,6,3,2,1,0,0,0]; var arrivalSchedule = [0,0,1,5,6,6,7,8,5,4,3,1,0,0]; //var entryDistribution = [[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0],[0.7,1.0]]; var entryDistribution = [[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0],[1.0]]; var arrival1 = new ArrivalsComponent(30.0,arrivalSchedule,entryDistribution); arrival1.defineDataGroup(90,2,80,"#00FFFF","#FF0000","#FFFF00"); var routingTable = [1.0]; var entry1 = new EntryComponent(2.0,routingTable); entry1.defineDataGroup(90,105,80,"#00FFFF","#FF0000","#FFFF00"); entry1.setRoutingMethod(3); //1 single connection, 2 distribution logic, 3 model logic arrival1.assignNextComponent(entry1); var path0 = new PathComponent(); path0.setStartPoint(168,168); path0.setEndPoint(168,193); path0.setSpeedTime(10.0,1.0); path0.calcPathLength(); entry1.assignNextComponent(path0); path0.assignPreviousComponent(entry1); //var queue0 = new QueueComponent(2.0,9.0); var queue0 = new QueueComponent(2.0,6.0); queue0.defineDataGroup(90,193,80,"#00FFFF","#FF0000","#FFFF00"); queue0.setRoutingMethod(2); //1 single connection, 2 distribution logic, 3 model logic path0.assignNextComponent(queue0); queue0.assignPreviousComponent(path0); var path1 = new PathComponent(); path1.setStartPoint(170,304); path1.setEndPoint(95,329); path1.setSpeedTime(20,1.0); path1.calcPathLength(); queue0.assignNextComponent(path1); path1.assignPreviousComponent(queue0); var path2 = new PathComponent(); path2.setStartPoint(170,304); path2.setEndPoint(245,329); path2.setSpeedTime(20,1.0); path2.calcPathLength(); queue0.assignNextComponent(path2); path2.assignPreviousComponent(queue0); var queue1 = new QueueComponent(2.0,3.0,3); queue1.defineDataGroup(5,329,80,"#00FFFF","#FF0000","#FFFF00"); queue1.setRoutingMethod(1); //1 single connection, 2 distribution logic, 3 model logic queue1.setExclusive(true); path1.assignNextComponent(queue1); queue1.assignPreviousComponent(path1); var queue2 = new QueueComponent(2.0,3.0,3); queue2.defineDataGroup(175,329,80,"#00FFFF","#FF0000","#FFFF00"); queue2.setRoutingMethod(1); //1 single connection, 2 distribution logic, 3 model logic queue2.setExclusive(true); path2.assignNextComponent(queue2); queue2.assignPreviousComponent(path2); var path3 = new PathComponent(); path3.setStartPoint(95,440); path3.setEndPoint(95,465); path3.setSpeedTime(20,1.0); path3.calcPathLength(); queue1.assignNextComponent(path3); path3.assignPreviousComponent(queue1); var path4 = new PathComponent(); path4.setStartPoint(245,440); path4.setEndPoint(245,465); path4.setSpeedTime(20,1.0); path4.calcPathLength(); queue2.assignNextComponent(path4); path4.assignPreviousComponent(queue2); var process1 = new ProcessComponent(2.0,10.0); process1.defineDataGroup(5,465,80,"#00FFFF","#FF0000","#FFFF00"); process1.setExclusive(true); process1.setRoutingMethod(1); //1 single connection, 2 distribution logic, 3 model logic path3.assignNextComponent(process1); process1.assignPreviousComponent(path3); var process2 = new ProcessComponent(2.0,10.0); process2.defineDataGroup(175,465,80,"#00FFFF","#FF0000","#FFFF00"); process2.setExclusive(true); process2.setRoutingMethod(1); //1 single connection, 2 distribution logic, 3 model logic path4.assignNextComponent(process2); process2.assignPreviousComponent(path4); var path5 = new PathComponent(); path5.setStartPoint(95,539); path5.setEndPoint(170,564); path5.setSpeedTime(30,1.0); path5.calcPathLength(); process1.assignNextComponent(path5); path5.assignPreviousComponent(process1); var path6 = new PathComponent(); path6.setStartPoint(245,539); path6.setEndPoint(170,564); path6.setSpeedTime(30,1.0); path6.calcPathLength(); process2.assignNextComponent(path6); path6.assignPreviousComponent(process2); var exit1 = new ExitComponent(2.0); exit1.defineDataGroup(90,564,80,"#00FFFF","#FF0000","#FFFF00"); path5.assignNextComponent(exit1); path6.assignNextComponent(exit1); exit1.assignPreviousComponent(path5); exit1.assignPreviousComponent(path6); |
Here’s the code for the entire Queue component. Of special note is the logic for the forwardEntity
method which supports three different methods for calculating the next destination of the forwarded entity (1: single connection, 2: distribution logic, 3: model routing logic). I’m going to have to adjust this a bit so I test for whether an entity can be forwarded before I run the distribution logic, or possibly all three types of logic. Pull requests sent from downstream components can exercise the logic when no entity is available to be pulled, and entities can finish traversing the queue but be preventing from moving downstream because the next components are blocked. It may be a simple matter of calculating a potential destination and saving it as a temporary variable, and then acting on it and updating the persistent variable when an entity can actually be forwarded.
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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
function QueueComponent(displayDelay, traversalTime, maxCapacity = Infinity) { //for now always non-exclusive, could be exclusive when at capacity--or just limited by isOpen function and exclusivity doesn't matter setOfComponents.push(this); this.componentID = getNewComponentID(); this.componentType = "Queue"; this.componentName = "Queue"; this.exclusive = false; this.routingMethod = 2; //1: one connection, 2: distribution, 3 routing this.previousComponentList = []; this.previousComponentCount = 0; this.nextComponentList = []; this.nextComponentCount = 0; this.traversalTime = traversalTime; this.maxCapacity = maxCapacity; //negative or very large means infinite capacity and non-exclusive by default) this.previousComponentIndex = 0; this.nextComponentIndex = 0; this.entityQueue = []; this.openStatus = true; this.entryTime = ""; this.entryEntityID = ""; this.exitTime = ""; this.exitEntityID = ""; this.exitResidenceTime = ""; this.countInQueue = 0; this.countInTraversal = 0; this.activity = ""; this.endEntryDisplayTime = 0; this.endExitDisplayTime = 0; this.endAllDisplayTime = 0; this.displayDelay = displayDelay; //this.nextTraverseCompleteTime = 0.0; //TODO: eliminate this when related TODOs addressed this.reset = function() { this.previousComponentIndex = this.previousComponentCount - 1; this.nextComponentIndex = this.nextComponentCount - 1; this.entityQueue = []; this.openStatus = true; this.entryTime = ""; this.entryEntityID = ""; this.exitTime = ""; this.exitEntityID = ""; this.exitResidenceTime = ""; this.countInQueue = 0; this.countInTraversal = 0; this.activity = ""; this.endEntryDisplayTime = 0; this.endExitDisplayTime = 0; this.endAllDisplayTime = 0; //this.nextTraverseCompleteTime = 0.0; //TODO: eliminate this when related TODOs addressed }; this.assignPreviousComponent = function(prev) { //TODO: implement code that makes this actually work this.previousComponentList.push(prev); this.previousComponentCount++; this.previousComponentIndex = this.previousComponentCount - 1; }; this.assignNextComponent = function(next) { this.nextComponentList.push(next); this.nextComponentCount++; this.nextComponentIndex = this.nextComponentCount - 1; }; this.getComponentID = function() { return this.componentID; }; this.getComponentType = function() { //PathComponent return this.componentType; }; this.getComponentName = function() { return this.componentName; }; this.setComponentName = function(componentName) { this.componentName = componentName; }; this.getExclusive = function() { return this.exclusive; }; this.setExclusive = function(exclusive) { this.exclusive = exclusive; }; this.getTraversalTime = function() { return this.traversalTime; }; this.getMaxCapacity = function() { return this.maxCapacity; }; this.getOpenStatus = function() { return this.openStatus; }; this.setOpenStatus = function(openStatus) { this.openStatus = openStatus; }; this.getRoutingMethod = function() { return this.routingMethod; }; this.setRoutingMethod = function(routingMethod) { this.routingMethod = routingMethod; }; this.getEntryTime = function() { return this.entryTime; }; this.getEntryEntityID = function() { return this.entryEntityID; }; this.getExitTime = function() { return this.exitTime; }; this.getExitEntityID = function() { return this.exitEntityID; }; this.getExitResidenceTime = function() { return this.exitResidenceTime; }; this.getCountInQueue = function() { return this.countInQueue; }; this.getCountInTraversal = function() { return this.countInTraversal; }; this.getActivity = function() { return this.activity; }; this.getEndEntryDisplayTime =function() { return this.endEntryDisplayTime; }; this.getEndExitDisplayTime =function() { return this.endExitDisplayTime; }; this.getEndAllDisplayTime =function() { return this.endAllDisplayTime; }; this.dataGroup = new DisplayGroup1(); this.defineDataGroup = function(x,y,vw,bc,vc,lc) { this.dataGroup.define(this.componentID,this.componentType,x,y,vw,bc,vc,lc); }; this.dataGroup.addValue(this.entryEntityID,"Entry ID","integer"); this.dataGroup.addValue(this.countInQueue,"# In Queue","integer"); this.dataGroup.addValue(this.countInTraversal,"# Traversing","integer"); this.dataGroup.addValue(this.maxCapacity,"Capacity","integer") this.dataGroup.addValue(this.exitEntityID,"Exit ID","integer"); this.dataGroup.addValue(this.exitResidenceTime,"Resdnce Tm","numdec",5); this.dataGroup.addValue(this.nextComponentIndex,"Next comp.","integer"); this.dataGroup.addValue(this.activity,"Activity","text"); this.assignDisplayValues = function() { this.dataGroup.valueList[0].value = this.entryEntityID; this.dataGroup.valueList[1].value = this.countInQueue; this.dataGroup.valueList[2].value = this.countInTraversal; this.dataGroup.valueList[3].value = this.maxCapacity; this.dataGroup.valueList[4].value = this.exitEntityID; this.dataGroup.valueList[5].value = this.exitResidenceTime; this.dataGroup.valueList[6].value = this.nextComponentIndex; this.dataGroup.valueList[7].value = this.activity; if (this.exclusive) { if (this.openStatus) { this.dataGroup.setBorderColor("#00FF00"); } else { this.dataGroup.setBorderColor("#FF0000"); } } }; this.drawData = function() { this.assignDisplayValues(); this.dataGroup.drawBasic(); }; this.isOpen = function() { //QueueComponent return this.openStatus; //Queue can always receive more entities, see exclusivity discussion above }; this.clearEntryDisplay = function() { //only clear display if a new one hasn't started a new timer if (globalSimClock >= this.endEntryDisplayTime) { this.entryTime = ""; this.entryEntityID = ""; //this.exitResidenceTime = ""; } if (globalSimClock >= this.endAllDisplayTime) { this.activity= ""; } //displayProgressText("Queue entry "+this.componentID+" clears at time "+globalSimClock.toFixed(6)); }; this.clearExitDisplay = function() { //only clear display if a new one hasn't started a new timer if (globalSimClock >= this.endExitDisplayTime) { this.exitTime = ""; this.exitEntityID = ""; this.exitResidenceTime = ""; } if (globalSimClock >= this.endAllDisplayTime) { this.activity= ""; } //displayProgressText("Queue exit "+this.componentID+" clears at time "+globalSimClock.toFixed(6)); }; this.pullFromPrevious = function() { //QueueComponent if (this.previousComponentList[0].getComponentType() != "Path") { //TODO: make this support indices other than 0. How? if (this.previousComponentList[0].getComponentType() != "Entry") { this.previousComponentList[0].forwardEntity(); //TODO: must only be used on Queue and Process components that support multiple previous links } } else { //this.previousComponentList[0].previousComponent.forwardEntity(); //TODO: rework so can pull through arbitrary # of paths to next nonpath component this.previousComponentList[0].pullFromPrevious(); } }; this.nextOpen = function() { var startIndex = this.nextComponentIndex; var tempIndex = startIndex; do { tempIndex++; if (tempIndex >= this.nextComponentCount) { tempIndex = 0; } if (this.nextComponentList[tempIndex].isOpen()) { //open link found, update and return nextComponentIndex this.nextComponentIndex = tempIndex; return tempIndex; } } while (tempIndex != startIndex); return -1; //no open links found, leave nextComponentIndex unchanged }; this.traverseComplete = function() { this.countInTraversal--; //TODO: ensure handled properly if traversal time is zero //figure out which entity just finished traversing var tempID = this.entityQueue[this.countInTraversal].entityID; //TODO: this works for FIFO, but not necessarily for other logics displayProgressText("Queue comp. "+this.componentID+" entity: "+tempID+" trav. at time "+globalSimClock.toFixed(6)); this.forwardEntity(); }; this.forwardEntity = function() { //QueueComponent var dest = -1; if (this.routingMethod == 1) { //single connection if (this.nextComponentList[0].isOpen()) { dest = 0; } } else if (this.routingMethod == 2) { //distribution if (this.nextOpen() >= 0) { dest = this.nextComponentIndex; } } else if (this.routingMethod == 3) { //model routing logic if (this.savedDestination >= 0) { dest = this.savedDestination; } else { dest = 0; var test = Math.random(); while (test > routingTable[dest]) { dest++; } if (dest <= this.nextComponentCount) { if (!this.nextComponentList[dest].isOpen()) { dest = -1; } } else { alert("Entry comp. tried to assign destination with too high of an index") } } } else { //0 uninitialized or anything else alert("comp. "+this.componentID+" incorrect routing method: "+this.routingMethod); } if (dest >= 0) { if (this.countInQueue > this.countInTraversal) { var entity = this.entityQueue.pop(); //TODO: are we testing to ensure the next entity is really available if (entity) { //calculate how long item was in queue this.exitResidenceTime = globalSimClock - entity.getLocalEntryTime(); //now use this to calculate stats for the interval //TODO: calculate stats as needed this.countInQueue--; if (this.countInQueue >= this.maxCapacity) { this.openStatus = false; } else { this.openStatus = true; } this.exitTime = globalSimClock; this.exitEntityID = entity.entityID; this.activity = "forward entity"; this.endExitDisplayTime = globalSimClock+this.displayDelay; this.endAllDisplayTime = this.endExitDisplayTime; feq.newItem(this.endExitDisplayTime,this,"clearExitDisplay"); displayProgressText("Queue comp. "+this.componentID+" forwards entity: "+this.exitEntityID+" at time "+globalSimClock.toFixed(6)); if (this.exclusive) { if (this.previousComponentList[0].getComponentType() == "Path") { //TODO: figure out how to do this backwards through multiple connections this.previousComponentList[0].setPreviousOpen(); } this.pullFromPrevious(); //TODO: call this with a modest (~1 sec) delay to account for reaction time? //may or may not successfully get an entity but should always be called } this.nextComponentList[this.nextComponentIndex].receiveEntity(entity); } } } }; this.receiveEntity = function(entity) { //QueueComponent //receive the entity entity.setLocalEntryTime(); //record time entity entered queue this.entityQueue.unshift(entity); this.countInQueue++; this.countInTraversal++; //TODO: ensure handled properly if traversal time is zero if (this.countInQueue >= this.maxCapacity) { this.openStatus = false; } else { this.openStatus = true; } //display what was done this.entryTime = globalSimClock; this.entryEntityID = entity.entityID; this.activity = "receive entity"; //set timer to clear the display after a bit this.endEntryDisplayTime = globalSimClock+this.displayDelay; this.endAllDisplayTime = this.endEntryDisplayTime; feq.newItem(this.endEntryDisplayTime,this,"clearEntryDisplay"); //set timer for the new entity to track its traversal time //TODO: don't bother if traversal time is zero, also insure countInTraversal is decremented (or not incremented) as needed //this.nextTraverseCompleteTime = globalSimClock+this.traversalTime; //feq.newItem(this.nextTraverseCompleteTime,this,"traverseComplete") var traverseCompleteTime = globalSimClock+this.traversalTime; feq.newItem(traverseCompleteTime,this,"traverseComplete"); //TODO: eliminate variable and assign value directly in function call displayProgressText("Queue comp. "+this.componentID+" receives entity: "+this.entryEntityID+" at time "+globalSimClock.toFixed(6)); //if (this.nextComponent.isOpen()) { //TODO: run this if traversal time is zero // this.forwardEntity(); //} }; this.activate = function(nextState) { if (nextState == "clearEntryDisplay") { this.clearEntryDisplay(); } else if (nextState == "clearExitDisplay") { this.clearExitDisplay(); } else if (nextState == "traverseComplete") { this.traverseComplete(); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate } //QueueComponent |
The Process component works almost exactly the same way as the Queue component, except for the time being the maximum capacity is one.
Here’s the code for the Path component. It works largely the same way but each path currently supports only one previous and one next connection. Note the recursiive nature of the pullFromPrevious
method, which passes the request through contiguous Path components until asking the first-enncountered non-Path (non-Entry) component to forward an entity.
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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
function PathComponent() { setOfComponents.push(this); this.componentID = getNewComponentID(); this.componentType = "Path"; this.componentName = "Path"; this.exclusive = false; this.previousComponent = null; this.nextComponent = null; this.entityQueue = []; this.traverseQueue = []; this.openStatus = true; this.entryTime = ""; this.entryEntityID = ""; this.exitTime = ""; this.exitEntityID = ""; this.exitResidenceTime = ""; //TODO: probably not needed this.countInQueue = 0; this.x1 = 0.0; this.y1 = 0.0; this.x2 = 0.0; this.y2 = 0.0; this.pathLength = 0.0; //this.endTraverse = 0.0; this.reset = function() { this.entityQueue = []; this.traverseQueue = []; this.openStatus = true; this.entryTime = ""; this.entryEntityID = ""; this.exitTime = ""; this.exitEntityID = ""; this.exitResidenceTime = ""; //TODO: probably not needed this.countInQueue = 0; //this.endTraverse = 0.0; }; this.assignPreviousComponent = function(previous) { this.previousComponent = previous; }; this.assignNextComponent = function(next) { this.nextComponent = next; this.exclusive = next.getExclusive(); }; this.setStartPoint = function(x1,y1) { this.x1 = x1; this.y1 = y1; }; this.setEndPoint = function(x2,y2) { this.x2 = x2; this.y2 = y2; }; this.setSpeedTime = function(speed, maxRefreshTime) { this.speed = speed; this.maxRefreshTime = maxRefreshTime; }; this.calcPathLength = function() { this.pathLength = Math.sqrt((this.x2-this.x1)*(this.x2-this.x1) + (this.y2-this.y1)*(this.y2-this.y1)); //this.endTraverse = this.pathLength; }; this.getComponentID =function() { return this.componentID; }; this.getComponentType =function() { //PathComponent return this.componentType; }; this.getComponentName =function() { return this.componentName; }; this.setComponentName =function(componentName) { this.componentName = componentName; }; this.getExclusive = function() { return this.exclusive; }; this.setExclusive = function(exclusive) { this.exclusive = exclusive; }; this.getOpenStatus = function() { return this.openStatus; }; this.setOpenStatus = function(openStatus) { this.openStatus = openStatus; }; this.getEntryTime =function() { return this.entryTime; }; this.getEntryEntityID =function() { return this.entryEntityID; }; this.getExitTime =function() { return this.exitTime; }; this.getExitEntityID =function() { return this.exitEntityID; }; this.getExitResidenceTime =function() { return this.exitResidenceTime; }; this.getCountInQueue =function() { return this.countInQueue; }; this.drawData = function() { //this.nodeColor = "#FF00FF"; if (this.exclusive) { if (this.openStatus) { this.lineColor = "#00FF00"; } else { this.lineColor = "#FF0000"; } } else { this.lineColor = "#FFFFFF"; } globalCTX.strokeStyle = this.lineColor; globalCTX.beginPath(); globalCTX.moveTo(this.x1+0.5,this.y1+0.5); globalCTX.lineTo(this.x2+0.5,this.y2+0.5); globalCTX.stroke(); //drawNode(this.x1+0.5,this.y1+0.5,3,this.nodeColor); //drawNode(this.x2+0.5,this.y2+0.5,3,this.nodeColor); //var numEntities = this.entityQueue.length; //if (numEntities > 0) { // for (var i=0; i<numEntities; i++) { // this.entityQueue[i].drawEntity(); // } //} }; this.drawNodes = function() { this.nodeColor = "#FF00FF"; drawNode(this.x1+0.5,this.y1+0.5,3,this.nodeColor); drawNode(this.x2+0.5,this.y2+0.5,3,this.nodeColor); }; this.drawEntities = function() { var numEntities = this.entityQueue.length; if (numEntities > 0) { for (var i=0; i<numEntities; i++) { this.entityQueue[i].drawEntity(); } } }; this.isOpen = function() { //PathComponent if (!this.exclusive) { if (this.entityQueue.length > 0) { if (this.traverseQueue[0] < (10 + 2)) { if (this.previousComponent.componentType == "Path") { this.openStatus = false; return false; //} else { // this.openStatus = true; // return true; } } } } else { if (this.countInQueue <= 0){ this.openStatus = this.nextComponent.isOpen(); return this.openStatus; } else { this.openStatus = false; return false; } } this.openStatus = true; return true; }; this.pullFromPrevious = function() { //PathComponent if (this.previousComponent.getComponentType != "Path") { //TODO: figure out what, if anything, happens when need to support multiple incoming/previous links if (this.previousComponent.getComponentType != "Entry") { this.previousComponent.forwardEntity(); } } else { this.previousComponent.pullFromPrevious(); } }; this.setNextClosed = function() { this.openStatus = false; if (this.nextComponent.getComponentType != "Path") { this.nextComponent.setOpenStatus(false); } else { this.nextComponent.setNextClosed(); } }; this.setPreviousOpen = function() { this.openStatus = true; if (this.previousComponent.getComponentType == "Path") { this.previousComponent.setPreviousOpen(); } }; this.moveEntity = function(entity) { //PathComponent //moves entities down path, but keeps them from overlapping //for now assumes radius of 5 and clearance of 2 //start from discharge end of list var index = this.entityQueue.length - 1; while ((this.entityQueue[index].entityID != entity.entityID) && (index > 0)) { index--; } var distanceIncrement = this.speed * this.maxRefreshTime; var dist = distanceIncrement; var timeIncrement = this.maxRefreshTime; var nextState = "move"; if (index < this.entityQueue.length - 1) { //not the lead entity, check against next entity if (this.traverseQueue[index]+distanceIncrement > (this.traverseQueue[index+1] - 10 - 2)) { dist = this.traverseQueue[index+1] - 10 - 2 - this.traverseQueue[index]; if (dist <= 0.0) { dist = 0.0; timeIncrement = this.maxRefreshTime; //0.5; } else { timeIncrement *= dist / distanceIncrement; } } } else { //this is the lead entity, check against end of path if (this.traverseQueue[index]+distanceIncrement > this.pathLength) { dist = this.pathLength - this.traverseQueue[index] + 0.001; timeIncrement *= dist / distanceIncrement; nextState = "forward"; } } //TODO: also check against endTraverse, which considers possible entity on subsequent path this.traverseQueue[index] += dist; //distanceIncrement; var frac = this.traverseQueue[index] / this.pathLength; var x = this.x1 + (this.x2 - this.x1) * frac; var y = this.y1 + (this.y2 - this.y1) * frac; entity.setLocation(x,y); feq.newItem(globalSimClock+timeIncrement,this,nextState,entity); displayProgressText("Path comp. "+this.componentID+" moves entity "+entity.entityID+" at time "+globalSimClock.toFixed(6)); }; this.forwardEntity = function() { //PathComponent if (this.nextComponent.isOpen()) { this.traverseQueue.pop(); var entity = this.entityQueue.pop(); if (entity) { this.countInQueue--; this.exitTime = globalSimClock; this.exitEntityID = entity.entityID; displayProgressText("Path comp. "+this.componentID+" forwards entity: "+this.exitEntityID+" at time "+globalSimClock.toFixed(6)); this.nextComponent.receiveEntity(entity); } } //if this Component is exclusive it can only be set back to open by the downstream component when it clears }; this.receiveEntity = function(entity) { //PathComponent //receive the entity entity.setLocalEntryTime(); //record time entity entered queue entity.setLocation(this.x1,this.y1); feq.newItem(globalSimClock+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.openStatus = false; //if (this.openStatus) { // this.setNextClosed(); //} } //if (this.nextComponent.isOpen()) { // this.forwardEntity(); //} }; this.activate = function(nextState,entity2) { if (nextState == "move") { this.moveEntity(entity2); } else if (nextState == "forward") { this.forwardEntity(); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate } //PathComponent |
At this point I’ve mostly worked out the procedures for routing entities forward using different kinds of logic, but I haven’t examined support for complex logic for pulling entities forward from multiple upstream connections. So far the pull logic has only been used for components with a single upstream connection (the code blindly references this.previousConnectionList[0]
). What if multiple components are all trying to forward entities to the same exclusive component downstream? When the downstream component clears by forwarding one of its own entities, how does it know which upstream component to pull from? I’ve mentioned previously that the required logic, applicable only to pulling to exclusive components (non-exclusive components can simply receive everything sent to them) should work something like the distribution logic going forward, but an extra queue may be required. It’s also possible that the need to do this can be largely finessed by ensuring that some kind of non-exclusive queue is always included so the complex, rotating pull logic is never required. Alternatively, there may be a simpler solution I simply haven’t seen because I haven’t experimented with it yet. The pulling component, for example, could poll all of the connections to see which upstream has the oldest forwarding request outstanding, but there could be other considerations that demand a different priority order.
I’m slowly trying different combinations and catching inconsistencies but I’m getting the feeling that things are getting a little entropic. Continued inspection, testing, and honing should bring more loose ends into focus so they can be continually corrected and streamlined as opportunities present themselves. At some point I’m going to have to run tests against a number of configurations to ensure every combination of components is operating properly. That’s going on the To Do list.
I reworked the drawing of Path components so the line segment is drawn at the same time as all the other components are drawn, and then the drawModel
function circles back to draw all of the nodes and then all of the entities. This makes the display look more consistent. I also removed the ending times for the display of entry and exit entity IDs since we know that mechanism works and because it saves space. Never mind that I added the maximum Capacity to the Queue component displays and the exit count to the Exit component. Finally, I changed the lower text region to stop displaying messages when the entry and exit ID information clears for each component. This means that some steps will execute without generating a message. This was done to reduce clutter so I could concentrate on debugging.