Step 3: Move cargo
1. Tasks
1.1. Add and export tasks package
-
In the 'simulation' bundle, create a new
com.company.warehouse.simulation.taskspackage. -
Open the
META-INF/MANIFEST.MFfile in thesimulationbundle. -
Open its 'Runtime' tab.
-
Make sure that the
com.company.warehouse.simulation.tasksis in the list of exported packages.
1.2. IEquipmentState
Add the IEquipmentState interface to the 'simulation' bundle. It will be the parent of forklift states. Its only method indicates
whether an equipment item is being utilized when in this state, so that we can calculate the overall utilization.
package com.company.warehouse.simulation.tasks;
public interface IEquipmentState {
boolean isUtilized();
}
1.3. MovePalletTask
Create the MovePalletTask class:
package com.company.warehouse.simulation.task;
import com.amalgamasimulation.engine.Engine;
import com.amalgamasimulation.engine.StateMachine;
import com.company.warehouse.simulation.PalletPosition;
import com.company.warehouse.simulation.graph.agents.Forklift;
/**
* A task for one forklift to move a pallet from its current position to another.
*/
public class MovePalletTask {
/**
* All the possible states for the <code>StateMachine</code>.
*/
public enum State implements IEquipmentState {
PENDING(false), // not utilizing
MOVE_TO_LOADING(true),
LOADING(true),
MOVE_TO_UNLOADING(true),
UNLOADING(true),
FINISHED(false), // not utilizing
;
private boolean utilized;
@Override
public boolean isUtilized() {
return utilized;
}
private State(boolean utilized) {
this.utilized = utilized;
}
}
private final Forklift forklift;
private final PalletPosition from;
private final PalletPosition to;
private final StateMachine<State> control;
/**
* User-provided task completion callback
*/
private Runnable onComplete;
public MovePalletTask(Engine engine, Forklift forklift, PalletPosition from, PalletPosition to) {
this.forklift = forklift;
this.from = from;
this.to = to;
// Create a StateMachine providing all the states and specifying a starting one.
control = new StateMachine<>(State.values(), State.PENDING, engine)
// Declare all allowed transitions
.addTransition(State.PENDING, State.MOVE_TO_LOADING)
.addTransition(State.MOVE_TO_LOADING, State.LOADING)
.addTransition(State.LOADING, State.MOVE_TO_UNLOADING)
.addTransition(State.MOVE_TO_UNLOADING, State.UNLOADING)
.addTransition(State.UNLOADING, State.FINISHED)
// Define state handlers
.addEnterAction(State.MOVE_TO_LOADING, state -> moveToLoading())
.addEnterAction(State.LOADING, state -> loading())
.addEnterAction(State.MOVE_TO_UNLOADING, state -> moveToUnloading())
.addEnterAction(State.UNLOADING, state -> unloading())
.addEnterAction(State.FINISHED, state -> finished())
;
}
/**
* Starts the task.
* @param onComplete callback to invoke on completion
*/
public void start(Runnable onComplete) {
this.onComplete = onComplete != null ? onComplete : () -> {};
control.receiveMessage(State.MOVE_TO_LOADING);
}
private void moveToLoading() {
forklift.moveTo(from.getNode(),
() -> control.receiveMessage(State.LOADING));
}
private void loading() {
forklift.load(from,
() -> control.receiveMessage(State.MOVE_TO_UNLOADING));
}
private void moveToUnloading() {
forklift.moveTo(to.getNode(),
() -> control.receiveMessage(State.UNLOADING));
}
private void unloading() {
forklift.unload(to,
() -> control.receiveMessage(State.FINISHED));
}
private void finished() {
onComplete.run();
}
}
Don’t worry: this class contains some compilation errors that will soon be fixed by the upcoming changes.
1.4. Update the Forklift class ('simulation' bundle)
Add the following fields and methods:
private MovePalletTask task;
//...
public MovePalletTask getCurrentTask() {
return task;
}
public void setCurrentTask(MovePalletTask task) {
this.task = task;
}
public boolean isIdle() {
return task == null;
}
public void load(PalletPosition container, Runnable onComplete) {
resetAction(onComplete);
loaded = true;
container.performPalletOperation(Operation.UNLOADING);
finishAction();
}
public void unload(PalletPosition container, Runnable onComplete) {
resetAction(onComplete);
loaded = false;
container.performPalletOperation(Operation.LOADING);
finishAction();
}
Add the missing import statements.
1.5. Update the Model class
Add the dispatchIdleForklifts() method:
private void dispatchIdleForklifts() {
engine().scheduleRelative(0, () -> getAgents() .stream()
.filter(Forklift::isIdle)
.forEach(this::dispatchAgent));
}
Replace the dispatchAgent() method with the following field and two methods:
// pallet positions that are used in currently running tasks
private Set<PalletPosition> palletPositionsInTasks = new HashSet<>();
private void dispatchAgent(Forklift forklift) {
if (tryDispatchAgent(forklift, palletPositionsAtDockAreas.get(Direction.IN), mainStoragePalletPositions)) {
return;
}
if (tryDispatchAgent(forklift, mainStoragePalletPositions, palletPositionsAtDockAreas.get(Direction.OUT))) {
return;
}
if (tryDispatchAgent(forklift, palletPositionsAtDockAreas.get(Direction.IN), palletPositionsAtDockAreas.get(Direction.OUT))) {
return;
}
}
private boolean tryDispatchAgent(Forklift forklift, List<PalletPosition> possibleSources, List<PalletPosition> possibleDestinations) {
possibleSources = possibleSources .stream()
.filter(pp -> !palletPositionsInTasks.contains(pp))
.filter(pp -> pp.isAvailableFor(Operation.UNLOADING))
.toList();
possibleDestinations = possibleDestinations .stream()
.filter(pp -> !palletPositionsInTasks.contains(pp))
.filter(pp -> pp.isAvailableFor(Operation.LOADING))
.toList();
if (possibleSources.isEmpty() || possibleDestinations.isEmpty()) {
return false;
}
var from = possibleSources.get(random.nextInt(possibleSources.size()));
var to = possibleDestinations.get(random.nextInt(possibleDestinations.size()));
var movingTask = new MovePalletTask(engine(), forklift, from, to);
// starting the task
palletPositionsInTasks.add(from);
palletPositionsInTasks.add(to);
forklift.setCurrentTask(movingTask);
movingTask.start(() -> {
palletPositionsInTasks.remove(from);
palletPositionsInTasks.remove(to);
forklift.setCurrentTask(null);
dispatchIdleForklifts();
});
return true;
}
Add the missing import statements.
Run the application. Now the forklifts should move cargo around the warehouse.
2. Update Gantt chart
In the com.company.warehouse.simulation.graph.agents.stats.slots package,
remove the BypassingSlot and MovingSlot classes.
Then, replace the contents of the AgentStatsSlot class:
package com.company.warehouse.simulation.graph.agents.stats.slots;
import com.amalgamasimulation.core.scheduling.Slot;
import com.company.warehouse.simulation.graph.agents.Forklift;
import com.company.warehouse.simulation.tasks.IEquipmentState;
public class AgentStatsSlot extends Slot {
private boolean closed;
private Forklift agent;
private IEquipmentState state;
public AgentStatsSlot(double beginTime, Forklift agent, IEquipmentState state) {
super(beginTime, beginTime);
this.agent = agent;
this.state = state;
}
@Override
public double endTime() {
return Math.max(beginTime(), closed ? super.endTime() : agent.time());
}
public void close() {
max = agent.time();
closed = true;
}
public IEquipmentState getState() {
return state;
}
}
The Forklift class now contains some compilation errors due to the removal of the BypassingSlot and MovingSlot classes.
In the Forklift class:
-
Remove the
AgentStateenum. -
Remove the
controlfield and its initialization in the constructor. -
Remove the
onEnteredState(MOVING, engine);call from the constructor (the last line). -
Remove the
onOtherAgentReached(),onBypassingEntered(),onMovingEntered(),onEnteredState()methods.
Add the following methods and a field:
public void putStatsSlot(IEquipmentState state) {
var newSlot = new AgentStatsSlot(engine.time(), this, state);
if(!statsSlots.isEmpty()) {
var lastSlot = statsSlots.get(statsSlots.size() - 1);
if(lastSlot.duration() > 0) {
lastSlot.close();
statsSlots.add(newSlot);
} else {
statsSlots.set(statsSlots.size() - 1, newSlot);
}
} else {
statsSlots.add(newSlot);
}
utilization = Utils.zidz(
statsSlots.stream().filter(slot -> slot.getState().isUtilized()).mapToDouble(slot -> slot.duration()).sum(),
time());
}
private double utilization = 0;
public double getUtilization() {
return utilization;
}
Add the missing import statements. The Forklift class should now compile.
In the MovePalletTask class constructor, append the following command to the control initialization:
//...
.addEnterAction((state, message) -> forklift.putStatsSlot(state))
//...
Switch to the 'application' bundle and add the new EquipmentStateUI class:
package com.company.warehouse.application;
import java.awt.Color;
import java.util.Map;
import com.amalgamasimulation.utils.Colors;
import com.company.warehouse.simulation.tasks.IEquipmentState;
import com.company.warehouse.simulation.tasks.MovePalletTask;
public class EquipmentStateUI {
public static String nameOf(IEquipmentState state) {
return names.get(state);
}
public static Color colorOf(IEquipmentState state) {
return colors.get(state);
}
private static Map<IEquipmentState, String> names = Map.of(
MovePalletTask.State.PENDING, "-",
MovePalletTask.State.MOVE_TO_LOADING, "Move to loading",
MovePalletTask.State.LOADING, "Loading",
MovePalletTask.State.MOVE_TO_UNLOADING, "Move to unloading",
MovePalletTask.State.UNLOADING, "Unloading",
MovePalletTask.State.FINISHED, "-"
);
private static Map<IEquipmentState, Color> colors = Map.of(
MovePalletTask.State.PENDING, Colors.gray,
MovePalletTask.State.MOVE_TO_LOADING, Colors.orange,
MovePalletTask.State.LOADING, Colors.blue,
MovePalletTask.State.MOVE_TO_UNLOADING, Colors.orangeRed,
MovePalletTask.State.UNLOADING, Colors.green,
MovePalletTask.State.FINISHED, Colors.gray
);
}
Replace the GanttChartPart.updateContent() method:
private void updateContent(Model model) {
ganttChart.getVisualSetContainer().clear();
if (model != null) {
ganttChart.getXAxis().setTimeStyle(AxisTimeStyle.getDefault(model.timeToDate(0), model.timeUnit()))
.setDisplayedRange(0, model.dateToTime(model.timeToDate(model.getEndTime())));
model.getAgents().forEach(forklift -> {
var visualSet = new GanttVisualSet<>(forklift.getName(), () -> forklift.getStatsSlots(), t -> t.beginTime(), t -> t.endTime())
.setBackgroundColor(s -> EquipmentStateUI.colorOf(s.getState()))
.setLabelText( LabelSide.TOP_LEFT, this::getTopLeftText, s -> 9.0, s -> Colors.white )
.setLabelText( LabelSide.TOP_CENTER, this::getTopCenterText, s -> 9.0, s -> Colors.white )
.setLabelText( LabelSide.TOP_RIGHT, this::getTopRightText, s -> 9.0, s -> Colors.white )
.setLabelText( LabelSide.MIDDLE_CENTER, this::getMiddleCenterText, s -> 12.0, s -> Colors.white );
ganttChart.getVisualSetContainer().addVisualSet(visualSet);
});
}
ganttChart.redraw();
}
Add the missing import statements.
Run the simulation and open the 'Gantt Chart' part: