Today I added a Process component. This component is meant to represent a process that takes some amount of time to complete. The time could be zero and there can be other side effects, which usually involve changing properties of the entities being processed, but the time consumed is almost always the salient feature.
The Process component works almost exactly like the Queue component, including the incorporation of the internal array that can hold multiple entities. I don’t do anything special with this yet, but this capability is in place to support special processes of which I’m aware (e.g., a stacked booth where entity A enters first, followed by entity B, and where their process times are independent; if A finishes first it can leave right away but if B finishes first it has to wait for A). For the current implementation the Process component can work with only a single entity at a time.
I also modified the handoff of entities between components. When an entity gets to the far end of a Queue component (which happens instantaneously at present), it queries the next component (the Process component) to see if it can accept an entity. If it can then the entity is passed downstream. If it cannot then nothing happens. On the other end, the downstream component, when it becomes able to accept a new entity, queries the upstream entity to pull the next entity if one is available. This push-pull mechanism requires links to be stored in both directions, and may also be generalized as the mode of connections between all components.
(Click on the “Step” button to advance through the model, refresh the page to run it again.)
Here’s the code for the Process component.
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 |
function ProcessComponent(displayDelay,processTime) { setOfEntities.push(this); this.componentID = getNewID(); this.componentName = "Process"; this.entryTime = ""; this.currentEntryEntityID = ""; this.endEntryDisplayTime = 0; this.countInQueue = 0; this.exitTime = ""; this.currentExitEntityID = ""; this.endExitDisplayTime = 0; this.endAllDisplayTime = 0; this.activity = ""; this.displayDelay = displayDelay; this.processTime = processTime; this.openStatus = true; this.previousComponent = null; this.nextComponent = null; this.entityQueue = []; //this.nextState = "clearState"; this.dataGroup = new DisplayGroup(this.componentName,10,127,80,"#00FFFF","#FF0000","#FFFF00"); this.dataGroup.addValue(this.componentID,"comp. ID","integer"); this.dataGroup.addValue(this.entryTime,"Entry Time","numdec",5); this.dataGroup.addValue(this.currentEntryEntityID,"Entry ID","integer"); this.dataGroup.addValue(this.countInQueue,"# In Process","numdec","integer"); this.dataGroup.addValue(this.exitTime,"Exit Time","numdec",5); this.dataGroup.addValue(this.currentExitEntityID,"Exit ID","integer"); this.dataGroup.addValue(this.activity,"Activity","text"); this.assignDisplayValues = function() { this.dataGroup.valueList[0].value = this.componentID; this.dataGroup.valueList[1].value = this.entryTime; this.dataGroup.valueList[2].value = this.currentEntryEntityID; this.dataGroup.valueList[3].value = this.countInQueue; this.dataGroup.valueList[4].value = this.exitTime; this.dataGroup.valueList[5].value = this.currentExitEntityID; this.dataGroup.valueList[6].value = this.activity; }; this.drawData = function() { this.assignDisplayValues(); this.dataGroup.drawBasic(); }; this.clearEntryDisplay = function() { //only clear display if a new one hasn't started a new timer if (globalSimClock >= this.endEntryDisplayTime) { this.entryTime = ""; this.currentEntryEntityID = ""; } if (globalSimClock >= this.endAllDisplayTime) { this.activity= ""; } displayProgressText("Process 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.currentExitEntityID = ""; } if (globalSimClock >= this.endAllDisplayTime) { this.activity= ""; } displayProgressText("Process exit "+this.componentID+" clears at time "+globalSimClock.toFixed(6)); }; this.assignPreviousComponent = function(previous) { this.previousComponent = previous; }; this.assignNextComponent = function(next) { this.nextComponent = next; }; this.isOpen = function() { return this.openStatus; }; this.pullFromPrevious = function() { this.previousComponent.forwardEntity(); }; this.forwardEntity = function() { var entity = this.entityQueue.pop(); if (entity) { //calculate how long item was in process var residenceTime = globalSimClock - entity.getLocalEntryTime(); //now use this to calculate stats for the interval //TODO: calculate stats as needed //this.nextComponent.receiveEntity(entity); this.countInQueue--; this.exitTime = globalSimClock; this.currentExitEntityID = entity.entityID; this.activity = "New Exit"; this.endExitDisplayTime = globalSimClock+this.displayDelay; this.endAllDisplayTime = this.endExitDisplayTime; feq.newItem(this.endExitDisplayTime,this,"clearExitDisplay"); displayProgressText("Process comp. "+this.componentID+" forwards entity: "+this.currentExitEntityID+" at time "+globalSimClock.toFixed(6)); this.openStatus = true; this.pullFromPrevious(); } }; this.receiveEntity = function(entity) { //receive the entity entity.setLocalEntryTime(); //record time entity entered process this.entityQueue.unshift(entity); this.countInQueue++; //display what was done this.entryTime = globalSimClock; this.currentEntryEntityID = entity.entityID; this.activity = "New Entry"; //set timer for the process duration feq.newItem(globalSimClock+this.processTime,this,"processTimeout"); //set timer to clear the display after a bit this.endEntryDisplayTime = globalSimClock+this.displayDelay; this.endAllDisplayTime = this.endEntryDisplayTime; feq.newItem(this.endEntryDisplayTime,this,"clearEntryDisplay"); displayProgressText("Process comp. "+this.componentID+" receives entity: "+this.currentEntryEntityID+" at time "+globalSimClock.toFixed(6)); this.openStatus = false; }; this.activate = function(nextState) { if (nextState == "processTimeout") { this.forwardEntity(); } else if (nextState == "clearEntryDisplay") { this.clearEntryDisplay(); } else if (nextState == "clearExitDisplay") { this.clearExitDisplay(); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate } //ProcessComponent |
Here’s the code that initializes all the components.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//schedule for six hours of arrivals in half-hour blocks var arrivalSchedule = [0,0,1,3,4,4,5,6,3,2,1,0]; var arrival1 = new ArrivalsComponent(30.0,arrivalSchedule); var entry1 = new EntryComponent(2.0); arrival1.assignNextComponent(entry1); var queue1 = new QueueComponent(2.0); entry1.assignNextComponent(queue1); var process1 = new ProcessComponent(2.0,9.0); process1.assignPreviousComponent(queue1); queue1.assignNextComponent(process1); //process1.assignNextComponent(NULL); //need to assign something real eventually |
The processing time was set to nine seconds so we can watch a nice collection of entities build up in the queue but which allows the system to completely empty out by the end of the simulation. Once the queue is continually not empty the process is engaged continuously.
Stepping through the simulation in this mode takes some patience, so once I get the Exit component incorporated I’m going to change things up a bit.