diff --git a/src/linkgraph/linkgraph.cpp b/src/linkgraph/linkgraph.cpp index 6685f5b7ae..b8119c7a2e 100644 --- a/src/linkgraph/linkgraph.cpp +++ b/src/linkgraph/linkgraph.cpp @@ -39,6 +39,7 @@ inline void LinkGraph::BaseEdge::Init() { this->capacity = 0; this->usage = 0; + this->travel_time_sum = 0; this->last_unrestricted_update = INVALID_DATE; this->last_restricted_update = INVALID_DATE; this->last_aircraft_update = INVALID_DATE; @@ -75,6 +76,7 @@ void LinkGraph::Compress() if (edge.capacity > 0) { edge.capacity = std::max(1U, edge.capacity / 2); edge.usage /= 2; + edge.travel_time_sum = std::max(1ULL, edge.travel_time_sum / 2); } } } @@ -102,9 +104,11 @@ void LinkGraph::Merge(LinkGraph *other) backward = other->edges[node2][node1]; forward.capacity = LinkGraph::Scale(forward.capacity, age, other_age); forward.usage = LinkGraph::Scale(forward.usage, age, other_age); + forward.travel_time_sum = LinkGraph::Scale(forward.travel_time_sum, age, other_age); if (forward.next_edge != INVALID_NODE) forward.next_edge += first; backward.capacity = LinkGraph::Scale(backward.capacity, age, other_age); backward.usage = LinkGraph::Scale(backward.usage, age, other_age); + backward.travel_time_sum = LinkGraph::Scale(backward.travel_time_sum, age, other_age); if (backward.next_edge != INVALID_NODE) backward.next_edge += first; } BaseEdge &new_start = this->edges[new_node][new_node]; @@ -190,13 +194,14 @@ NodeID LinkGraph::AddNode(const Station *st) * @param usage Usage to be added. * @param mode Update mode to be used. */ -void LinkGraph::Node::AddEdge(NodeID to, uint capacity, uint usage, EdgeUpdateMode mode) +void LinkGraph::Node::AddEdge(NodeID to, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { assert(this->index != to); BaseEdge &edge = this->edges[to]; BaseEdge &first = this->edges[this->index]; edge.capacity = capacity; edge.usage = usage; + edge.travel_time_sum = travel_time * capacity; edge.next_edge = first.next_edge; first.next_edge = to; if (mode & EUM_UNRESTRICTED) edge.last_unrestricted_update = _date; @@ -211,14 +216,14 @@ void LinkGraph::Node::AddEdge(NodeID to, uint capacity, uint usage, EdgeUpdateMo * @param usage Usage to be added. * @param mode Update mode to be used. */ -void LinkGraph::Node::UpdateEdge(NodeID to, uint capacity, uint usage, EdgeUpdateMode mode) +void LinkGraph::Node::UpdateEdge(NodeID to, uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { assert(capacity > 0); assert(usage <= capacity); if (this->edges[to].capacity == 0) { - this->AddEdge(to, capacity, usage, mode); + this->AddEdge(to, capacity, usage, travel_time, mode); } else { - (*this)[to].Update(capacity, usage, mode); + (*this)[to].Update(capacity, usage, travel_time, mode); } } @@ -235,6 +240,7 @@ void LinkGraph::Node::RemoveEdge(NodeID to) edge.last_restricted_update = INVALID_DATE; edge.last_aircraft_update = INVALID_DATE; edge.usage = 0; + edge.travel_time_sum = 0; NodeID prev = this->index; NodeID next = this->edges[this->index].next_edge; @@ -253,23 +259,39 @@ void LinkGraph::Node::RemoveEdge(NodeID to) /** * Update an edge. If mode contains UM_REFRESH refresh the edge to have at - * least the given capacity and usage, otherwise add the capacity and usage. + * least the given capacity and usage, otherwise add the capacity, usage and travel time. * In any case set the respective update timestamp(s), according to the given * mode. * @param capacity Capacity to be added/updated. * @param usage Usage to be added. + * @param travel_time Travel time to be added, in ticks. * @param mode Update mode to be applied. */ -void LinkGraph::Edge::Update(uint capacity, uint usage, EdgeUpdateMode mode) +void LinkGraph::Edge::Update(uint capacity, uint usage, uint32 travel_time, EdgeUpdateMode mode) { assert(this->edge.capacity > 0); assert(capacity >= usage); if (mode & EUM_INCREASE) { + if (this->edge.travel_time_sum == 0) { + this->edge.travel_time_sum = (this->edge.capacity + capacity) * travel_time; + } else if (travel_time == 0) { + this->edge.travel_time_sum += this->edge.travel_time_sum / this->edge.capacity * capacity; + } else { + this->edge.travel_time_sum += travel_time * capacity; + } this->edge.capacity += capacity; this->edge.usage += usage; } else if (mode & EUM_REFRESH) { - this->edge.capacity = std::max(this->edge.capacity, capacity); + /* If travel time is not provided, we scale the stored time based on + * the capacity increase. */ + if (capacity > this->edge.capacity && travel_time == 0) { + this->edge.travel_time_sum = this->edge.travel_time_sum / this->edge.capacity * capacity; + this->edge.capacity = capacity; + } else { + this->edge.capacity = std::max(this->edge.capacity, capacity); + this->edge.travel_time_sum = std::max(this->edge.travel_time_sum, travel_time * capacity); + } this->edge.usage = std::max(this->edge.usage, usage); } if (mode & EUM_UNRESTRICTED) this->edge.last_unrestricted_update = _date; diff --git a/src/linkgraph/linkgraph.h b/src/linkgraph/linkgraph.h index bb9db08ada..2e093bd3bf 100644 --- a/src/linkgraph/linkgraph.h +++ b/src/linkgraph/linkgraph.h @@ -70,6 +70,7 @@ public: struct BaseEdge { uint capacity; ///< Capacity of the link. uint usage; ///< Usage of the link. + uint64 travel_time_sum; ///< Sum of the travel times of the link, in ticks. Date last_unrestricted_update; ///< When the unrestricted part of the link was last updated. Date last_restricted_update; ///< When the restricted part of the link was last updated. Date last_aircraft_update; ///< When aircraft capacity of the link was last updated. @@ -106,6 +107,12 @@ public: */ uint Usage() const { return this->edge.usage; } + /** + * Get edge's average travel time. + * @return Travel time, in ticks. + */ + uint32 TravelTime() const { return this->edge.travel_time_sum / this->edge.capacity; } + /** * Get the date of the last update to the edge's unrestricted capacity. * @return Last update. @@ -311,7 +318,7 @@ public: * @param edge Edge to be wrapped. */ Edge(BaseEdge &edge) : EdgeWrapper(edge) {} - void Update(uint capacity, uint usage, EdgeUpdateMode mode); + void Update(uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); void Restrict() { this->edge.last_unrestricted_update = INVALID_DATE; } void Release() { this->edge.last_restricted_update = INVALID_DATE; } void ClearAircraft() { this->edge.last_aircraft_update = INVALID_DATE; } @@ -445,8 +452,8 @@ public: this->node.demand = demand; } - void AddEdge(NodeID to, uint capacity, uint usage, EdgeUpdateMode mode); - void UpdateEdge(NodeID to, uint capacity, uint usage, EdgeUpdateMode mode); + void AddEdge(NodeID to, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); + void UpdateEdge(NodeID to, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode); void RemoveEdge(NodeID to); }; diff --git a/src/linkgraph/mcf.cpp b/src/linkgraph/mcf.cpp index 14900ab77d..51a35838fe 100644 --- a/src/linkgraph/mcf.cpp +++ b/src/linkgraph/mcf.cpp @@ -310,16 +310,33 @@ void MultiCommodityFlow::Dijkstra(NodeID source_node, PathVector &paths) capacity /= 100; if (capacity == 0) capacity = 1; } - /* punish in-between stops a little */ - uint distance = DistanceMaxPlusManhattan(this->job[from].XY(), this->job[to].XY()) + 1; + /* Prioritize the fastest route for passengers, mail and express cargo, + * and the shortest route for other classes of cargo. + * In-between stops are punished with a 1 tile or 1 day penalty. */ + bool express = IsCargoInClass(this->job.Cargo(), CC_PASSENGERS) || + IsCargoInClass(this->job.Cargo(), CC_MAIL) || + IsCargoInClass(this->job.Cargo(), CC_EXPRESS); + + auto calculate_distance = [&]() { + return DistanceMaxPlusManhattan(this->job[from].XY(), this->job[to].XY()) + 1; + }; + + uint distance_anno; + if (express) { + /* Compute a default travel time from the distance and an average speed of 1 tile/day. */ + distance_anno = (edge.TravelTime() != 0) ? edge.TravelTime() + DAY_TICKS : calculate_distance() * DAY_TICKS; + } else { + distance_anno = calculate_distance(); + } + if (edge.LastAircraftUpdate() != INVALID_DATE && aircraft_link_scale > 100) { - distance *= aircraft_link_scale; - distance /= 100; + distance_anno *= aircraft_link_scale; + distance_anno /= 100; } Tannotation *dest = static_cast(paths[to]); - if (dest->IsBetter(source, capacity, capacity - edge.Flow(), distance)) { + if (dest->IsBetter(source, capacity, capacity - edge.Flow(), distance_anno)) { if (dest->GetAnnosSetFlag()) annos.erase(AnnoSetItem(dest)); - dest->Fork(source, capacity, capacity - edge.Flow(), distance); + dest->Fork(source, capacity, capacity - edge.Flow(), distance_anno); dest->UpdateAnnotation(); annos.insert(AnnoSetItem(dest)); dest->SetAnnosSetFlag(true); diff --git a/src/linkgraph/refresh.cpp b/src/linkgraph/refresh.cpp index 2ccc402f74..604bd2836a 100644 --- a/src/linkgraph/refresh.cpp +++ b/src/linkgraph/refresh.cpp @@ -249,6 +249,12 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint8 flag /* A link is at least partly restricted if a vehicle can't load at its source. */ EdgeUpdateMode restricted_mode = (cur->GetCargoLoadType(c) & OLFB_NO_LOAD) == 0 ? EUM_UNRESTRICTED : EUM_RESTRICTED; + Station *st_to = Station::GetIfValid(next_station); + /* This estimates the travel time of the link as the time needed + * to travel between the stations at half the max speed of the consist. + * The result is in tiles/tick (= 2048 km-ish/h). */ + uint32 time_estimate = (st_to != nullptr) ? + DistanceManhattan(st->xy, st_to->xy) * 4096U / this->vehicle->GetDisplayMaxSpeed() : 0; if (HasBit(flags, AIRCRAFT)) restricted_mode |= EUM_AIRCRAFT; @@ -264,15 +270,15 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next, uint8 flag uint effective_capacity = cargo_quantity * this->vehicle->load_unload_ticks; if (effective_capacity > (uint)this->vehicle->orders->GetTotalDuration()) { IncreaseStats(st, c, next_station, effective_capacity / - this->vehicle->orders->GetTotalDuration(), 0, + this->vehicle->orders->GetTotalDuration(), 0, 0, EUM_INCREASE | restricted_mode); } else if (RandomRange(this->vehicle->orders->GetTotalDuration()) < effective_capacity) { - IncreaseStats(st, c, next_station, 1, 0, EUM_INCREASE | restricted_mode); + IncreaseStats(st, c, next_station, 1, 0, 0, EUM_INCREASE | restricted_mode); } else { - IncreaseStats(st, c, next_station, cargo_quantity, 0, EUM_REFRESH | restricted_mode); + IncreaseStats(st, c, next_station, cargo_quantity, 0, time_estimate, EUM_REFRESH | restricted_mode); } } else { - IncreaseStats(st, c, next_station, cargo_quantity, 0, EUM_REFRESH | restricted_mode); + IncreaseStats(st, c, next_station, cargo_quantity, 0, time_estimate, EUM_REFRESH | restricted_mode); } } } diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index dc50be408e..6f7a4fc757 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -178,6 +178,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_TOWN_SETTING_OVERRIDE, XSCF_NULL, 1, 1, "town_setting_override", nullptr, nullptr, nullptr }, { XSLFI_SCRIPT_INT64, XSCF_NULL, 1, 1, "script_int64", nullptr, nullptr, nullptr }, { XSLFI_U64_TICK_COUNTER, XSCF_NULL, 1, 1, "u64_tick_counter", nullptr, nullptr, nullptr }, + { XSLFI_LINKGRAPH_TRAVEL_TIME, XSCF_NULL, 1, 1, "linkgraph_travel_time", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index dd3739b777..753bd8ac55 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -132,6 +132,7 @@ enum SlXvFeatureIndex { XSLFI_SCRIPT_INT64, ///< See: SLV_SCRIPT_INT64 XSLFI_U64_TICK_COUNTER, ///< See: SLV_U64_TICK_COUNTER + XSLFI_LINKGRAPH_TRAVEL_TIME, ///< See: SLV_LINKGRAPH_TRAVEL_TIME XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk diff --git a/src/saveload/linkgraph_sl.cpp b/src/saveload/linkgraph_sl.cpp index eb0ad68cef..0b934ee6e5 100644 --- a/src/saveload/linkgraph_sl.cpp +++ b/src/saveload/linkgraph_sl.cpp @@ -126,6 +126,7 @@ static const SaveLoad _edge_desc[] = { SLE_CONDNULL(4, SL_MIN_VERSION, SLV_191), // distance SLE_VAR(Edge, capacity, SLE_UINT32), SLE_VAR(Edge, usage, SLE_UINT32), + SLE_CONDVAR_X(Edge, travel_time_sum, SLE_UINT64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_LINKGRAPH_TRAVEL_TIME)), SLE_VAR(Edge, last_unrestricted_update, SLE_INT32), SLE_CONDVAR(Edge, last_restricted_update, SLE_INT32, SLV_187, SL_MAX_VERSION), SLE_CONDVAR_X(Edge, last_aircraft_update, SLE_INT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_LINKGRAPH_AIRCRAFT)), diff --git a/src/saveload/upstream/linkgraph_sl.cpp b/src/saveload/upstream/linkgraph_sl.cpp index 8c06894644..db49921478 100644 --- a/src/saveload/upstream/linkgraph_sl.cpp +++ b/src/saveload/upstream/linkgraph_sl.cpp @@ -33,7 +33,7 @@ public: inline static const SaveLoad description[] = { SLE_VAR(Edge, capacity, SLE_UINT32), SLE_VAR(Edge, usage, SLE_UINT32), - //SLE_CONDVAR(Edge, travel_time_sum, SLE_UINT64, SLV_LINKGRAPH_TRAVEL_TIME, SL_MAX_VERSION), + SLE_CONDVAR(Edge, travel_time_sum, SLE_UINT64, SLV_LINKGRAPH_TRAVEL_TIME, SL_MAX_VERSION), SLE_VAR(Edge, last_unrestricted_update, SLE_INT32), SLE_CONDVAR(Edge, last_restricted_update, SLE_INT32, SLV_187, SL_MAX_VERSION), SLE_VAR(Edge, next_edge, SLE_UINT16), diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 4e65483cc1..7615b71df3 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -4499,7 +4499,7 @@ void DeleteStaleLinks(Station *from) * @param usage Usage to add to link stat. * @param mode Update mode to be applied. */ -void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint capacity, uint usage, EdgeUpdateMode mode) +void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint capacity, uint usage, uint32 time, EdgeUpdateMode mode) { GoodsEntry &ge1 = st->goods[cargo]; Station *st2 = Station::Get(next_station_id); @@ -4541,7 +4541,7 @@ void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint c } } if (lg != nullptr) { - (*lg)[ge1.node].UpdateEdge(ge2.node, capacity, usage, mode); + (*lg)[ge1.node].UpdateEdge(ge2.node, capacity, usage, time, mode); } } diff --git a/src/station_func.h b/src/station_func.h index 51eb8a3ff2..dcfe6746e3 100644 --- a/src/station_func.h +++ b/src/station_func.h @@ -52,8 +52,8 @@ void UpdateAirportsNoise(); bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrackOffset *overlay_offset); -void IncreaseStats(Station *st, const Vehicle *v, StationID next_station_id); -void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint capacity, uint usage, EdgeUpdateMode mode); +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); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 6729fa8c5c..89801f4c07 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3140,6 +3140,7 @@ void Vehicle::DeleteUnreachedImplicitOrders() */ static void VehicleIncreaseStats(const Vehicle *front) { + uint32 travel_time = front->current_order_time; for (const Vehicle *v = front; v != nullptr; v = v->Next()) { StationID last_loading_station = HasBit(front->vehicle_flags, VF_LAST_LOAD_ST_SEP) ? v->last_loading_station : front->last_loading_station; if (v->refit_cap > 0 && @@ -3156,7 +3157,7 @@ static void VehicleIncreaseStats(const Vehicle *front) EdgeUpdateMode restricted_mode = EUM_INCREASE; if (v->type == VEH_AIRCRAFT) restricted_mode |= EUM_AIRCRAFT; IncreaseStats(Station::Get(last_loading_station), v->cargo_type, front->last_station_visited, v->refit_cap, - std::min(v->refit_cap, v->cargo.StoredCount()), restricted_mode); + std::min(v->refit_cap, v->cargo.StoredCount()), travel_time, restricted_mode); } } }