Step 12: Main Storage Area
In the previous step, we started the implementation of the required warehouse operation process as described in the introduction.
We will complete this implementation in this step. To do this, we need to code the transportation of pallets between the dispatch areas and the main storage area. Newly arrived pallets should be taken from the dispatch area to the main storage area. Symmetrically, pallets being shipped should be moved from the main storage area to the dispatch area for shipment.
1. Main storage area
...
private StorageArea mainStorage;
public StorageArea getMainStorage() {
return mainStorage;
}
...
private void initializeMainStorage() {
final var places = scenario.getStoragePlaces().stream()
.map(scenarioNode -> newPalletPosition(scenarioNode, randomTrue(0.5)))
.collect(Collectors.toCollection(LinkedList::new));
mainStorage = new StorageArea(places);
}
...
In the initializeGates()
method, in the call to the newPalletPosition()
method, replace the last parameter with false
.
...
// .map(scenarioNode -> newPalletPosition(scenarioNode, direction == Direction.OUT))
.map(scenarioNode -> newPalletPosition(scenarioNode, false))
...
This code change makes all dispatch zones initially empty.
2. Bringing the Main Storage into Play
Let’s update the handleTruckOnGate()
method:
private void handleTruckOnGate(Truck truck, Gate gate, Runnable onComplete) {
if (gate.getDirection() == Direction.IN) {
// unload the truck and then unload the gate storage
handleTruck(truck, gate, () -> handleGateArea(gate, onComplete));
} else {
// load the gate storage and then load the truck
handleGateArea(gate, () -> handleTruck(truck, gate, onComplete));
}
}
And add these 2 methods:
/**
* (Un)loads gate storage.
*/
private void handleGateArea(Gate gate, Runnable onComplete) {
final var loading = gate.getDirection() == Direction.OUT;
final var places = gate.getStorageArea()
.getPlacesAvailableFor(loading)
.toList();
final var placeCount = new AtomicInteger(places.size());
if (placeCount.get() == 0) {
onComplete.run();
return;
}
for (var p : places) {
handleGatePlace(loading, p, () -> {
if (placeCount.decrementAndGet() == 0) {
onComplete.run();
}
});
}
}
/**
* Gets corresponding place in the main storage.
* Gets a forklift.
* Moves a pallet.
* Releases the forklift.
*/
private void handleGatePlace(boolean loading, PalletPosition gatePlace, Runnable onComplete) {
model.getMainStorage().reservePlace(loading, storagePlace ->
requestForklift((f, releaser) ->
new MovePalletTask(engine, f, gatePlace, storagePlace, loading).start(() -> {
releaser.run();
onComplete.run();
})
)
);
}
Let’s look at the handleGateArea()
method.
Here we start several handleGatePlace()
tasks, one for every place in the gate area.
We have to invoke the onComplete
callback only after all these tasks are finished.
Yet we don’t know which of them will be the last.
So we decrement the placeCount
value every time one of the tasks finishes.
As soon as placeCount
hits zero, we can be sure that the last one is over, the whole handleGateArea()
procedure is done, and it’s time to call onComplete
.
Primitive |
3. Checking the logic
Let’s run the simulation… Great, everything works as expected.
However, if you carefully watched the animation, you probably noticed that forklifts sometimes move through the pallets:

But why is this happening? We should have fixed this in Step 4, right?
The reason is that the implementation made in that step considered the occupied places at the time of route finding. And now, the pallet can be put into the place during some forklift’s movement. What can we do with this shortcoming?