Step 6: Idling Task

Let’s make sure that idle forklifts return to the garage.

The IdlingTask code will look a lot like a simplified version of the MoveCargoTask. However, it will have one important additional feature: this task must be able to be canceled at any time during its execution. This will allow us to assign a forklift to a new useful job without having to wait for it to reach the garage.

IdlingTask.java
package com.company.warehouse.simulation.tasks;

import com.amalgamasimulation.engine.Engine;
import com.amalgamasimulation.engine.StateMachine;
import com.company.warehouse.simulation.equipment.Forklift;
import com.company.warehouse.simulation.equipment.IEquipmentState;

public class IdlingTask extends ForkliftTask {

    public enum State  implements IEquipmentState {
        PENDING,
        MOVE_TO_BASE,
        IDLING,
        CANCELLED,
        ;

        /**
         * Idling task is non utilizing by definition.
         */
        @Override
        public boolean isUtilized() {
            return false;
        }
    }

    private StateMachine<State> control;

    public IdlingTask(Engine engine, Forklift forklift) {
        super(engine, forklift);
        control = new StateMachine<>(State.values(), State.PENDING, engine)
                .addTransition(State.PENDING, State.MOVE_TO_BASE)
                .addTransition(State.MOVE_TO_BASE, State.IDLING)
                .addTransition(State.PENDING, State.CANCELLED)
                .addTransition(State.MOVE_TO_BASE, State.CANCELLED)
                .addTransition(State.IDLING, State.CANCELLED)
                .addEnterAction(State.MOVE_TO_BASE, state -> moveToBase())
                .addEnterAction(State.CANCELLED, state -> cancelling())
                ;
    }

    @Override
    public void run() {
        control.receiveMessage(State.MOVE_TO_BASE);
    }

    /**
     * Cancels task execution and also aborts current action of the forklift.
     */
    public void cancel() {
        control.receiveMessage(State.CANCELLED);
    }

    private void moveToBase() {
        forklift.moveToBase(
                () -> control.receiveMessage(State.IDLING));
    }

    private void cancelling() {
        forklift.cancelCurrentAction();
    }

    public Forklift getForklift() {
        return forklift;
    }

}

Now let’s update the main loop in the Model::makeAssignments() method:

Model.java
...
    private void makeAssignments() {
...
        int i = 0;
        for (var forklift : forklifts) {
            final var startTime = i++ * 2 * minute();
            busyPositions.tryAdvance(from ->
                freePositions.tryAdvance(to -> {
                    final var movingTask = new MovePalletTask(engine(), forklift, from, to);
                    final var idlingTask = new IdlingTask(engine(), forklift);
                    engine().scheduleRelative(startTime,
                            () -> movingTask.start(
                                    () -> idlingTask.start(null)
                            )
                    );
                })
            );
        }
    }
...

Add appropriate imports.

When we start MoveCargoTask, we ask it to start an IdlingTask upon completion.

To improve the visibility of the process, we increased the task start interval to 2 minutes. This removes too much crowding.

Run the simulation and make sure that after hard work, the forklifts return home and have a rest.