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