/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2013 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.ProcessFlow;
import java.util.ArrayList;
import java.util.HashMap;
import com.jaamsim.Graphics.DisplayEntity;
import com.jaamsim.Graphics.PolylineInfo;
import com.jaamsim.Samples.SampleInput;
import com.jaamsim.basicsim.EntityTarget;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.ColourInput;
import com.jaamsim.input.Input;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.ValueInput;
import com.jaamsim.math.Vec3d;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.TimeUnit;
/**
* Moves one or more Entities along a path with a specified travel time. Entities can have different travel times, which
* are represented as varying speeds.
*/
public class EntityDelay extends LinkedComponent {
@Keyword(description = "The delay time for the path.\n" +
"The input can be a constant value, a time series of values, or a probability distribution to be sampled.",
exampleList = { "3.0 h", "NormalDistribution1", "'1[s] + 0.5*[TimeSeries1].PresentValue'" })
private final SampleInput duration;
@Keyword(description = "If TRUE, a delayed entity is moved along the " +
"specified path to indicate its progression through the delay.",
exampleList = {"TRUE"})
private final BooleanInput animation;
@Keyword(description = "The width of the path in pixels.",
exampleList = {"1"})
private final ValueInput widthInput;
@Keyword(description = "The colour of the path.\n" +
"The input can be a colour keyword or RGB value.",
exampleList = {"red"})
private final ColourInput colorInput;
private final HashMap<Long, EntityDelayEntry> entityMap = new HashMap<>(); // List of the entities being handled
{
stateGraphics.setHidden(false);
duration = new SampleInput("Duration", "Key Inputs", null);
duration.setUnitType(TimeUnit.class);
duration.setEntity(this);
duration.setValidRange(0, Double.POSITIVE_INFINITY);
duration.setRequired(true);
this.addInput(duration);
animation = new BooleanInput("Animation", "Key Inputs", true);
this.addInput(animation);
widthInput = new ValueInput("Width", "Key Inputs", 1.0d);
widthInput.setUnitType(DimensionlessUnit.class);
widthInput.setValidRange(1.0d, Double.POSITIVE_INFINITY);
this.addInput(widthInput);
colorInput = new ColourInput("Color", "Key Inputs", ColourInput.BLACK);
this.addInput(colorInput);
this.addSynonym(colorInput, "Colour");
}
public EntityDelay() {}
@Override
public void updateForInput(Input<?> in) {
super.updateForInput( in );
// If animation is turned off, clear the list of entities to be displayed
if (in == animation) {
if (!animation.getValue())
entityMap.clear();
return;
}
// If Points were input, then use them to set the start and end coordinates
if (in == pointsInput || in == colorInput || in == widthInput) {
invalidateScreenPoints();
return;
}
}
@Override
public void earlyInit() {
super.earlyInit();
entityMap.clear();
}
@Override
public String getInitialState() {
return "Idle";
}
private static class EntityDelayEntry {
DisplayEntity ent;
double startTime;
double duration;
}
@Override
public void addEntity(DisplayEntity ent) {
super.addEntity(ent);
// Select the delay time for this entity
double simTime = this.getSimTime();
double dur = duration.getValue().getNextSample(simTime);
// Add the entity to the list of entities being delayed
if (animation.getValue()) {
EntityDelayEntry entry = new EntityDelayEntry();
entry.ent = ent;
entry.startTime = simTime;
entry.duration = dur;
entityMap.put(ent.getEntityNumber(), entry);
}
this.scheduleProcess(dur, 5, new RemoveDisplayEntityTarget(this, ent));
// Set the present state to Working
this.setPresentState();
}
private static class RemoveDisplayEntityTarget extends EntityTarget<EntityDelay> {
private final DisplayEntity delayedEnt;
RemoveDisplayEntityTarget(EntityDelay d, DisplayEntity e) {
super(d, "removeDisplayEntity");
delayedEnt = e;
}
@Override
public void process() {
ent.removeDisplayEntity(delayedEnt);
}
}
public void removeDisplayEntity(DisplayEntity ent) {
// Remove the entity from the lists
if (animation.getValue())
entityMap.remove(ent.getEntityNumber());
// Send the entity to the next component
this.sendToNextComponent(ent);
this.setPresentState();
}
@Override
public void setPresentState() {
if (this.getNumberInProgress() > 0) {
this.setPresentState("Working");
}
else {
this.setPresentState("Idle");
}
}
@Override
public void updateGraphics(double simTime) {
// Loop through the entities on the path
for (EntityDelayEntry entry : entityMap.values()) {
// Calculate the distance travelled by this entity
double frac = ( simTime - entry.startTime ) / entry.duration;
// Set the position for the entity
Vec3d localPos = this.getPositionOnPolyline(simTime, frac);
entry.ent.setGlobalPosition(this.getGlobalPosition(localPos));
}
}
@Override
public PolylineInfo[] buildScreenPoints(double simTime) {
int w = Math.max(1, widthInput.getValue().intValue());
PolylineInfo[] ret = new PolylineInfo[1];
ret[0] = new PolylineInfo(pointsInput.getValue(), getCurveType(), colorInput.getValue(), w);
return ret;
}
// LinkDisplayable overrides
@Override
public Vec3d getSourcePoint() {
ArrayList<Vec3d> points = pointsInput.getValue();
if (points.size() == 0) {
return getGlobalPosition();
}
return new Vec3d(points.get(points.size()-1));
}
@Override
public Vec3d getSinkPoint() {
ArrayList<Vec3d> points = pointsInput.getValue();
if (points.size() == 0) {
return getGlobalPosition();
}
return new Vec3d(points.get(0));
}
@Override
public double getRadius() {
return 0.2; // TODO: make this a tunable parameter
}
}