Step 2: Introduce a Forklift Entity to the Simulation Model
It’s time to bring life to our static and dull world.
Its first inhabitants - forklifts - will move and function within the warehouse graph.
Therefore, we will inherit their class from GraphAgent
.
1. Add Forklift
class to the simulation bundle
-
In Project Explorer expand
bundles
/…warehouse.simulation
/src
-
Right-click on the
…warehouse.simulation
package. -
From the context menu, choose New / Class
-
Fill in the New Java Class dialog
-
Package: append
.equipment
to place the new class in the…simulation.equipment
sub-package. -
Name:
Forklift
-
Superclass:
GraphAgent<Node, Arc>
-
-
The Forklift.java file should appear in a newly created
…equipment
package
While writing code, you may need to include missing imports. You can use Eclipse IDE’s ‘import …’ quick fix feature to automate this task. |
package com.company.warehouse.simulation.equipment;
import com.amalgamasimulation.engine.Engine;
import com.amalgamasimulation.graphagent.GraphAgent;
import com.amalgamasimulation.graphagent.GeometricGraphPosition;
import com.amalgamasimulation.graphagent.GraphEnvironment;
import com.company.warehouse.simulation.graph.Arc;
import com.company.warehouse.simulation.graph.Node;
/**
* A forklift simulation.
*
* Extends <code>GraphAgent</code> as this thing is about to move around the warehouse graph.
*/
public class Forklift extends GraphAgent<Node, Arc> {
/**
* Having object name is handy for monitoring the system state and for debugging.
*/
private final String name;
/**
* A garage location.
*/
private final Node base;
/**
* A movement speed characteristic of this model of equipment.
*/
private final double velocity;
/**
* Amount of time required for loading a pallet.
*/
private final double loadingTime;
/**
* Amount of time required for unloading a pallet.
*/
private final double unloadingTime;
/**
* User provided callback to notify completion of a current action.
*/
private Runnable onComplete;
public Forklift(Engine engine, GraphEnvironment<Node, Arc, ?> graphEnvironment, String name, Node base,
double velocity, double loadingTime, double unloadingTime) {
super(engine);
this.name = name;
this.base = base;
this.velocity = velocity;
this.loadingTime = loadingTime;
this.unloadingTime = unloadingTime;
// Immerse the forklift to the graph environment
setGraphEnvironment(graphEnvironment);
// and place it to the starting node..
jumpTo(base);
}
@Override
public String getName() {
return name;
}
/**
* Starts the movement from current to specified location.
*
* @param node target location
* @param onComplete callback to notify about arrival
*/
public void moveTo(Node node, Runnable onComplete) {
resetAction(onComplete);
moveTo(node, velocity);
}
/**
* Starts the movement from current location to garage.
*
* @param onComplete callback to notify about arrival
*/
public void moveToBase(Runnable onComplete) {
moveTo(base, onComplete);
}
/**
* Aborts the action in progress. E.g. stops moving.
*/
public void cancelCurrentAction() {
resetAction(null);
}
private void resetAction(Runnable onComplete) {
cancelMoving();
this.onComplete = onComplete;
}
/**
* This overriden method is called by GraphAgent class when the movement finishes at the destination point.
*/
@Override
public void onDestinationReached(GeometricGraphPosition<Node, Arc> node) {
super.onDestinationReached(node);
finishAction();
}
/**
* Invoke callback to notify the outer world that the current action is complete.
* Reset the callback reference.
*/
private void finishAction() {
if (onComplete != null) {
var callback = onComplete;
onComplete = null;
callback.run();
}
}
/**
* It's easier to debug with a meaningful <code>toString()</code> method.
*/
@Override
public String toString() {
return name;
}
}
2. Add a forklift instance to the simulation model
Now it’s time to spawn our forklift.
Open the …simulation/Model.java source file.
This is a wizard-generated simulation model that initializes itself with the scenario data. Until now, it has built a graph and some kind of generic Agents
. Let’s replace them with our self-made forklifts.
-
Add a forklifts collection.
-
Create a
Forklift
instance from the scenario data. -
Add it to the
forklifts
list. -
Don’t forget to add the missing imports.
There are two Forklift classes available for import.
Make sure you import the correct one: com.company.warehouse.simulation.equipment.Forklift .
|
private List<Forklift> forklifts = new ArrayList<>();
public List<Forklift> getForklifts() {
return forklifts;
}
...
private void initializeForklifts() {
for (var scenarioForklift : scenario.getForklifts()) {
forklifts.add(new Forklift(
engine(),
graphEnvironment,
scenarioForklift.getName(),
mapping.nodesMap.get(scenarioForklift.getBase()),
scenarioForklift.getVelocity(),
scenarioForklift.getLoadingTimeSec() * second(),
scenarioForklift.getUnloadingTimeSec() * second()
));
}
}
And now we can give the first assignment to our forklifts.
private void makeAssignments() {
var nodesSpliter = graphEnvironment.getNodeValues().spliterator();
int i = 0;
for (var forklift : forklifts) {
final var startTime = i++ * minute();
nodesSpliter.tryAdvance(targetNode ->
engine().scheduleRelative(startTime, () -> forklift.moveTo(targetNode, null))
);
}
}
We iterate over all the forklifts and make them start movement one by one over 1-minute intervals. As destination points, we consequentially select nodes from the graph.
Now call both methods from the constructor instead of initializing Agents
.
public Model(Engine engine, Scenario scenario) {
...
// engine().setTemporal(scenario.getBeginDate(), ChronoUnit.HOURS);
engine().setTemporal(scenario.getBeginDate(), ChronoUnit.MINUTES);
...
graphEnvironment = new GraphEnvironment<>();
// engine.scheduleRelative(0, () -> getAgents().forEach(a -> dispatchAgent(a)));
initializeNodes();
initializeArcs();
initializeForklifts();
makeAssignments();
}
We updated the call to Engine::setTemporal() in order to set minutes as the main time units for our simulation. Minutes are more convenient than hours for our task.
|
3. Add some shape to the animation
If you already tried to start the simulation, you might have been disappointed.
Nothing remarkable is going on there.
That’s because the Forklift
is a new entity for the application.
So, no one knows how to display it.
Let’s give a visible shape to our creation.
-
In the
application
bundle,animation
package, add theForkliftShape
class derived fromGroupShape
. -
Pass a
Forklift
to the constructor. At this step, you should also export theequipment
package from the simulation bundle. Nicely, Eclipse export quick fix can help you with that. -
In the constructor, add two rectangle shapes that will represent a cabin and a fork.
package com.company.warehouse.application.animation;
import java.awt.Color;
import com.amalgamasimulation.animation.shapes.shapes2d.GroupShape;
import com.amalgamasimulation.animation.shapes.shapes2d.RectangleShape;
import com.amalgamasimulation.geometry.Point;
import com.amalgamasimulation.utils.Colors;
import com.company.warehouse.simulation.equipment.Forklift;
public class ForkliftShape extends GroupShape {
private Forklift forklift;
public ForkliftShape(Forklift forklift) {
super(() -> forklift.getCurrentAnimationPoint());
this.forklift = forklift;
withShape(new RectangleShape(() -> new Point(-5, -5), () -> 10.0, () -> 10.0)
.withFillColor(Colors.orange)
);
withShape(new RectangleShape(() -> new Point(-cargoSize(), 10 - cargoSize()), () -> cargoSize() * 2, () -> cargoSize() * 2)
.withFillColor(() -> cargoColor())
);
withRotationAngle(() -> - forklift.getCurrentAnimationHeading());
withFixedScale(1);
}
private double cargoSize() {
return 5;
}
private Color cargoColor() {
return Color.black;
}
}
-
In the
parts.simulation
package, open theSimulationPart
class. It is responsible for the graphical representation of the simulation model. -
In the
onShowModel()
method, replace the line that createsAgentShapes
with a similar one that createsForkliftShape
for every forklift in the model.
private void onShowModel(Model model) {
...
model.getArcs().forEach(r -> animationView.addShape(new ArcShape(r)));
model.getForklifts().forEach(f -> animationView.addShape(new ForkliftShape(f)));
}
animationView.adjustWindow();
}