Step 3: Move cargo
1. Tasks
1.1. Add and export tasks
package
-
In the 'simulation' bundle, create a new
com.company.warehouse.simulation.tasks
package. -
Open the
META-INF/MANIFEST.MF
file in thesimulation
bundle. -
Open its 'Runtime' tab.
-
Make sure that the
com.company.warehouse.simulation.tasks
is 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, update 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
AgentState
enum. -
Remove the
control
field 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, add 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:
