From 57d4f52c151a02ea57ba77f20ba12ae3309d045e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sat, 2 Dec 2023 17:52:26 +0000 Subject: [PATCH] Link graph: Use timetable for order-based link refresh travel time estimate --- src/linkgraph/refresh.cpp | 104 +++++++++++++++++++++++++++++++++----- src/linkgraph/refresh.h | 20 ++++++-- src/station_func.h | 1 - 3 files changed, 109 insertions(+), 16 deletions(-) diff --git a/src/linkgraph/refresh.cpp b/src/linkgraph/refresh.cpp index 9414cdf1c6..aad56a8459 100644 --- a/src/linkgraph/refresh.cpp +++ b/src/linkgraph/refresh.cpp @@ -58,7 +58,7 @@ uint8 flags = 0; if (iter_cargo_mask & have_cargo_mask) flags |= 1 << HAS_CARGO; if (v->type == VEH_AIRCRAFT) flags |= 1 << AIRCRAFT; - refresher.RefreshLinks(first, first, flags); + refresher.RefreshLinks(first, first, { 0, TTT_NO_WAIT_TIME }, flags); } cargo_mask &= ~iter_cargo_mask; @@ -161,16 +161,74 @@ void LinkRefresher::ResetRefit() } } +/** + * Update the linear timetable travel time with the times between two orders. + * The caller is responsible for ensuring that these orders are in a linear sequence. + * @param from Start order. + * @param to End order. + * @param travel Travel time so far. + * @return Updated travel time. + */ +LinkRefresher::TimetableTravelTime LinkRefresher::UpdateTimetableTravelSoFar(const Order *from, const Order *to, LinkRefresher::TimetableTravelTime travel) +{ + if (from == to || from == nullptr || to == nullptr || (travel.flags & TTT_INVALID) != 0) return travel; + + do { + if (from->IsType(OT_CONDITIONAL)) { + if (from->GetConditionVariable() == OCV_UNCONDITIONALLY) { + /* Taken branch travel time */ + travel.time_so_far += from->GetWaitTime(); + from = this->vehicle->orders->GetOrderAt(from->GetConditionSkipToOrder()); + travel.flags |= TTT_NO_TRAVEL_TIME; + } else if ((travel.flags & TTT_ALLOW_CONDITION) == 0) { + /* Unexpected conditional branch, give up */ + travel.flags |= TTT_INVALID; + return travel; + } else { + /* Non-taken branch, ignore travel time field */ + from = this->vehicle->orders->GetNext(from); + travel.flags &= ~TTT_NO_TRAVEL_TIME; + } + } else { + if ((travel.flags & TTT_NO_WAIT_TIME) == 0) { + if (from->IsScheduledDispatchOrder(true)) { + travel.flags |= TTT_INVALID; + return travel; + } + travel.time_so_far += from->GetWaitTime(); + } + from = this->vehicle->orders->GetNext(from); + travel.flags &= ~TTT_NO_TRAVEL_TIME; + } + + travel.flags &= ~TTT_NO_WAIT_TIME; + travel.flags &= ~TTT_ALLOW_CONDITION; + + if (!from->IsType(OT_CONDITIONAL) && (travel.flags & TTT_NO_TRAVEL_TIME) == 0) { + if (from->GetTravelTime() == 0 && !from->IsTravelTimetabled() && !from->IsType(OT_IMPLICIT)) { + travel.flags |= TTT_INVALID; + return travel; + } + travel.time_so_far += from->GetTravelTime(); + } + + travel.flags &= ~TTT_NO_TRAVEL_TIME; + } while (from != to); + + return travel; +} + /** * Predict the next order the vehicle will execute and resolve conditionals by * recursion and return next non-conditional order in list. * @param cur Current order being evaluated. * @param next Next order to be evaluated. + * @param travel Travel time so far. * @param flags RefreshFlags to give hints about the previous link and state carried over from that. * @param num_hops Number of hops already taken by recursive calls to this method. - * @return new next Order. + * @return new next Order, and travel time so far. */ -const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next, uint8 flags, uint num_hops) +std::pair LinkRefresher::PredictNextOrder(const Order *cur, const Order *next, LinkRefresher::TimetableTravelTime travel, uint8 flags, uint num_hops) { /* next is good if it's either nullptr (then the caller will stop the * evaluation) or if it's not conditional and the caller allows it to be @@ -184,16 +242,18 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next if (next->IsType(OT_CONDITIONAL)) { if (next->GetConditionVariable() == OCV_UNCONDITIONALLY) { + const Order *current = next; CargoTypes this_cargo_mask = this->cargo_mask; next = this->vehicle->orders->GetNextDecisionNode( this->vehicle->orders->GetOrderAt(next->GetConditionSkipToOrder()), num_hops++, this_cargo_mask); assert(this_cargo_mask == this->cargo_mask); + travel = this->UpdateTimetableTravelSoFar(current, next, travel); continue; } CargoTypes this_cargo_mask = this->cargo_mask; - const Order *skip_to = this->vehicle->orders->GetNextDecisionNode( - this->vehicle->orders->GetOrderAt(next->GetConditionSkipToOrder()), num_hops, this_cargo_mask); + const Order *target = this->vehicle->orders->GetOrderAt(next->GetConditionSkipToOrder()); + const Order *skip_to = this->vehicle->orders->GetNextDecisionNode(target, num_hops, this_cargo_mask); assert(this_cargo_mask == this->cargo_mask); if (skip_to != nullptr && num_hops < std::min(64, this->vehicle->orders->GetNumOrders()) && skip_to != next) { /* Make copies of capacity tracking lists. There is potential @@ -206,28 +266,39 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next auto iter = this->seen_hops->lower_bound(hop); if (iter == this->seen_hops->end() || *iter != hop) { this->seen_hops->insert(iter, hop); + TimetableTravelTime branch_travel = travel; + branch_travel.time_so_far += next->GetWaitTime(); + branch_travel.flags |= TTT_NO_TRAVEL_TIME; LinkRefresher branch(*this); - branch.RefreshLinks(cur, skip_to, flags, num_hops + 1); + branch.RefreshLinks(cur, skip_to, this->UpdateTimetableTravelSoFar(target, skip_to, branch_travel), flags, num_hops + 1); } } + + travel.time_so_far += next->GetWaitTime(); } /* Reassign next with the following stop. This can be a station or a * depot.*/ CargoTypes this_cargo_mask = this->cargo_mask; + const Order *current = next; next = this->vehicle->orders->GetNextDecisionNode( this->vehicle->orders->GetNext(next), num_hops++, this_cargo_mask); assert(this_cargo_mask == this->cargo_mask); + + travel.flags |= TTT_ALLOW_CONDITION; + travel = this->UpdateTimetableTravelSoFar(current, next, travel); } - return next; + return std::make_pair(next, travel); } /** * Refresh link stats for the given pair of orders. * @param cur Last stop where the consist could interact with cargo. * @param next Next order to be processed. + * @param travel_estimate Estimated travel time, only valid if non-zero. + * @param flags RefreshFlags to give hints about the previous link and state carried over from that. */ -void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint8 flags) +void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint32 travel_estimate, uint8 flags) { StationID next_station = next->GetDestination(); Station *st = Station::GetIfValid(cur->GetDestination()); @@ -257,6 +328,13 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint8 flag * The result is in tiles/tick (= 2048 km-ish/h). */ uint32 time_estimate = DistanceManhattan(st->xy, st_to->xy) * 4096U / this->vehicle->GetDisplayMaxSpeed(); + if (travel_estimate > 0) { + /* If a timetable-based time is available, use that, clamping it to be in the range (estimate / 3, estimate * 2) + * of the distance/speed based estimate. + * This is effectively clamping it to be within the estimated speed range: (max_speed / 4, max_speed * 1.5). */ + time_estimate = Clamp(travel_estimate, time_estimate / 3, time_estimate * 2); + } + if (HasBit(flags, AIRCRAFT)) restricted_mode |= EUM_AIRCRAFT; /* If the vehicle is currently full loading, increase the capacities at the station @@ -293,10 +371,11 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint8 flag * OT_IMPLICIT orders in between. * @param cur Current order being evaluated. * @param next Next order to be checked. + * @param travel Travel time so far. * @param flags RefreshFlags to give hints about the previous link and state carried over from that. * @param num_hops Number of hops already taken by recursive calls to this method. */ -void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flags, uint num_hops) +void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, TimetableTravelTime travel, uint8 flags, uint num_hops) { while (next != nullptr) { @@ -309,7 +388,7 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flag LinkRefresher backup(*this); for (CargoID c = 0; c != NUM_CARGO; ++c) { if (CargoSpec::Get(c)->IsValid() && this->HandleRefit(c)) { - this->RefreshLinks(cur, next, flags, num_hops); + this->RefreshLinks(cur, next, travel, flags, num_hops); *this = backup; } } @@ -325,7 +404,7 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flag ClrBit(flags, RESET_REFIT); } - next = this->PredictNextOrder(cur, next, flags, num_hops); + std::tie(next, travel) = this->PredictNextOrder(cur, next, travel, flags, num_hops); if (next == nullptr) break; Hop hop(cur->index, next->index, this->cargo); auto iter = this->seen_hops->lower_bound(hop); @@ -350,7 +429,7 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flag if (cur->IsType(OT_GOTO_STATION) || cur->IsType(OT_IMPLICIT)) { if (cur->CanLeaveWithCargo(HasBit(flags, HAS_CARGO), FindFirstBit(this->cargo_mask))) { SetBit(flags, HAS_CARGO); - this->RefreshStats(cur, next, flags); + this->RefreshStats(cur, next, ((travel.flags & TTT_INVALID) == 0 && travel.time_so_far > 0) ? (uint32)travel.time_so_far : 0, flags); } else { ClrBit(flags, HAS_CARGO); } @@ -359,5 +438,6 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flag /* "cur" is only assigned here if the stop is a station so that * whenever stats are to be increased two stations can be found. */ cur = next; + travel = { 0, TTT_NO_WAIT_TIME }; } } diff --git a/src/linkgraph/refresh.h b/src/linkgraph/refresh.h index bf0043fa8e..6392a42adc 100644 --- a/src/linkgraph/refresh.h +++ b/src/linkgraph/refresh.h @@ -81,6 +81,19 @@ protected: bool operator!=(const Hop &other) const { return !(*this == other); } }; + /** For TimetableTravelTime::flags */ + enum : uint { + TTT_NO_WAIT_TIME = 1 << 0, + TTT_NO_TRAVEL_TIME = 1 << 1, + TTT_ALLOW_CONDITION = 1 << 2, + TTT_INVALID = 1 << 3, + }; + + struct TimetableTravelTime { + int time_so_far = 0; + uint flags = 0; + }; + typedef std::vector RefitList; typedef btree::btree_set HopSet; @@ -97,10 +110,11 @@ protected: bool HandleRefit(CargoID refit_cargo); void ResetRefit(); - void RefreshStats(const Order *cur, const Order *next, uint8 flags); - const Order *PredictNextOrder(const Order *cur, const Order *next, uint8 flags, uint num_hops = 0); + void RefreshStats(const Order *cur, const Order *next,uint32 travel_estimate, uint8 flags); + TimetableTravelTime UpdateTimetableTravelSoFar(const Order *from, const Order *to, TimetableTravelTime travel); + std::pair PredictNextOrder(const Order *cur, const Order *next, TimetableTravelTime travel, uint8 flags, uint num_hops = 0); - void RefreshLinks(const Order *cur, const Order *next, uint8 flags, uint num_hops = 0); + void RefreshLinks(const Order *cur, const Order *next, TimetableTravelTime travel, uint8 flags, uint num_hops = 0); }; #endif /* REFRESH_H */ diff --git a/src/station_func.h b/src/station_func.h index a026322377..c001cfc7db 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -47,7 +47,6 @@ void UpdateAirportsNoise(); bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrackOffset *overlay_offset); -void IncreaseStats(Station *st, const Vehicle *v, StationID next_station_id, uint32 time); void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); void RerouteCargo(Station *st, CargoID c, StationID avoid, StationID avoid2); void RerouteCargoFromSource(Station *st, CargoID c, StationID source, StationID avoid, StationID avoid2);