My main motivation for trying to break the current functionality into M, V, and C layers isn’t about purity of design pattern so much as getting a lot of unnecessary overhead out of the simulation components themselves. They shouldn’t contain any less support functionality than they need, but they shouldn’t include any more, either.
Looking at the output and code for, say, the Queue component, shows that a lot of what’s going on inside the component itself is merely in support of the status display and timing mechanisms. I’m thinking that as much of that as possible should be moved outside of the component object itself.
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 |
function QueueComponent(displayDelay) { setOfComponents.push(this); this.componentID = getNewID(); this.componentName = "Queue"; this.entryTime = ""; this.currentEntryEntityID = ""; this.endEntryDisplayTime = 0; this.countInQueue = 0; this.exitTime = ""; this.currentExitEntityID = ""; this.exitResidenceTime = ""; this.endExitDisplayTime = 0; this.endAllDisplayTime = 0; this.activity = ""; this.displayDelay = displayDelay; this.nextComponent = null; this.entityQueue = []; //this.nextState = "clearState"; // this.dataGroup = new DisplayGroup(this.componentName,175,79,80,"#00FFFF","#FF0000","#FFFF00"); this.dataGroup = new DisplayGroup(); this.defineDataGroup = function(x,y,vw,bc,vc,lc) { this.dataGroup.define(this.componentName,x,y,vw,bc,vc,lc); }; 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 Queue","numdec","integer"); this.dataGroup.addValue(this.exitTime,"Exit Time","numdec",5); this.dataGroup.addValue(this.currentExitEntityID,"Exit ID","integer"); this.dataGroup.addValue(this.exitResidenceTime,"Resdnce Tm","numdec",5); 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.exitResidenceTime; this.dataGroup.valueList[7].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 = ""; //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.currentExitEntityID = ""; this.exitResidenceTime = ""; } if (globalSimClock >= this.endAllDisplayTime) { this.activity= ""; } displayProgressText("Queue exit "+this.componentID+" clears at time "+globalSimClock.toFixed(6)); }; this.assignNextComponent = function(next) { this.nextComponent = next; }; this.forwardEntity = function() { if (this.nextComponent.isOpen()) { var entity = this.entityQueue.pop(); 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--; this.exitTime = globalSimClock; this.currentExitEntityID = 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.currentExitEntityID+" at time "+globalSimClock.toFixed(6)); this.nextComponent.receiveEntity(entity); } } }; this.receiveEntity = function(entity) { //receive the entity entity.setLocalEntryTime(); //record time entity entered queue this.entityQueue.unshift(entity); this.countInQueue++; //display what was done this.entryTime = globalSimClock; this.currentEntryEntityID = 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"); displayProgressText("Queue comp. "+this.componentID+" receives entity: "+this.currentEntryEntityID+" at time "+globalSimClock.toFixed(6)); if (this.nextComponent.isOpen()) { this.forwardEntity(); } }; this.activate = function(nextState) { if (nextState == "clearEntryDisplay") { this.clearEntryDisplay(); } else if (nextState == "clearExitDisplay") { this.clearExitDisplay(); } else { errorUndefinedAdvanceState(this.entityID,this.nextState); } }; //this.activate } //QueueComponent |
If all of the display overhead were removed from this code we’d be left with roughly this:
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 |
function QueueComponent(displayDelay) { setOfComponents.push(this); this.componentID = getNewID(); this.componentName = "Queue"; this.currentEntryEntityID = ""; this.countInQueue = 0; this.exitResidenceTime = ""; this.nextComponent = null; this.entityQueue = []; this.drawData = function() { }; this.assignNextComponent = function(next) { this.nextComponent = next; }; this.forwardEntity = function() { if (this.nextComponent.isOpen()) { var entity = this.entityQueue.pop(); 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--; this.currentExitEntityID = entity.entityID; displayProgressText("Queue comp. "+this.componentID+" forwards entity: "+this.currentExitEntityID+" at time "+globalSimClock.toFixed(6)); this.nextComponent.receiveEntity(entity); } } }; this.receiveEntity = function(entity) { //receive the entity entity.setLocalEntryTime(); //record time entity entered queue this.entityQueue.unshift(entity); this.countInQueue++; //display what was done this.currentEntryEntityID = entity.entityID; displayProgressText("Queue comp. "+this.componentID+" receives entity: "+this.currentEntryEntityID+" at time "+globalSimClock.toFixed(6)); if (this.nextComponent.isOpen()) { this.forwardEntity(); } }; } //QueueComponent |
You can see that gets rid of most of the code, and even some of what is left is only there to draw status information in the lower, scrolling text area.
If we want to provide the deleted capability in a more streamlined, external way then we can identify some possible requirements:
- As much initialization and signalling as possible should be external to the simulation elements.
- As much state information as possible should be external to the simulation elements.
- The updating and display of such information should be able to be suppressed (or reactivated) by setting a single, global variable.
- The external display mechanism needs to maintain a link (pointer) to the simulation element so it knows where to get its data from.
- The simulation element may need to be able to reference an external display element via a link (pointer) but it would be better if such a link were not necessary.
- Some displays should be permanent and updatable.
- Since the display is always visible once defined the simulation element may not need to do anything special to allow the external display mechanism to update itself.
- If any information in the display mechanism is to “hide” itself on a timed basis then the timing mechanism should be handled entirely within the display mechanism.
- Some displays should be temporary on a fire-and-forget basis.
- The internal assignment or initiation of such a display should be kept to one line.
- If a subsequent update should happen that would change the originally initialized display before it times out, the one-line call should be able to identify the correct external display mechanism and cause it to display the updated information instead.
- The external display mechanism should incorporate its own timing and hiding capability, if appropriate.
- Setters and getters need to be created for all internal properties whose values will need to be displayed.
- Functions that do external update assignments of series of values may need to be defined externally.
- Some design decisions will reference the fact that (for the time being) the entire display is redrawn after certain classes of events, namely ones that involve processing an event from the future events queue. This idea will have to be revisited when we reincorporate wait..until events and the current events queue.
- Given the foregoing, it may be possible to detect certain classes of events outside of the simulation elements.