Departures: Allow timetabled depot orders to be calling points

This commit is contained in:
Jonathan G Rennison 2024-09-10 19:56:03 +01:00
parent 27f8cfba22
commit 7fd1d5e54d
4 changed files with 113 additions and 65 deletions

View File

@ -45,6 +45,13 @@ static constexpr Ticks INVALID_DEPARTURE_TICKS = INT32_MIN;
using ScheduledDispatchCache = btree::btree_map<const DispatchSchedule *, btree::btree_set<StateTicks>>;
using ScheduledDispatchVehicleRecords = btree::btree_map<std::pair<uint, VehicleID>, LastDispatchRecord>;
CallAtTargetID CallAtTargetID::FromOrder(const Order *order)
{
uint32_t id = order->GetDestination();
if (order->IsType(OT_GOTO_DEPOT)) id |= DEPOT_TAG;
return CallAtTargetID(id);
}
struct ArrivalHistoryEntry {
const Order *order;
Ticks offset;
@ -323,7 +330,7 @@ static void ScheduledDispatchDepartureLocalFix(DepartureList &departure_list)
static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_list, Ticks loop_duration = 0)
{
btree::btree_map<StationID, StateTicks> earliest_seen;
btree::btree_map<CallAtTargetID, StateTicks> earliest_seen;
auto check_departure = [&](Departure *d) {
size_t calling_at_size = d->calling_at.size();
@ -336,12 +343,12 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis
while (calling_at_size >= 2) {
if (d->terminus.scheduled_tick != 0) {
auto iter = earliest_seen.find(d->terminus.station);
auto iter = earliest_seen.find(d->terminus.target);
if (iter != earliest_seen.end() && iter->second <= d->terminus.scheduled_tick) {
/* Terminus can be reached at same or earlier time on a later vehicle */
calling_at_size--;
size_t new_terminus_offset = calling_at_size - 1;
d->terminus = CallAt(d->calling_at[new_terminus_offset]);
d->terminus = d->calling_at[new_terminus_offset];
auto remove_via = [&](StationID st) {
if (d->via2 == st) d->via2 = INVALID_STATION;
@ -350,7 +357,9 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis
d->via2 = INVALID_STATION;
}
};
remove_via(d->terminus.station);
if (d->terminus.target.IsStationID()) {
remove_via(d->terminus.target.GetStationID());
}
for (const RemoveVia &rv : d->remove_vias) {
if (rv.calling_at_offset == new_terminus_offset) {
remove_via(rv.via);
@ -364,7 +373,7 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis
for (const CallAt &c : d->calling_at) {
if (c.scheduled_tick != 0) {
StateTicks &seen = earliest_seen[c.station];
StateTicks &seen = earliest_seen[c.target];
if (seen == 0 || c.scheduled_tick < seen) seen = c.scheduled_tick;
}
}
@ -571,10 +580,11 @@ static void GetDepartureCandidateOrderDatesFromVehicle(std::vector<OrderDate> &n
}
}
static bool IsStationIDCallingPointOrder(const Order *order)
static bool IsCallingPointTargetOrder(const Order *order)
{
if ((order->IsType(OT_GOTO_STATION) || order->IsType(OT_IMPLICIT)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) return true;
if (order->IsType(OT_GOTO_WAYPOINT) && order->IsWaitTimetabled()) return true;
if (order->IsType(OT_GOTO_DEPOT) && ((order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) && (order->IsWaitTimetabled() || (order->GetDepotActionType() & ODATFB_HALT) != 0)) return true;
return false;
}
@ -636,9 +646,9 @@ bool DepartureViaTerminusState::CheckOrder(const Vehicle *v, Departure *d, const
bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *order, CallAt c, DepartureCallingSettings calling_settings)
{
if (!IsStationIDCallingPointOrder(order)) return false;
if (!IsCallingPointTargetOrder(order)) return false;
if (order->IsType(OT_GOTO_WAYPOINT)) {
if (order->IsType(OT_GOTO_WAYPOINT) || order->IsType(OT_GOTO_DEPOT)) {
if (!calling_settings.ShowAllStops()) return false;
} else {
if (!calling_settings.ShowAllStops() && order->GetUnloadType() == OUFB_NO_UNLOAD) return false;
@ -650,6 +660,11 @@ bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *or
return true;
}
d->terminus = c;
d->calling_at.push_back(c);
if (order->IsType(OT_GOTO_DEPOT)) return (order->GetDepotActionType() & ODATFB_HALT) != 0;
/* Add the station to the calling at list and make it the candidate terminus. */
if (d->via == INVALID_STATION && pending_via != INVALID_STATION) {
d->via = this->pending_via;
@ -658,8 +673,6 @@ bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *or
if (d->via == INVALID_STATION && this->candidate_via == (StationID)order->GetDestination()) {
d->via = (StationID)order->GetDestination();
}
d->terminus = c;
d->calling_at.push_back(c);
/* If we unload all at this station and departure load tests are not disabled, then it is the terminus. */
if (order->GetType() == OT_GOTO_STATION && order->GetUnloadType() == OUFB_UNLOAD && !calling_settings.DepartureNoLoadTest()) {
@ -692,7 +705,7 @@ static bool ProcessArrivalHistory(Departure *d, std::span<ArrivalHistoryEntry> a
for (uint i = 0; i < (uint)arrival_history.size(); i++) {
const Order *o = arrival_history[i].order;
if (IsStationIDCallingPointOrder(o)) {
if (IsCallingPointTargetOrder(o)) {
if (source.StationMatches(o->GetDestination())) {
/* Same as source order, remove all possible origins */
possible_origins.clear();
@ -707,7 +720,7 @@ static bool ProcessArrivalHistory(Departure *d, std::span<ArrivalHistoryEntry> a
}
}
if (o->IsType(OT_GOTO_WAYPOINT)) {
if (o->IsType(OT_GOTO_WAYPOINT) || o->IsType(OT_GOTO_DEPOT)) {
if (calling_settings.ShowAllStops()) possible_origins.push_back({ o->GetDestination(), i });
} else {
if (calling_settings.ShowAllStops() || o->GetLoadType() != OLFB_NO_LOAD) possible_origins.push_back({ o->GetDestination(), i });
@ -742,19 +755,19 @@ static bool ProcessArrivalHistory(Departure *d, std::span<ArrivalHistoryEntry> a
auto make_call_at = [&](const ArrivalHistoryEntry &entry) -> CallAt {
if (entry.offset == INVALID_DEPARTURE_TICKS) {
return CallAt((StationID)entry.order->GetDestination());
return CallAt(entry.order);
} else {
return CallAt((StationID)entry.order->GetDestination(), entry.offset + arrival_offset);
return CallAt(entry.order, entry.offset + arrival_offset);
}
};
for (uint i = origin_arrival_history_index + 1; i < (uint)arrival_history.size(); i++) {
const Order *o = arrival_history[i].order;
if (IsStationIDCallingPointOrder(o)) {
if (IsCallingPointTargetOrder(o)) {
check_order(o);
if (o->IsType(OT_GOTO_STATION) && (o->GetLoadType() != OLFB_NO_LOAD || calling_settings.ShowAllStops())) {
d->calling_at.push_back(make_call_at(arrival_history[i]));
} else if (o->IsType(OT_GOTO_WAYPOINT) && calling_settings.ShowAllStops()) {
} else if ((o->IsType(OT_GOTO_WAYPOINT) || o->IsType(OT_GOTO_DEPOT))&& calling_settings.ShowAllStops()) {
d->calling_at.push_back(make_call_at(arrival_history[i]));
}
}
@ -880,7 +893,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector
order = lod.v->orders->GetNext(order);
StateTicks departure_tick = d->scheduled_tick;
bool travel_time_required = true;
CallAt c = CallAt((StationID)order->GetDestination(), departure_tick);
CallAt c = CallAt(order, departure_tick);
for (uint i = order_iteration_limit; i > 0; --i) {
/* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */
if (order == lod.order) {
@ -927,7 +940,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector
}
if (c.scheduled_tick != 0) c.scheduled_tick = departure_tick;
c.station = (StationID)order->GetDestination();
c.target = CallAtTargetID::FromOrder(order);
/* We're not interested in this order any further if we're not calling at it. */
if (via_state.HandleCallingPoint(d, order, c, calling_settings)) break;
@ -1013,7 +1026,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector
cumul += o->GetTravelTime() + o->GetWaitTime();
if (o->GetType() == OT_GOTO_STATION || o->GetType() == OT_IMPLICIT || (o->IsType(OT_GOTO_WAYPOINT) && o->IsWaitTimetabled())) {
if (IsCallingPointTargetOrder(o)) {
new_history.push_back({ o, cumul });
}
}
@ -1266,7 +1279,7 @@ void DepartureListScheduleModeSlotEvaluator::EvaluateFromSourceOrder(const Order
order = this->v->orders->GetNext(order);
bool travel_time_required = true;
CallAt c = CallAt((StationID)order->GetDestination(), departure_tick);
CallAt c = CallAt(order, departure_tick);
for (uint i = order_iteration_limit; i > 0; --i) {
/* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */
if (order == source_order) {
@ -1313,7 +1326,7 @@ void DepartureListScheduleModeSlotEvaluator::EvaluateFromSourceOrder(const Order
c.scheduled_tick = 0;
}
if (c.scheduled_tick != 0) c.scheduled_tick = departure_tick;
c.station = (StationID)order->GetDestination();
c.target = CallAtTargetID::FromOrder(order);
/* We're not interested in this order any further if we're not calling at it. */
if (via_state.HandleCallingPoint(&d, order, c, this->calling_settings)) break;
@ -1579,8 +1592,8 @@ static DepartureList MakeDepartureListScheduleMode(DepartureOrderDestinationDete
std::sort(result.begin(), result.end(), [](std::unique_ptr<Departure> &a, std::unique_ptr<Departure> &b) -> bool {
if (a->scheduled_tick == b->scheduled_tick) {
return std::tie(a->terminus.station, a->terminus.scheduled_tick, a->vehicle->index)
< std::tie(b->terminus.station, b->terminus.scheduled_tick, b->vehicle->index);
return std::tie(a->terminus.target, a->terminus.scheduled_tick, a->vehicle->index)
< std::tie(b->terminus.target, b->terminus.scheduled_tick, b->vehicle->index);
}
return a->scheduled_tick < b->scheduled_tick;
});

View File

@ -821,15 +821,21 @@ uint DeparturesWindow::GetMinWidth() const
return result + ScaleGUITrad(140);
}
/* Uses 2 parameters */
static void FillBaseStationDParam(size_t n, StationID id)
/* Uses 3 parameters */
static void FillCallingAtTargetDParam(size_t n, const Departure *d, CallAtTargetID target)
{
if (Waypoint::IsValidID(id)) {
SetDParam(n, STR_WAYPOINT_NAME);
if (target.IsStationID()) {
if (Waypoint::IsValidID(target.GetStationID())) {
SetDParam(n, STR_WAYPOINT_NAME);
} else {
SetDParam(n, STR_STATION_NAME);
}
SetDParam(n + 1, target.GetStationID());
} else {
SetDParam(n, STR_STATION_NAME);
SetDParam(n, STR_DEPOT_NAME);
SetDParam(n + 1, d->vehicle->type);
SetDParam(n + 2, target.GetDepotDestinationID());
}
SetDParam(n + 1, id);
}
/**
@ -938,7 +944,7 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
continue;
}
if (d->terminus == INVALID_STATION) continue;
if (!d->terminus.IsValid()) continue;
if (time_width > 0) {
StringID time_str;
@ -1011,10 +1017,12 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
/* The icons to show with the destination and via stations. */
StringID icon = STR_DEPARTURES_STATION_NONE;
if (_settings_client.gui.departure_destination_type) {
Station *t = Station::Get(d->terminus.station);
if (_settings_client.gui.departure_destination_type && d->terminus.target.IsStationID()) {
Station *t = Station::GetIfValid(d->terminus.target.GetStationID());
if (t->facilities & FACIL_DOCK &&
if (t == nullptr) {
/* No icon change */
} else if (t->facilities & FACIL_DOCK &&
t->facilities & FACIL_AIRPORT &&
d->vehicle->type != VEH_SHIP &&
d->vehicle->type != VEH_AIRCRAFT) {
@ -1030,11 +1038,11 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
StationID via = d->via;
StationID via2 = d->via2;
if (via == d->terminus.station || this->source.StationMatches(via)) {
if (d->terminus.target.MatchesStationID(via) || this->source.StationMatches(via)) {
via = via2;
via2 = INVALID_STATION;
}
if (via2 == d->terminus.station || this->source.StationMatches(via2)) via2 = INVALID_STATION;
if (d->terminus.target.MatchesStationID(via2) || this->source.StationMatches(via2)) via2 = INVALID_STATION;
/* Arrival time */
if (arrival_time_width != 0 && d->terminus.scheduled_tick != 0) {
@ -1057,8 +1065,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
if (via == INVALID_STATION) {
/* Only show the terminus. */
FillBaseStationDParam(0, d->terminus.station);
SetDParam(2, icon);
FillCallingAtTargetDParam(0, d, d->terminus.target);
SetDParam(3, icon);
DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS);
} else {
auto set_via_dparams = [&](uint offset) {
@ -1096,16 +1104,16 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
SetDParam(offset, SPECSTR_TEMP_START);
};
/* Show the terminus and the via station. */
FillBaseStationDParam(0, d->terminus.station);
SetDParam(2, icon);
set_via_dparams(3);
FillCallingAtTargetDParam(0, d, d->terminus.target);
SetDParam(3, icon);
set_via_dparams(4);
int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width;
if (dest_left + text_width < dest_right) {
/* They will both fit, so show them both. */
FillBaseStationDParam(0, d->terminus.station);
SetDParam(2, icon);
set_via_dparams(3);
FillCallingAtTargetDParam(0, d, d->terminus.target);
SetDParam(3, icon);
set_via_dparams(4);
DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION);
} else {
/* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
@ -1113,8 +1121,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
set_via_dparams(0);
DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_VIA);
} else {
FillBaseStationDParam(0, d->terminus.station);
SetDParam(2, icon);
FillCallingAtTargetDParam(0, d, d->terminus.target);
SetDParam(3, icon);
DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS_VIA);
}
this->scroll_refresh = true;
@ -1197,23 +1205,23 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
/* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
std::string buffer;
/* Uses 3 or 4 parameters */
/* Uses 4 or 5 parameters */
auto fill_calling_at_dparam = [&](size_t n, const CallAt &c) {
if (c.scheduled_tick != 0 && arrival_time_width > 0) {
SetDParam(n, STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME);
n++;
}
FillBaseStationDParam(n, c.station);
SetDParam(n + 2, c.scheduled_tick);
FillCallingAtTargetDParam(n, d, c.target);
SetDParam(n + 3, c.scheduled_tick);
};
if (d->calling_at.size() != 0) {
fill_calling_at_dparam(0, d->calling_at[0]);
std::string calling_at_buffer = GetString(STR_JUST_STRING3);
std::string calling_at_buffer = GetString(STR_JUST_STRING4);
const CallAt *continues_to = nullptr;
if (d->calling_at[0].station == d->terminus.station && d->calling_at.size() > 1) {
if (d->calling_at[0].target == d->terminus.target && d->calling_at.size() > 1) {
continues_to = &(d->calling_at[d->calling_at.size() - 1]);
} else if (d->calling_at.size() > 1) {
/* There's more than one stop. */
@ -1221,8 +1229,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
uint i;
/* For all but the last station, write out ", <station>". */
for (i = 1; i < d->calling_at.size() - 1; ++i) {
StationID s = d->calling_at[i].station;
if (s == d->terminus.station) {
CallAtTargetID target = d->calling_at[i].target;
if (target == d->terminus.target) {
continues_to = &(d->calling_at[d->calling_at.size() - 1]);
break;
}

View File

@ -39,17 +39,44 @@ enum DeparturesSourceMode : uint8_t {
DSM_END
};
struct CallAtTargetID {
private:
static constexpr uint32_t DEPOT_TAG = 1 << 31;
uint32_t id;
constexpr CallAtTargetID(uint32_t id) : id(id) {}
public:
constexpr CallAtTargetID() : id(INVALID_STATION) {}
static CallAtTargetID FromOrder(const Order *order);
static constexpr CallAtTargetID FromStation(StationID station) { return CallAtTargetID(station); }
inline bool IsValid() const { return id != INVALID_STATION; }
inline bool IsStationID() const { return (id & DEPOT_TAG) == 0; }
inline StationID GetStationID() const { return (StationID)this->id; }
inline DestinationID GetDepotDestinationID() const { return this->id & ~DEPOT_TAG; }
inline bool MatchesStationID(StationID st) const { return this->IsStationID() && st == this->GetStationID(); }
bool operator==(const CallAtTargetID& c) const = default;
auto operator<=>(const CallAtTargetID& c) const = default;
};
struct CallAt {
StationID station;
CallAtTargetID target;
StateTicks scheduled_tick;
CallAt(const StationID& s) : station(s), scheduled_tick(0) { }
CallAt(const StationID& s, StateTicks t) : station(s), scheduled_tick(t) { }
CallAt(const CallAt& c) : station(c.station), scheduled_tick(c.scheduled_tick) { }
CallAt(CallAtTargetID target) : target(target), scheduled_tick(0) {}
CallAt(CallAtTargetID target, StateTicks t) : target(target), scheduled_tick(t) {}
CallAt(const Order *order) : target(CallAtTargetID::FromOrder(order)), scheduled_tick(0) {}
CallAt(const Order *order, StateTicks t) : target(CallAtTargetID::FromOrder(order)), scheduled_tick(t) {}
inline bool IsValid() const { return this->target.IsValid(); }
inline bool operator==(const CallAt& c) const
{
return this->station == c.station;
return this->target == c.target;
}
CallAt& operator=(const CallAt& c) = default;
@ -72,7 +99,7 @@ struct Departure {
Ticks lateness = 0; ///< How delayed the departure is expected to be
StationID via = INVALID_STATION; ///< The station the departure should list as going via
StationID via2 = INVALID_STATION; ///< Secondary station the departure should list as going via
CallAt terminus = INVALID_STATION; ///< The station at which the vehicle will terminate following this departure
CallAt terminus = CallAtTargetID(); ///< The station at which the vehicle will terminate following this departure
std::vector<CallAt> calling_at; ///< The stations both called at and unloaded at by the vehicle after this departure before it terminates
std::vector<RemoveVia> remove_vias; ///< Vias to remove when using smart terminus.
DepartureStatus status{}; ///< Whether the vehicle has arrived yet for this departure

View File

@ -1368,19 +1368,19 @@ STR_DEPARTURES_TIME :{COLOUR}{STRING
STR_DEPARTURES_TIME_DEP :{COLOUR}{STRING1} {GREEN}{UP_ARROW}
STR_DEPARTURES_TIME_ARR :{COLOUR}{STRING1} {RED}{DOWN_ARROW}
STR_DEPARTURES_TIME_BOTH :{COLOUR}{STRING1} {RED}{DOWN_ARROW} {COLOUR}{STRING1} {GREEN}{UP_ARROW}
STR_DEPARTURES_TERMINUS :{ORANGE}{STRING1}{STRING}
STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STRING1}{STRING} via {STRING}
STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STRING1}{STRING} via
STR_DEPARTURES_TERMINUS :{ORANGE}{STRING2}{STRING}
STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STRING2}{STRING} via {STRING}
STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STRING2}{STRING} via
STR_DEPARTURES_VIA :{ORANGE}via {STRING}
STR_DEPARTURES_TOC :{ORANGE}{COMPANY}
STR_DEPARTURES_GROUP :{ORANGE}{GROUP}
STR_DEPARTURES_VEH :{ORANGE}{VEHICLE}
STR_DEPARTURES_CALLING_AT :{ORANGE}Calling at:
STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STRING3}
STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STRING3}
STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STRING4}
STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STRING4}
STR_DEPARTURES_CALLING_AT_LIST :{ORANGE}{RAW_STRING}.
STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{ORANGE}{RAW_STRING}. This service continues to {STRING3}.
STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME :{STRING1} ({TT_TIME_ABS})
STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{ORANGE}{RAW_STRING}. This service continues to {STRING4}.
STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME :{STRING2} ({TT_TIME_ABS})
STR_DEPARTURES_TINY :{TINY_FONT}{STRING2}
STR_DEPARTURES_VIA_DESCRIPTOR :{STRING1}{STRING}
STR_DEPARTURES_VIA_AND :{STRING} and {STRING}