Step 4: Cargo arrival & departure

1. Update the DockArea class

Add the following methods to the DockArea class (the 'simulation' bundle):

DockArea.java
    public boolean tryAddCargo(int palletsToAdd) {
        return tryPerformPalletOperation(palletsToAdd, PalletPosition.Operation.LOADING);
    }

    public boolean tryRemoveCargo(int palletsToRemove) {
        return tryPerformPalletOperation(palletsToRemove, PalletPosition.Operation.UNLOADING);
    }

    private boolean tryPerformPalletOperation(int palletsCount, PalletPosition.Operation operation) {
        var emptyPalletPositions = palletPositions.stream().filter(pp -> pp.isAvailableFor(operation)).toList();
        if (emptyPalletPositions.size() < palletsCount) {
            return false;
        }
        for (int i = 0; i < palletsCount; i++) {
            emptyPalletPositions.get(i).performPalletOperation(operation);
        }
        return true;
    }

2. Update the Model class

In the Model class, add the following fields and methods:

Model.java
//...
    private int receiptAttempts = 0;
    private int successfulReceipts = 0;
    private int shipmentAttempts = 0;
    private int successfulShipments = 0;
//...

    private void attemptReceipt() {
        final int receiptSize = 6;
        final double receiptInterval = 1 * day();
        for (var dockArea : dockAreas.getOrDefault(Direction.IN, List.of())) {
            receiptAttempts++;
            if (dockArea.tryAddCargo(receiptSize)) {
                successfulReceipts++;
                dispatchIdleForklifts();
            }
        }
        engine().scheduleRelative(receiptInterval, this::attemptReceipt);
    }

    private void attemptShipment() {
        final int shipmentSize = 1;
        final double shipmentInterval = 4 * hour();
        for (var dockArea : dockAreas.getOrDefault(Direction.OUT, List.of())) {
            shipmentAttempts++;
            if (dockArea.tryRemoveCargo(shipmentSize)) {
                successfulShipments++;
                dispatchIdleForklifts();
            }
        }
        engine().scheduleRelative(shipmentInterval, this::attemptShipment);
    }

    public int getReceiptAttempts() {
        return receiptAttempts;
    }

    public int getSuccessfulReceipts() {
        return successfulReceipts;
    }

    public int getShipmentAttempts() {
        return shipmentAttempts;
    }

    public int getSuccessfulShipments() {
        return successfulShipments;
    }

    public double getServiceLevel() {
        return Utils.zidz(successfulReceipts + successfulShipments, receiptAttempts + shipmentAttempts);
    }

    public double getUtilization() {
        return getAgents().stream().mapToDouble(agent -> agent.getUtilization()).average().orElse(0.0);
    }

Append the attemptReceipt() and attemptShipment() method calls in the end of the Model class constructor:

Model.java
    engine.scheduleRelative(0, this::attemptReceipt);
    engine.scheduleRelative(0, this::attemptShipment);

Run the simulation.

During the previous runs, the inbound dock area (left-hand side) became empty by the end of the simulation. Now it gets replenished (receives new cargo from some external source).

Also, the right-hand side dock area can now ship its cargo.

3. Display service level and utilization

In the SimulationStatisticsPart class, add new indicators in the onShowModel() method (inside the List.of() call):

SimulationStatisticsPart.java
// ...
    new Indicator("Successful receipts", () -> (double) model.getSuccessfulReceipts(), Formats.getDefaultFormats()::noDecimals, false),
    new Indicator("Receipts attempted", () -> (double) model.getReceiptAttempts(), Formats.getDefaultFormats()::noDecimals, false),
    new Indicator("Successful shipments", () -> (double) model.getSuccessfulShipments(), Formats.getDefaultFormats()::noDecimals, false),
    new Indicator("Shipments attempted", () -> (double) model.getShipmentAttempts(), Formats.getDefaultFormats()::noDecimals, false),
    new Indicator("Service level", () -> model.getServiceLevel(), Formats.getDefaultFormats()::percent, true),
    new Indicator("Forklift utilization", () -> model.getUtilization(), Formats.getDefaultFormats()::percent, true)
// ...

New statistics items are added and shown in the bottom left corner in the simulation mode:

Updated statistics in summary statistics
Figure 1. Updated statistics in the statistics part (bottom left)

In the SimulationStatusShape class, add the following two new TextShapes (see class constructor):

SimulationStatusShape.java
        withShape(new TextShape(() -> "Service level: %s".formatted(Formats.getDefaultFormats().percent(model.getServiceLevel())))
                .withPoint(new Point(10, 55))
                .withFontColor(Colors.DARK_GREEN)
                .withFontSize(18)
                .withHorizontalAlignment(HorizontalAlignment.RIGHT)
                .withVerticalAlignment(VerticalAlignment.BOTTOM));

        withShape(new TextShape(() -> "Utilization: %s".formatted(Formats.getDefaultFormats().percent(model.getUtilization())))
                .withPoint(new Point(10, 100))
                .withFontColor(Colors.DARK_SLATE_BLUE)
                .withFontSize(18)
                .withHorizontalAlignment(HorizontalAlignment.RIGHT)
                .withVerticalAlignment(VerticalAlignment.BOTTOM));

Add the missing import statements.

The two new items are displayed in the top left part int the simulation mode:

Updated statistics in simulation status
Figure 2. Updated statistics in the simulation status shape (top left)