Step 7: Assigning requests to trucks
Here comes the most complex part of the simulation model logic: we need to coordinate trucks to take transportation requests.
The process of assigning a truck to a request will be triggered by events of two types:
-
a new transportation request has come;
-
a truck that was busy is now free.
The truck selection logic depends on the trigger event:
-
In case of a new transportation request, we will assign the request to the first available truck; but if all trucks are busy - the request should be placed in the waiting queue.
-
In case of a truck becoming free, we will take that truck and pick the first request from the request waiting queue, if any.
Now that we have discussed the request processing, we will put this logic into the Dispatcher
class.
The Dispatcher class
Create the following Dispatcher
class in the 'tutorial.model' package:
package tutorial.model;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.function.Consumer;
public class Dispatcher {
private final Model model;
private int lastTaskId = 0;
private List<TransportationTask> transportationTasks = new ArrayList<>();
private Queue<TransportationTask> waitingTasks = new LinkedList<>();
private List<Consumer<TransportationTask>> taskStateChangeHandlers = new ArrayList<>();
public Dispatcher(Model model) {
this.model = model;
}
public List<TransportationTask> getTransportationTasks() {
return transportationTasks;
}
public void addTaskStateChangeHandler(Consumer<TransportationTask> handler) {
taskStateChangeHandlers.add(handler);
}
public void clearTaskStateChangeHandlers() {
taskStateChangeHandlers.clear();
}
public void onNewRequest(TransportationRequest newRequest) {
TransportationTask task = new TransportationTask(String.valueOf(++lastTaskId), newRequest, this::onTaskCompleted, model);
transportationTasks.add(task);
onTaskStateChanged(task);
Optional<Truck> freeTruck = model.getTrucks().stream().filter(Truck::isIdle).findFirst();
if (freeTruck.isPresent()) {
task.execute(freeTruck.get());
onTaskStateChanged(task);
} else {
addWaitingTask(task);
}
}
private void onTaskCompleted(TransportationTask task) {
onTaskStateChanged(task);
TransportationTask waitingTask = getNextWaitingTask();
if (waitingTask != null) {
waitingTask.execute(task.getTruck());
onTaskStateChanged(waitingTask);
}
}
private TransportationTask getNextWaitingTask() {
return waitingTasks.poll();
}
private void addWaitingTask(TransportationTask task) {
waitingTasks.add(task);
}
private void onTaskStateChanged(TransportationTask task) {
taskStateChangeHandlers.forEach(handler -> handler.accept(task));
}
}
To keep our Dispatcher
object updated about any new requests,
we add a onNewRequest()
method that gets called from outside the class
whenever a transportation request is generated.
In this method, we look for the first available truck and assign the received transportation request
to the found truck (or place the request in the queue).
The onTruckRelease()
method gets called from a TransportationTask
object upon cargo delivery.
Changes to the Model class
In the Model
class, a new Dispatcher
object will be created and subscribed to new requests.
Add a new field to the Model
class:
private final Dispatcher dispatcher;
Add the following code to the Model
class constructor:
dispatcher = new Dispatcher(this);
requestGenerator.addNewRequestHandler(dispatcher::onNewRequest);
Check the result
Run the program and see if you get the following output to the console:
0,000 Request #1 created 0,000 Task #1 : TRANSPORTATION_STARTED. Request #1; Truck #1 at Store 1; From Warehouse 3 -> To Store 2 1,024 Request #2 created 2,500 Task #1 : TRANSPORTATION_FINISHED 2,500 Task #2 : TRANSPORTATION_STARTED. Request #2; Truck #1 at Store 2; From Warehouse 1 -> To Store 3 3,733 Request #3 created 3,871 Request #4 created 3,877 Request #5 created 4,037 Request #6 created 4,750 Task #2 : TRANSPORTATION_FINISHED 4,750 Task #3 : TRANSPORTATION_STARTED. Request #3; Truck #1 at Store 3; From Warehouse 3 -> To Store 3 4,789 Request #7 created 6,750 Task #3 : TRANSPORTATION_FINISHED 6,750 Task #4 : TRANSPORTATION_STARTED. Request #4; Truck #1 at Store 3; From Warehouse 2 -> To Store 1 7,358 Request #8 created 8,120 Request #9 created 8,197 Request #10 created 9,000 Task #4 : TRANSPORTATION_FINISHED 9,000 Task #5 : TRANSPORTATION_STARTED. Request #5; Truck #1 at Store 1; From Warehouse 1 -> To Store 3 9,129 Request #11 created 10,034 Request #12 created 10,803 Request #13 created 11,250 Task #5 : TRANSPORTATION_FINISHED 11,250 Task #6 : TRANSPORTATION_STARTED. Request #6; Truck #1 at Store 3; From Warehouse 1 -> To Store 2
Note how the request waiting queue is used here. When Request #2 gets created (at time 1.024), there are no available trucks, so the request goes to the waiting queue. Truck #1 then finishes its transportation at 2.500 and immediately takes the Request #2 from the waiting queue.
What about truck travel duration, is it correct?
Consider Task #1.
Truck speed is 40 km/h. It needs to travel empty from its initial position at Store-1 to Warehouse-3
(50 km, see static{}
section in the Main
class)
and then bring cargo from Warehouse-3 to Store-2 (another 50 km), for a total of 100 km.
Time required is 100 km / 40 km/h = 2.5 hours, and we see that Task #1 is finished exactly at that time.