diff --git a/src/departures.cpp b/src/departures.cpp index 133ea4e9d1..c1b84d6867 100644 --- a/src/departures.cpp +++ b/src/departures.cpp @@ -30,12 +30,21 @@ #include "order_base.h" #include "settings_type.h" #include "core/smallvec_type.hpp" +#include "core/sort_func.hpp" #include "date_type.h" #include "company_type.h" #include "cargo_type.h" #include "departures_func.h" #include "departures_type.h" +#include +#include +#include +#include + +/* A cache of used departure time for scheduled dispatch in departure time calculation */ +typedef std::map> schdispatch_cache_t; + /** A scheduled order. */ typedef struct OrderDate { @@ -44,6 +53,7 @@ typedef struct OrderDate DateTicks expected_date;///< The date on which the order is expected to complete Ticks lateness; ///< How late this order is expected to finish DepartureStatus status; ///< Whether the vehicle has arrived to carry out the order yet + uint scheduled_waiting_time; ///< Scheduled waiting time if scheduled dispatch is used } OrderDate; static bool IsDeparture(const Order *order, StationID station) { @@ -70,6 +80,119 @@ static bool IsArrival(const Order *order, StationID station) { order->GetWaitTime() != 0); } +static inline bool VehicleSetNextDepartureTime(DateTicks *previous_departure, uint *waiting_time, const DateTicksScaled date_only_scaled, const Vehicle *v, const Order *order, bool arrived_at_timing_point, schdispatch_cache_t &dept_schedule_last) +{ + if (HasBit(v->vehicle_flags, VF_SCHEDULED_DISPATCH)) { + /* Loop over all order to find the first waiting order */ + for (int j = 0; j < v->orders.list->GetNumOrders(); ++j) { + Order* iterating_order = v->orders.list->GetOrderAt(j); + + if (iterating_order->IsWaitTimetabled() && !iterating_order->IsType(OT_IMPLICIT)) { + /* This condition means that we want departure time for the first order */ + /* but not if the vehicle has arrived at the first order because the timetable is already shifted */ + if (iterating_order == order && !(arrived_at_timing_point && v->cur_implicit_order_index == j)) { + DateTicksScaled actual_departure = -1; + const DateTicksScaled begin_time = v->orders.list->GetScheduledDispatchStartTick(); + const uint32 dispatch_duration = v->orders.list->GetScheduledDispatchDuration(); + const int32 max_delay = v->orders.list->GetScheduledDispatchDelay(); + + /* Earliest possible departure according to schedue */ + DateTicksScaled earliest_departure = begin_time + v->orders.list->GetScheduledDispatchLastDispatch(); + + /* Earliest possible departure according to vehicle current timetable */ + if (earliest_departure + max_delay < date_only_scaled + *previous_departure + order->GetTravelTime()) { + earliest_departure = date_only_scaled + *previous_departure + order->GetTravelTime() - max_delay - 1; + /* -1 because this number is actually a moment before actual departure */ + } + + /* Find next available slots */ + for (auto current_offset : v->orders.list->GetScheduledDispatch()) { + DateTicksScaled current_departure = begin_time + current_offset; + while (current_departure <= earliest_departure) { + current_departure += dispatch_duration; + } + + /* Make sure the slots has not already been used previously in this departure board calculation */ + while (dept_schedule_last[v->orders.list->index].count(current_departure) > 0) { + current_departure += dispatch_duration; + } + + if (actual_departure == -1 || actual_departure > current_departure) { + actual_departure = current_departure; + } + } + + *waiting_time = order->GetWaitTime() + actual_departure - date_only_scaled - *previous_departure - order->GetTravelTime(); + *previous_departure = actual_departure - date_only_scaled + order->GetWaitTime(); + dept_schedule_last[v->orders.list->index].insert(actual_departure); + + /* Return true means that vehicle lateness should be clear from this point onward */ + return true; + } + + /* This is special case for proper calculation of arrival time. */ + if (arrived_at_timing_point && v->cur_implicit_order_index == j) { + *previous_departure += order->GetTravelTime() + order->GetWaitTime(); + *waiting_time = -v->lateness_counter + order->GetWaitTime(); + return false; + } + break; + } /* if it is first waiting order */ + } /* for in order list */ + } /* if vehicle is on scheduled dispatch */ + + /* Not using schedule for this departure time */ + *previous_departure += order->GetTravelTime() + order->GetWaitTime(); + *waiting_time = 0; + return false; +} + +static void ScheduledDispatchDepartureLocalFix(DepartureList *departure_list) +{ + /* Seperate departure by each shared order group */ + std::map> separated_departure; + for (Departure** departure = departure_list->Begin(); departure != departure_list->End(); departure++) { + separated_departure[(*departure)->vehicle->orders.list->index].push_back(*departure); + } + + for (auto& pair : separated_departure) { + auto d_list = pair.second; + + /* If the group is scheduled dispatch, then */ + if (HasBit(d_list[0]->vehicle->vehicle_flags, VF_SCHEDULED_DISPATCH)) { + /* Separate departure time and sort them ascendently */ + std::vector departure_time_list; + for (const auto& d : d_list) { + departure_time_list.push_back(d->scheduled_date); + } + std::sort(departure_time_list.begin(), departure_time_list.end()); + + /* Sort the departure list by arrival time */ + std::sort(d_list.begin(), d_list.end(), [](const Departure * const &a, const Departure * const &b) -> bool { + DateTicksScaled arr_a = a->scheduled_date - (a->scheduled_waiting_time > 0 ? a->scheduled_waiting_time : a->order->GetWaitTime()); + DateTicksScaled arr_b = b->scheduled_date - (b->scheduled_waiting_time > 0 ? b->scheduled_waiting_time : b->order->GetWaitTime()); + return arr_a < arr_b; + }); + + /* Re-assign them sequentially */ + for (size_t i = 0; i < d_list.size(); i++) { + const DateTicksScaled arrival = d_list[i]->scheduled_date - (d_list[i]->scheduled_waiting_time > 0 ? d_list[i]->scheduled_waiting_time : d_list[i]->order->GetWaitTime()); + d_list[i]->scheduled_waiting_time = departure_time_list[i] - arrival; + d_list[i]->scheduled_date = departure_time_list[i]; + + if (d_list[i]->scheduled_waiting_time == d_list[i]->order->GetWaitTime()) { + d_list[i]->scheduled_waiting_time = 0; + } + } + } + } + + /* Re-sort the departure list */ + QSortT(departure_list->Begin(), departure_list->Length(), [](Departure * const *a, Departure * const *b) -> int { + return (*a)->scheduled_date - (*b)->scheduled_date; + }); +} + /** * Compute an up-to-date list of departures for a station. * @param station the station to compute the departures of @@ -102,6 +225,9 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], /* The scheduled order in next_orders with the earliest expected_date field. */ OrderDate *least_order = NULL; + /* Cache for scheduled departure time */ + schdispatch_cache_t schdispatch_last_planned_dispatch; + /* Get all the vehicles stopping at this station. */ /* We do this to get the order which is the first time they will stop at this station. */ /* This order is stored along with some more information. */ @@ -141,8 +267,10 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], } const Order *order = (*v)->GetOrder((*v)->cur_implicit_order_index % (*v)->GetNumOrders()); - DateTicksScaled start_date = date_fract_scaled - (*v)->current_order_time; + DateTicks start_date = date_fract_scaled - (*v)->current_order_time; DepartureStatus status = D_TRAVELLING; + bool should_reset_lateness = false; + uint waiting_time = 0; /* If the vehicle is stopped in a depot, ignore it. */ if ((*v)->IsStoppedInDepot()) { @@ -163,7 +291,9 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], /* Loop through the vehicle's orders until we've found a suitable order or we've determined that no such order exists. */ /* We only need to consider each order at most once. */ for (int i = (*v)->GetNumOrders(); i > 0; --i) { - start_date += order->GetTravelTime() + order->GetWaitTime(); + if (VehicleSetNextDepartureTime(&start_date, &waiting_time, date_only_scaled, *v, order, status == D_ARRIVED, schdispatch_last_planned_dispatch)) { + should_reset_lateness = true; + } /* If the scheduled departure date is too far in the future, stop. */ if (start_date - (*v)->lateness_counter > max_date) { @@ -230,16 +360,23 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], od->expected_date = start_date; od->lateness = (*v)->lateness_counter > 0 ? (*v)->lateness_counter : 0; od->status = status; + od->scheduled_waiting_time = waiting_time; + + /* Reset lateness if timing is from scheduled dispatch */ + if (should_reset_lateness) { + od->lateness = 0; + } /* If we are early, use the scheduled date as the expected date. We also take lateness to be zero. */ - if ((*v)->lateness_counter < 0 && !(*v)->current_order.IsType(OT_LOADING)) { + if (!should_reset_lateness && (*v)->lateness_counter < 0 && !(*v)->current_order.IsType(OT_LOADING)) { od->expected_date -= (*v)->lateness_counter; } /* Update least_order if this is the current least order. */ if (least_order == NULL) { least_order = od; - } else if (least_order->expected_date - least_order->lateness - (type == D_ARRIVAL ? least_order->order->GetWaitTime() : 0) > od->expected_date - od->lateness - (type == D_ARRIVAL ? od->order->GetWaitTime() : 0)) { + } else if (int(least_order->expected_date - least_order->lateness - (type == D_ARRIVAL ? (least_order->scheduled_waiting_time > 0 ? least_order->scheduled_waiting_time : least_order->order->GetWaitTime()) : 0)) > int(od->expected_date - od->lateness - (type == D_ARRIVAL ? (od->scheduled_waiting_time > 0 ? od->scheduled_waiting_time : od->order->GetWaitTime()) : 0))) { + /* Somehow my compiler perform an unsigned comparition above so integer cast is required */ least_order = od; } @@ -289,6 +426,7 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], d->vehicle = least_order->v; d->type = type; d->order = least_order->order; + d->scheduled_waiting_time = least_order->scheduled_waiting_time; /* We'll be going through the order list later, so we need a separate variable for it. */ const Order *order = least_order->order; @@ -366,7 +504,7 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], } if (c.scheduled_date != 0 && (order->GetTravelTime() != 0 || order->IsTravelTimetabled())) { - c.scheduled_date += order->GetTravelTime(); + c.scheduled_date += order->GetTravelTime(); /* TODO smart terminal may not work correctly */ } else { c.scheduled_date = 0; } @@ -467,7 +605,7 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], /* Again, we define a station as being called at if the vehicle loads from it. */ /* However, the very first thing we do is use the arrival time as the scheduled time instead of the departure time. */ - d->scheduled_date -= order->GetWaitTime(); + d->scheduled_date -= d->scheduled_waiting_time > 0 ? d->scheduled_waiting_time : order->GetWaitTime(); const Order *candidate_origin = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; bool found_origin = false; @@ -549,7 +687,9 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], /* Go to the next order so we don't add the current order again. */ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; - least_order->expected_date += order->GetTravelTime() + order->GetWaitTime(); + if (VehicleSetNextDepartureTime(&least_order->expected_date, &least_order->scheduled_waiting_time, date_only_scaled, least_order->v, order, false, schdispatch_last_planned_dispatch)) { + least_order->lateness = 0; + } /* Go through the order list to find the next candidate departure. */ /* We only need to consider each order at most once. */ @@ -569,14 +709,18 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], break; } - least_order->expected_date += order->GetWaitTime(); + if (VehicleSetNextDepartureTime(&least_order->expected_date, &least_order->scheduled_waiting_time, date_only_scaled, least_order->v, order, false, schdispatch_last_planned_dispatch)) { + least_order->lateness = 0; + } continue; } case 2: { /* Do not take the branch */ order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; - least_order->expected_date += order->GetTravelTime() + order->GetWaitTime(); + if (VehicleSetNextDepartureTime(&least_order->expected_date, &least_order->scheduled_waiting_time, date_only_scaled, least_order->v, order, false, schdispatch_last_planned_dispatch)) { + least_order->lateness = 0; + } continue; } } @@ -608,7 +752,9 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], } order = (order->next == NULL) ? least_order->v->GetFirstOrder() : order->next; - least_order->expected_date += order->GetTravelTime() + order->GetWaitTime(); + if (VehicleSetNextDepartureTime(&least_order->expected_date, &least_order->scheduled_waiting_time, date_only_scaled, least_order->v, order, false, schdispatch_last_planned_dispatch)) { + least_order->lateness = 0; + } } /* If we didn't find a suitable order for being a departure, then we can ignore this vehicle from now on. */ @@ -632,8 +778,8 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], DateTicks odd = od->expected_date - od->lateness; if (type == D_ARRIVAL) { - lod -= least_order->order->GetWaitTime(); - odd -= od->order->GetWaitTime(); + lod -= least_order->scheduled_waiting_time > 0 ? least_order->scheduled_waiting_time : least_order->order->GetWaitTime(); + odd -= od->scheduled_waiting_time > 0 ? od->scheduled_waiting_time : od->order->GetWaitTime(); } if (lod > odd && od->expected_date - od->lateness < max_date) { @@ -648,6 +794,8 @@ DepartureList* MakeDepartureList(StationID station, bool show_vehicle_types[5], delete od; } + if (type == D_DEPARTURE) ScheduledDispatchDepartureLocalFix(result); + /* Done. Phew! */ return result; } diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp index c4ac0c5e4a..25305d772b 100644 --- a/src/departures_gui.cpp +++ b/src/departures_gui.cpp @@ -606,7 +606,7 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const /* Time */ SetDParam(0, d->scheduled_date); - SetDParam(1, d->scheduled_date - d->order->GetWaitTime()); + SetDParam(1, d->scheduled_date - (d->scheduled_waiting_time > 0 ? d->scheduled_waiting_time : d->order->GetWaitTime())); ltr ? DrawString( text_left, text_left + time_width, y + 1, time_str) : DrawString(text_right - time_width, text_right, y + 1, time_str); diff --git a/src/departures_type.h b/src/departures_type.h index b71ea62d7e..8ee178e140 100644 --- a/src/departures_type.h +++ b/src/departures_type.h @@ -74,6 +74,7 @@ typedef struct Departure { DepartureType type; ///< The type of the departure (departure or arrival) const Vehicle *vehicle; ///< The vehicle performing this departure const Order *order; ///< The order corresponding to this departure + uint scheduled_waiting_time; ///< Scheduled waiting time if scheduled dispatch is used Departure() : terminus(INVALID_STATION), via(INVALID_STATION), calling_at(), vehicle(NULL) { } ~Departure() { diff --git a/src/lang/english.txt b/src/lang/english.txt index 3dfa4453ff..6c854ba938 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5829,7 +5829,7 @@ STR_SCHDISPATCH_START :{BLACK}Start Da STR_SCHDISPATCH_START_TOOLTIP :{BLACK}Select a date to start this schedule. STR_SCHDISPATCH_START_CAPTION_MINUTE :{BLACK}Start time (hhmm) STR_SCHDISPATCH_DELAY :{BLACK}Delay -STR_SCHDISPATCH_DELAY_TOOLTIP :{BLACK}Select a date to start this schedule. +STR_SCHDISPATCH_DELAY_TOOLTIP :{BLACK}Set maximum delay until a slot is skipped. STR_SCHDISPATCH_DELAY_CAPTION_MINUTE :{BLACK}Delay (minute) STR_SCHDISPATCH_DELAY_CAPTION_DAY :{BLACK}Delay (day) STR_SCHDISPATCH_RESET_LAST_DISPATCH :{BLACK}Reset Last Dispatched diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index c55ef402e1..453245a35d 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1132,6 +1132,11 @@ void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord) static CommandCost DecloneOrder(Vehicle *dst, DoCommandFlag flags) { if (flags & DC_EXEC) { + /* Clear cheduled dispatch flag if any */ + if (HasBit(dst->vehicle_flags, VF_SCHEDULED_DISPATCH)) { + ClrBit(dst->vehicle_flags, VF_SCHEDULED_DISPATCH); + } + DeleteVehicleOrders(dst); InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS); InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0);