Step 6: Adding Trucks and TransportationTasks
The Truck class
Create a new Truck
class in the 'tutorial.model' package:
package tutorial.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.amalgamasimulation.engine.Engine;
public class Truck {
private static final double OWNERSHIP_COST_PER_HOUR_USD = 10;
private static final double USAGE_COST_PER_HOUR_USD = 25;
private final String id;
private final String name;
private final double speed;
private final Engine engine;
private record ActivePeriod(double startTime, double endTime) {}
private List<ActivePeriod> activePeriods = new ArrayList<>();
private Optional<Double> currentActivePeriodStartTime = Optional.empty();
private Asset currentAsset;
private TransportationTask currentTask;
private List<TransportationTask> taskHistory = new ArrayList<>();
public Truck(String id, String name, double speed, Store initialStore, Engine engine) {
this.id = id;
this.name = name;
this.engine = engine;
this.speed = speed;
this.currentAsset = initialStore;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getSpeed() {
return speed;
}
public Asset getCurrentAsset() {
return currentAsset;
}
public TransportationTask getCurrentTask() {
return currentTask;
}
public List<TransportationTask> getTaskHistory() {
return taskHistory;
}
public boolean isIdle() {
return currentTask == null;
}
public double getExpenses() {
double ownershipDurationHours = engine.time() / engine.hour();
double usageDurationHours = getAllActivePeriodsDurationHrs();
return ownershipDurationHours * OWNERSHIP_COST_PER_HOUR_USD + usageDurationHours * USAGE_COST_PER_HOUR_USD;
}
private double getAllActivePeriodsDurationHrs() {
double result = activePeriods.stream().mapToDouble(p -> p.endTime - p.startTime).sum();
if (currentActivePeriodStartTime.isPresent()) {
result += (engine.time() - currentActivePeriodStartTime.get()) / engine.hour();
}
return result;
}
public void onTaskStarted(TransportationTask task) {
currentActivePeriodStartTime = Optional.of(engine.time());
currentTask = task;
taskHistory.add(currentTask);
}
public void onTaskCompleted() {
activePeriods.add(new ActivePeriod(currentActivePeriodStartTime.get(), engine.time()));
currentActivePeriodStartTime = Optional.empty();
currentAsset = currentTask.getRequest().getDestAsset();
currentTask = null;
}
}
A Truck
reports whether it is available for a new transportation using its isIdle()
method.
A Truck becomes busy when its onTaskStarted(task)
method is called.
The truck is free again after calling its onTaskCompleted()
method.
Both methods will be called from outside a truck.
Ownership duration is simply the current model time (in hours) returned by the Engine.time() / Engine.hour()
methods call.
The Engine.hour()
method returns the number of units of model time in one model hour.
So, for example, if we later decide to switch to another time unit and measure model time in minutes instead of hours (by calling Engine’s setTemporal()
method),
we can be sure that Engine.time() / Engine.hour()
always returns the number of hours, not minutes,
and our expenses calculations are still correct.
Truck usage duration is calculated by the transportation time intervals.
Here we use the Utils.zidz()
method, it helps to divide numbers with no risk of division-by-zero exception.
The TransportationTask class
The TransportationTask
class will contain the answer to the question:
"What does a truck need to do when assigned a request?"
Create a new TransportationTask
class in the 'tutorial.model' package:
package tutorial.model;
import java.util.function.Consumer;
public class TransportationTask {
public enum Status {
NOT_STARTED, IN_PROGRESS, COMPLETED_ON_TIME, COMPLETED_AFTER_DEADLINE;
}
private final String id;
private Truck truck;
private final TransportationRequest request;
private final Consumer<TransportationTask> taskCompletedHandler;
private final Model model;
private double beginTime;
public TransportationTask(String id, TransportationRequest request,
Consumer<TransportationTask> taskCompletedHandler, Model model) {
this.id = id;
this.request = request;
this.taskCompletedHandler = taskCompletedHandler;
this.model = model;
}
public String getId() {
return id;
}
public Truck getTruck() {
return truck;
}
public Status getStatus() {
if (truck == null) {
return Status.NOT_STARTED;
}
if (request.isCompleted()) {
return request.getCompletedTime() <= request.getDeadlineTime() ? Status.COMPLETED_ON_TIME : Status.COMPLETED_AFTER_DEADLINE;
}
return Status.IN_PROGRESS;
}
public TransportationRequest getRequest() {
return request;
}
public double getBeginTime() {
return beginTime;
}
public void execute(Truck truck) {
this.truck = truck;
this.beginTime = model.engine().time();
double toWarehouseDistance = model.getRouteLength(truck.getCurrentAsset(), request.getSourceAsset());
double warehouseToStoreDistance = model.getRouteLength(request.getSourceAsset(), request.getDestAsset());
double totalTravelTime = (toWarehouseDistance + warehouseToStoreDistance) / truck.getSpeed();
//System.out.println("%.3f\tTask #%s : TRANSPORTATION_STARTED. Request #%s; Truck #%s at %s; From %s -> To %s"
// .formatted(model.engine().time(), getId(), request.getId(), truck.getId(), truck.getCurrentAsset().getName(),
// request.getSourceAsset().getName(), request.getDestAsset().getName()));
truck.onTaskStarted(this);
model.engine().scheduleRelative(totalTravelTime, () -> {
truck.onTaskCompleted();
request.setCompletedTime(model.engine().time());
// System.out.println("%.3f\tTask #%s : TRANSPORTATION_FINISHED".formatted(model.engine().time(), getId()));
taskCompletedHandler.accept(this);
});
}
}
Uncomment the System.out.println
calls in the execute()
method to see more debug information.
The algorithm for a truck working with a request is placed in the execute()
method:
-
The beginTime of the task is set by the engine’s current model time.
-
When the task is executed, its Truck resides, by design of the model, in some Store. So, first the Truck will need to travel to the Warehouse, and then - carry the cargo to the Store. Travel distance is thus a sum of traveling from the current Store to the Warehouse and from the Warehouse to the destination Store.
-
Call to
truck.onTaskStarted(this)
marks the Truck as busy. Here we mark the truck as 'busy' for the transportation duration period using theEngine.scheduleRelative()
method.
In the end of the transportation time interval (i.e. when the truck should become free),
we notify the truck, the transportation request, and the external observer (via the truckReleaseHandler
)
object that the transportation is over.
The 'external observer' here will be the Dispatcher
object that we will create later.
Changes to the Model class
All Truck
instances will be created by a Model
object.
Add new fields to the Model
class:
private List<Truck> trucks = new ArrayList<>();
private List<TransportationRequest> requests = new ArrayList<>();
Add new methods:
public List<Truck> getTrucks() {
return trucks;
}
public List<TransportationRequest> getRequests() {
return requests;
}
public void addRequest(TransportationRequest request) {
requests.add(request);
}
private void initializeTrucks() {
var initialAssetForTrucks = getStores().get(0);
for (int i = 0; i < scenario.getTruckCount(); i++) {
trucks.add(new Truck(String.valueOf(i + 1), String.valueOf(i + 1), scenario.getTruckSpeed(), initialAssetForTrucks, engine()));
}
}
Update the initializeModelObjects()
method:
private void initializeModelObjects() {
initializeAssets();
initializeTrucks();
}
Add the following code to the Model
class constructor:
requestGenerator.addNewRequestHandler(this::addRequest);
Now the whole model state is stored in the Model
class.
Check the result
Run the program. This should appear in the console (the same output as before):
0,000 Request #1 created 1,024 Request #2 created 3,733 Request #3 created 3,871 Request #4 created 3,877 Request #5 created 4,037 Request #6 created 4,789 Request #7 created 7,358 Request #8 created 8,120 Request #9 created 8,197 Request #10 created 9,129 Request #11 created 10,034 Request #12 created 10,803 Request #13 created