From e1c9de23181658a88c75a2c10f5b72a8f268fda9 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Fri, 12 Jan 2024 01:31:48 +0000 Subject: [PATCH] Improve handling of conditional orders with vehicle route lines --- src/viewport.cpp | 133 +++++++++++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 52 deletions(-) diff --git a/src/viewport.cpp b/src/viewport.cpp index 18a2e37f0c1..3d96305a2d3 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -115,6 +115,7 @@ #include "worker_thread.h" #include "vehiclelist.h" #include "core/backup_type.hpp" +#include "3rdparty/robin_hood/robin_hood.h" #include #include @@ -345,11 +346,11 @@ uint _vp_route_step_string_width[4] = {}; struct DrawnPathRouteTileLine { TileIndex from_tile; TileIndex to_tile; - bool order_match; + bool order_conditional; bool operator==(const DrawnPathRouteTileLine &other) const { - return std::tie(this->from_tile, this->to_tile, this->order_match) == std::tie(other.from_tile, other.to_tile, other.order_match); + return std::tie(this->from_tile, this->to_tile, this->order_conditional) == std::tie(other.from_tile, other.to_tile, other.order_conditional); } bool operator!=(const DrawnPathRouteTileLine &other) const @@ -359,7 +360,7 @@ struct DrawnPathRouteTileLine { bool operator<(const DrawnPathRouteTileLine &other) const { - return std::tie(this->from_tile, this->to_tile, this->order_match) < std::tie(other.from_tile, other.to_tile, other.order_match); + return std::tie(this->from_tile, this->to_tile, this->order_conditional) < std::tie(other.from_tile, other.to_tile, other.order_conditional); } }; @@ -370,6 +371,20 @@ struct ViewportRouteOverlay { std::vector route_paths; std::vector route_paths_last_mark_dirty; + struct PrepareRouteStepState { + robin_hood::unordered_flat_set visited; + uint lines_added; + TileIndex from_tile; + + inline void reset(TileIndex from_tile) + { + this->visited.clear(); + this->lines_added = 0; + this->from_tile = from_tile; + } + }; + + void PrepareVehicleRoutePathsConditionalOrder(const Vehicle *veh, const Order *order, PrepareRouteStepState &state, bool conditional, uint depth); bool PrepareVehicleRouteSteps(const Vehicle *veh); bool PrepareVehicleRoutePaths(const Vehicle *veh); void MarkAllRouteStepsDirty(const Vehicle *veh); @@ -2347,45 +2362,36 @@ static bool ViewportVehicleRouteShouldSkipOrder(const Order *order) } } -static inline TileIndex GetLastValidOrderLocation(const Vehicle *veh) +void ViewportRouteOverlay::PrepareVehicleRoutePathsConditionalOrder(const Vehicle *veh, const Order *order, PrepareRouteStepState &state, bool conditional, uint depth) { - VehicleOrderID order_id = veh->GetNumOrders(); - while (order_id > 0) { - order_id--; - const Order *order = veh->GetOrder(order_id); - if (ViewportVehicleRouteShouldSkipOrder(order)) continue; - TileIndex location = order->GetLocation(veh, veh->type == VEH_AIRCRAFT); - if (location != INVALID_TILE) return location; - } - return INVALID_TILE; -} + /* Prevent excessive recursion */ + if (depth >= 10) return; -static inline std::pair GetFinalOrder(const Vehicle *veh, const Order *order) -{ - // Use Floyd's cycle-finding algorithm to prevent endless loop - // due to a cycle formed by confitional orders. - auto cycle_check = order; - - bool is_conditional = false; + for (; order != nullptr && state.lines_added < 16; order = veh->orders->GetNext(order)) { + if (!state.visited.insert(order).second) { + /* Already visited this order */ + return; + } - while (order->IsType(OT_CONDITIONAL)) { - if (order->GetConditionVariable() != OCV_UNCONDITIONALLY) is_conditional = true; - order = veh->GetOrder(order->GetConditionSkipToOrder()); + if (ViewportVehicleRouteShouldSkipOrder(order)) continue; - if (cycle_check->IsType(OT_CONDITIONAL)) { - cycle_check = veh->GetOrder(cycle_check->GetConditionSkipToOrder()); + if (order->IsType(OT_CONDITIONAL)) { + this->PrepareVehicleRoutePathsConditionalOrder(veh, veh->GetOrder(order->GetConditionSkipToOrder()), state, + conditional || order->GetConditionVariable() != OCV_UNCONDITIONALLY, depth + 1); + if (order->GetConditionVariable() == OCV_UNCONDITIONALLY) return; - if (cycle_check->IsType(OT_CONDITIONAL)) { - cycle_check = veh->GetOrder(cycle_check->GetConditionSkipToOrder()); - } + continue; } - bool cycle_detected = (order->IsType(OT_CONDITIONAL) && (order == cycle_check)); + const TileIndex to_tile = order->GetLocation(veh, veh->type == VEH_AIRCRAFT); + if (to_tile == INVALID_TILE) continue; - if (cycle_detected) return std::pair(nullptr, is_conditional); + DrawnPathRouteTileLine path = { state.from_tile, to_tile, conditional }; + if (path.from_tile > path.to_tile) std::swap(path.from_tile, path.to_tile); + this->route_paths.push_back(path); + state.lines_added++; + return; } - - return std::pair(order, is_conditional); } bool ViewportRouteOverlay::PrepareVehicleRoutePaths(const Vehicle *veh) @@ -2393,33 +2399,56 @@ bool ViewportRouteOverlay::PrepareVehicleRoutePaths(const Vehicle *veh) if (veh == nullptr) return false; if (this->route_paths.empty()) { - TileIndex from_tile = GetLastValidOrderLocation(veh); - if (from_tile == INVALID_TILE) return false; + PrepareRouteStepState state; + + TileIndex from_tile = INVALID_TILE; + bool conditional = false; + auto handle_order = [&](const Order *order) -> bool { + if (ViewportVehicleRouteShouldSkipOrder(order)) return false; + + if (order->IsType(OT_CONDITIONAL) && from_tile != INVALID_TILE) { + state.reset(from_tile); + this->PrepareVehicleRoutePathsConditionalOrder(veh, order, state, + conditional || order->GetConditionVariable() != OCV_UNCONDITIONALLY, 0); + if (order->GetConditionVariable() == OCV_UNCONDITIONALLY) { + from_tile = INVALID_TILE; + return true; + } + conditional = true; + return false; + } - for (const Order *order : veh->Orders()) { - auto guard = scope_guard([&]() { - if (order->IsType(OT_CONDITIONAL) && order->GetConditionVariable() == OCV_UNCONDITIONALLY) from_tile = INVALID_TILE; - }); - const Order *final_order; - bool conditional; - std::tie(final_order, conditional) = GetFinalOrder(veh, order); - if (final_order == nullptr) continue; - if (ViewportVehicleRouteShouldSkipOrder(final_order)) continue; - const TileIndex to_tile = final_order->GetLocation(veh, veh->type == VEH_AIRCRAFT); - if (to_tile == INVALID_TILE) continue; + const TileIndex to_tile = order->GetLocation(veh, veh->type == VEH_AIRCRAFT); + if (to_tile == INVALID_TILE) return false; if (from_tile != INVALID_TILE) { - DrawnPathRouteTileLine path = { from_tile, to_tile, !conditional }; + DrawnPathRouteTileLine path = { from_tile, to_tile, conditional }; if (path.from_tile > path.to_tile) std::swap(path.from_tile, path.to_tile); this->route_paths.push_back(path); } - const OrderType ot = order->GetType(); - if (ot == OT_GOTO_STATION || ot == OT_GOTO_DEPOT || ot == OT_GOTO_WAYPOINT || ot == OT_IMPLICIT) from_tile = to_tile; + from_tile = to_tile; + conditional = false; + + return true; + }; + for (const Order *order : veh->Orders()) { + handle_order(order); } - // remove duplicate lines + if (from_tile != INVALID_TILE) { + /* Handle wrap around from last order back to first */ + for (const Order *order : veh->Orders()) { + if (handle_order(order)) break; + } + } + + /* Remove duplicate lines */ std::sort(this->route_paths.begin(), this->route_paths.end()); - this->route_paths.erase(std::unique(this->route_paths.begin(), this->route_paths.end()), this->route_paths.end()); + auto unique_end = std::unique(this->route_paths.begin(), this->route_paths.end(), [](const DrawnPathRouteTileLine &a, const DrawnPathRouteTileLine &b) { + /* Consider elements with the same tile values but different order_conditional values as equal */ + return a.from_tile == b.from_tile && a.to_tile == b.to_tile; + }); + this->route_paths.erase(unique_end, this->route_paths.end()); } return true; } @@ -2455,7 +2484,7 @@ void ViewportRouteOverlay::DrawVehicleRoutePath(const Viewport *vp, ViewportDraw GfxDrawLine(&dpi_for_text, from_x, from_y, to_x, to_y, PC_BLACK, 3, _settings_client.gui.dash_level_of_route_lines); line_width = 1; } - GfxDrawLine(&dpi_for_text, from_x, from_y, to_x, to_y, iter.order_match ? PC_WHITE : PC_YELLOW, line_width, _settings_client.gui.dash_level_of_route_lines); + GfxDrawLine(&dpi_for_text, from_x, from_y, to_x, to_y, iter.order_conditional ? PC_YELLOW : PC_WHITE, line_width, _settings_client.gui.dash_level_of_route_lines); } }