From 0455627d16ba2ee3393146ef24bc39c453790a36 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 23 May 2025 10:36:28 +0100 Subject: [PATCH] Codechange: Move ownership of Orders to OrderList. (#13948) Removes the orders pool, and orders are now stored directly in each OrderList. Iterating orders now no longer needs to traverse a linked-list, all orders in an OrderList are sequential. --- src/autoreplace_cmd.cpp | 9 +- src/industry_cmd.cpp | 8 +- src/linkgraph/refresh.cpp | 63 ++-- src/linkgraph/refresh.h | 12 +- src/order_backup.cpp | 36 +-- src/order_backup.h | 8 +- src/order_base.h | 92 ++++-- src/order_cmd.cpp | 385 ++++++++++--------------- src/order_func.h | 4 +- src/order_gui.cpp | 36 +-- src/order_type.h | 1 - src/saveload/afterload.cpp | 52 ++-- src/saveload/oldloader_sl.cpp | 14 +- src/saveload/order_sl.cpp | 159 ++++++---- src/saveload/saveload.cpp | 10 +- src/saveload/saveload.h | 2 +- src/saveload/station_sl.cpp | 12 +- src/saveload/vehicle_sl.cpp | 18 +- src/saveload/waypoint_sl.cpp | 12 +- src/script/api/script_order.cpp | 48 +-- src/script/api/script_stationlist.cpp | 4 +- src/script/api/script_waypointlist.cpp | 4 +- src/station_cmd.cpp | 14 +- src/timetable_cmd.cpp | 3 +- src/timetable_gui.cpp | 53 ++-- src/vehicle.cpp | 91 +++--- src/vehicle_base.h | 68 +---- src/vehicle_cmd.cpp | 5 +- src/vehicle_gui.cpp | 27 +- src/vehiclelist_func.h | 4 +- 30 files changed, 602 insertions(+), 652 deletions(-) diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 888306b260..df64561cf3 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -185,9 +185,9 @@ static bool VerifyAutoreplaceRefitForOrders(const Vehicle *v, EngineID engine_ty CargoTypes union_refit_mask_b = GetUnionOfArticulatedRefitMasks(engine_type, false); const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; - for (const Order *o : u->Orders()) { - if (!o->IsRefit() || o->IsAutoRefit()) continue; - CargoType cargo_type = o->GetRefitCargo(); + for (const Order &o : u->Orders()) { + if (!o.IsRefit() || o.IsAutoRefit()) continue; + CargoType cargo_type = o.GetRefitCargo(); if (!HasBit(union_refit_mask_a, cargo_type)) continue; if (!HasBit(union_refit_mask_b, cargo_type)) return false; @@ -206,13 +206,12 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID { CargoTypes union_refit_mask = GetUnionOfArticulatedRefitMasks(engine_type, false); - const Order *o; const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; const OrderList *orders = u->orders; if (orders == nullptr) return -1; for (VehicleOrderID i = 0; i < orders->GetNumOrders(); i++) { - o = orders->GetOrderAt(i); + const Order *o = orders->GetOrderAt(i); if (!o->IsRefit()) continue; if (!HasBit(union_refit_mask, o->GetRefitCargo())) return i; } diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 62dfb9f333..617bb91847 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -2735,14 +2735,14 @@ int WhoCanServiceIndustry(Industry *ind) * We cannot check the first of shared orders only, since the first vehicle in such a chain * may have a different cargo type. */ - for (const Order *o : v->Orders()) { - if (o->IsType(OT_GOTO_STATION) && !(o->GetUnloadType() & OUFB_TRANSFER)) { + for (const Order &o : v->Orders()) { + if (o.IsType(OT_GOTO_STATION) && !(o.GetUnloadType() & OUFB_TRANSFER)) { /* Vehicle visits a station to load or unload */ - Station *st = Station::Get(o->GetDestination().ToStationID()); + Station *st = Station::Get(o.GetDestination().ToStationID()); assert(st != nullptr); /* Same cargo produced by industry is dropped here => not serviced by vehicle v */ - if ((o->GetUnloadType() & OUFB_UNLOAD) && !c_accepts) break; + if ((o.GetUnloadType() & OUFB_UNLOAD) && !c_accepts) break; if (ind->stations_near.find(st) != ind->stations_near.end()) { if (v->owner == _local_company) return 2; // Company services industry diff --git a/src/linkgraph/refresh.cpp b/src/linkgraph/refresh.cpp index 1ffb1dda50..25e5f47fb1 100644 --- a/src/linkgraph/refresh.cpp +++ b/src/linkgraph/refresh.cpp @@ -29,8 +29,8 @@ if (v->orders == nullptr) return; /* Make sure the first order is a useful order. */ - const Order *first = v->orders->GetNextDecisionNode(v->GetOrder(v->cur_implicit_order_index), 0); - if (first == nullptr) return; + VehicleOrderID first = v->orders->GetNextDecisionNode(v->cur_implicit_order_index, 0); + if (first == INVALID_VEH_ORDER_ID) return; HopSet seen_hops; LinkRefresher refresher(v, &seen_hops, allow_merge, is_full_loading); @@ -138,22 +138,25 @@ void LinkRefresher::ResetRefit() * @param num_hops Number of hops already taken by recursive calls to this method. * @return new next Order. */ -const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next, RefreshFlags flags, uint num_hops) +VehicleOrderID LinkRefresher::PredictNextOrder(VehicleOrderID cur, VehicleOrderID next, RefreshFlags flags, uint num_hops) { + assert(this->vehicle->orders != nullptr); + const OrderList &orderlist = *this->vehicle->orders; + auto orders = orderlist.GetOrders(); + /* 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 * chosen (by setting RefreshFlag::UseNext). */ - while (next != nullptr && (!flags.Test(RefreshFlag::UseNext) || next->IsType(OT_CONDITIONAL))) { + while (next < orderlist.GetNumOrders() && (!flags.Test(RefreshFlag::UseNext) || orders[next].IsType(OT_CONDITIONAL))) { /* After the first step any further non-conditional order is good, * regardless of previous RefreshFlag::UseNext settings. The case of cur and next or * their respective stations being equal is handled elsewhere. */ flags.Set(RefreshFlag::UseNext); - if (next->IsType(OT_CONDITIONAL)) { - const Order *skip_to = this->vehicle->orders->GetNextDecisionNode( - this->vehicle->orders->GetOrderAt(next->GetConditionSkipToOrder()), num_hops); - if (skip_to != nullptr && num_hops < this->vehicle->orders->GetNumOrders()) { + if (orders[next].IsType(OT_CONDITIONAL)) { + VehicleOrderID skip_to = orderlist.GetNextDecisionNode(orders[next].GetConditionSkipToOrder(), num_hops); + if (skip_to != INVALID_VEH_ORDER_ID && num_hops < orderlist.GetNumOrders()) { /* Make copies of capacity tracking lists. There is potential * for optimization here: If the vehicle never refits we don't * need to copy anything. Also, if we've seen the branched link @@ -165,8 +168,7 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next /* Reassign next with the following stop. This can be a station or a * depot.*/ - next = this->vehicle->orders->GetNextDecisionNode( - this->vehicle->orders->GetNext(next), num_hops++); + next = orderlist.GetNextDecisionNode(orderlist.GetNext(next), num_hops++); } return next; } @@ -176,10 +178,14 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next * @param cur Last stop where the consist could interact with cargo. * @param next Next order to be processed. */ -void LinkRefresher::RefreshStats(const Order *cur, const Order *next) +void LinkRefresher::RefreshStats(VehicleOrderID cur, VehicleOrderID next) { - StationID next_station = next->GetDestination().ToStationID(); - Station *st = Station::GetIfValid(cur->GetDestination().ToStationID()); + assert(this->vehicle->orders != nullptr); + const OrderList &orderlist = *this->vehicle->orders; + auto orders = orderlist.GetOrders(); + + StationID next_station = orders[next].GetDestination().ToStationID(); + Station *st = Station::GetIfValid(orders[cur].GetDestination().ToStationID()); if (st != nullptr && next_station != StationID::Invalid() && next_station != st->index) { Station *st_to = Station::Get(next_station); for (CargoType cargo = 0; cargo < NUM_CARGO; ++cargo) { @@ -197,7 +203,7 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next) } /* A link is at least partly restricted if a vehicle can't load at its source. */ - EdgeUpdateMode restricted_mode = (cur->GetLoadType() & OLFB_NO_LOAD) == 0 ? + EdgeUpdateMode restricted_mode = (orders[cur].GetLoadType() & OLFB_NO_LOAD) == 0 ? EdgeUpdateMode::Unrestricted : EdgeUpdateMode::Restricted; /* 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. @@ -240,14 +246,17 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next) * @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, RefreshFlags flags, uint num_hops) +void LinkRefresher::RefreshLinks(VehicleOrderID cur, VehicleOrderID next, RefreshFlags flags, uint num_hops) { - while (next != nullptr) { + assert(this->vehicle->orders != nullptr); + const OrderList &orderlist = *this->vehicle->orders; + while (next < orderlist.GetNumOrders()) { + const Order *next_order = orderlist.GetOrderAt(next); - if ((next->IsType(OT_GOTO_DEPOT) || next->IsType(OT_GOTO_STATION)) && next->IsRefit()) { + if ((next_order->IsType(OT_GOTO_DEPOT) || next_order->IsType(OT_GOTO_STATION)) && next_order->IsRefit()) { flags.Set(RefreshFlag::WasRefit); - if (!next->IsAutoRefit()) { - this->HandleRefit(next->GetRefitCargo()); + if (!next_order->IsAutoRefit()) { + this->HandleRefit(next_order->GetRefitCargo()); } else if (!flags.Test(RefreshFlag::InAutorefit)) { flags.Set(RefreshFlag::InAutorefit); LinkRefresher backup(*this); @@ -263,34 +272,38 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, RefreshFla /* Only reset the refit capacities if the "previous" next is a station, * meaning that either the vehicle was refit at the previous station or * it wasn't at all refit during the current hop. */ - if (flags.Test(RefreshFlag::WasRefit) && (next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT))) { + if (flags.Test(RefreshFlag::WasRefit) && (next_order->IsType(OT_GOTO_STATION) || next_order->IsType(OT_IMPLICIT))) { flags.Set(RefreshFlag::ResetRefit); } else { flags.Reset(RefreshFlag::ResetRefit); } next = this->PredictNextOrder(cur, next, flags, num_hops); - if (next == nullptr) break; - Hop hop(cur->index, next->index, this->cargo); + if (next == INVALID_VEH_ORDER_ID) break; + Hop hop(cur, next, this->cargo); if (this->seen_hops->find(hop) != this->seen_hops->end()) { break; } else { this->seen_hops->insert(hop); } + next_order = orderlist.GetOrderAt(next); + /* Don't use the same order again, but choose a new one in the next round. */ flags.Reset(RefreshFlag::UseNext); /* Skip resetting and link refreshing if next order won't do anything with cargo. */ - if (!next->IsType(OT_GOTO_STATION) && !next->IsType(OT_IMPLICIT)) continue; + if (!next_order->IsType(OT_GOTO_STATION) && !next_order->IsType(OT_IMPLICIT)) continue; if (flags.Test(RefreshFlag::ResetRefit)) { this->ResetRefit(); flags.Reset({RefreshFlag::ResetRefit, RefreshFlag::WasRefit}); } - if (cur->IsType(OT_GOTO_STATION) || cur->IsType(OT_IMPLICIT)) { - if (cur->CanLeaveWithCargo(flags.Test(RefreshFlag::HasCargo))) { + const Order *cur_order = orderlist.GetOrderAt(cur); + + if (cur_order->IsType(OT_GOTO_STATION) || cur_order->IsType(OT_IMPLICIT)) { + if (cur_order->CanLeaveWithCargo(flags.Test(RefreshFlag::HasCargo))) { flags.Set(RefreshFlag::HasCargo); this->RefreshStats(cur, next); } else { diff --git a/src/linkgraph/refresh.h b/src/linkgraph/refresh.h index 5473cbf92c..449bf2bd0d 100644 --- a/src/linkgraph/refresh.h +++ b/src/linkgraph/refresh.h @@ -56,8 +56,8 @@ protected: * line. */ struct Hop { - OrderID from; ///< Last order where vehicle could interact with cargo or absolute first order. - OrderID to; ///< Next order to be processed. + VehicleOrderID from; ///< Last order where vehicle could interact with cargo or absolute first order. + VehicleOrderID to; ///< Next order to be processed. CargoType cargo; ///< Cargo the consist is probably carrying or INVALID_CARGO if unknown. /** @@ -72,7 +72,7 @@ protected: * @param to Second order of the hop. * @param cargo Cargo the consist is probably carrying when passing the hop. */ - Hop(OrderID from, OrderID to, CargoType cargo) : from(from), to(to), cargo(cargo) {} + Hop(VehicleOrderID from, VehicleOrderID to, CargoType cargo) : from(from), to(to), cargo(cargo) {} constexpr auto operator<=>(const Hop &) const noexcept = default; }; @@ -92,10 +92,10 @@ protected: bool HandleRefit(CargoType refit_cargo); void ResetRefit(); - void RefreshStats(const Order *cur, const Order *next); - const Order *PredictNextOrder(const Order *cur, const Order *next, RefreshFlags flags, uint num_hops = 0); + void RefreshStats(VehicleOrderID cur, VehicleOrderID next); + VehicleOrderID PredictNextOrder(VehicleOrderID cur, VehicleOrderID next, RefreshFlags flags, uint num_hops = 0); - void RefreshLinks(const Order *cur, const Order *next, RefreshFlags flags, uint num_hops = 0); + void RefreshLinks(VehicleOrderID cur, VehicleOrderID next, RefreshFlags flags, uint num_hops = 0); }; #endif /* REFRESH_H */ diff --git a/src/order_backup.cpp b/src/order_backup.cpp index f71b46d29d..ae52f635c9 100644 --- a/src/order_backup.cpp +++ b/src/order_backup.cpp @@ -27,18 +27,7 @@ OrderBackupPool _order_backup_pool("BackupOrder"); INSTANTIATE_POOL_METHODS(OrderBackup) -/** Free everything that is allocated. */ -OrderBackup::~OrderBackup() -{ - if (CleaningPool()) return; - - Order *o = this->orders; - while (o != nullptr) { - Order *next = o->next; - delete o; - o = next; - } -} +OrderBackup::~OrderBackup() = default; /** * Create an order backup for the given vehicle. @@ -54,15 +43,7 @@ OrderBackup::OrderBackup(const Vehicle *v, uint32_t user) : user(user), tile(v-> this->clone = (v->FirstShared() == v) ? v->NextShared() : v->FirstShared(); } else { /* Else copy the orders */ - Order **tail = &this->orders; - - /* Count the number of orders */ - for (const Order *order : v->Orders()) { - Order *copy = new Order(); - copy->AssignOrder(*order); - *tail = copy; - tail = ©->next; - } + this->orders.assign(std::begin(v->Orders()), std::end(v->Orders())); } } @@ -75,9 +56,8 @@ void OrderBackup::DoRestore(Vehicle *v) /* If we had shared orders, recover that */ if (this->clone != nullptr) { Command::Do(DoCommandFlag::Execute, CO_SHARE, v->index, this->clone->index); - } else if (this->orders != nullptr && OrderList::CanAllocateItem()) { - v->orders = new OrderList(this->orders, v); - this->orders = nullptr; + } else if (!this->orders.empty() && OrderList::CanAllocateItem()) { + v->orders = new OrderList(std::move(this->orders), v); /* Make sure buoys/oil rigs are updated in the station list. */ InvalidateWindowClassesData(WC_STATION_LIST, 0); } @@ -251,12 +231,12 @@ CommandCost CmdClearOrderBackup(DoCommandFlags flags, TileIndex tile, ClientID u /* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination, bool hangar) { for (OrderBackup *ob : OrderBackup::Iterate()) { - for (Order *order = ob->orders; order != nullptr; order = order->next) { - OrderType ot = order->GetType(); - if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; + for (Order &order : ob->orders) { + OrderType ot = order.GetType(); + if (ot == OT_GOTO_DEPOT && (order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; if (ot == OT_GOTO_DEPOT && hangar && !IsHangarTile(ob->tile)) continue; // Not an aircraft? Can't have a hangar order. if (ot == OT_IMPLICIT || (IsHangarTile(ob->tile) && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION; - if (ot == type && order->GetDestination() == destination) { + if (ot == type && order.GetDestination() == destination) { /* Remove the order backup! If a station/depot gets removed, we can't/shouldn't restore those broken orders. */ delete ob; break; diff --git a/src/order_backup.h b/src/order_backup.h index 20eea9ee57..edeaf85e30 100644 --- a/src/order_backup.h +++ b/src/order_backup.h @@ -34,15 +34,19 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist private: friend SaveLoadTable GetOrderBackupDescription(); ///< Saving and loading of order backups. friend struct BKORChunkHandler; ///< Creating empty orders upon savegame loading. + template + friend class SlOrders; + uint32_t user = 0; ///< The user that requested the backup. TileIndex tile = INVALID_TILE; ///< Tile of the depot where the order was changed. GroupID group = GroupID::Invalid(); ///< The group the vehicle was part of. const Vehicle *clone = nullptr; ///< Vehicle this vehicle was a clone of. - Order *orders = nullptr; ///< The actual orders if the vehicle was not a clone. + std::vector orders; ///< The actual orders if the vehicle was not a clone. + uint32_t old_order_index = 0; /** Creation for savegame restoration. */ - OrderBackup() {} + OrderBackup() = default; OrderBackup(const Vehicle *v, uint32_t user); void DoRestore(Vehicle *v); diff --git a/src/order_base.h b/src/order_base.h index dd1cffcf17..1b0f6b1752 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -20,9 +20,7 @@ #include "timer/timer_game_tick.h" #include "saveload/saveload.h" -using OrderPool = Pool; using OrderListPool = Pool; -extern OrderPool _order_pool; extern OrderListPool _orderlist_pool; template @@ -31,15 +29,16 @@ class EndianBufferWriter; /* If you change this, keep in mind that it is saved on 3 places: * - Load_ORDR, all the global orders * - Vehicle -> current_order - * - REF_ORDER (all REFs are currently limited to 16 bits!!) */ -struct Order : OrderPool::PoolItem<&_order_pool> { +struct Order { private: friend struct VEHSChunkHandler; ///< Loading of ancient vehicles. friend SaveLoadTable GetOrderDescription(); ///< Saving and loading of orders. /* So we can use private/protected variables in the saveload code */ friend class SlVehicleCommon; friend class SlVehicleDisaster; + template + friend class SlOrders; template friend EndianBufferWriter &operator <<(EndianBufferWriter &buffer, const Order &data); @@ -56,11 +55,8 @@ private: uint16_t max_speed = UINT16_MAX; ///< How fast the vehicle may go on the way to the destination. public: - Order *next = nullptr; ///< Pointer to next order. If nullptr, end of list - Order() {} Order(uint8_t type, uint8_t flags, DestinationID dest) : type(type), flags(flags), dest(dest) {} - ~Order(); /** * Check whether this order is of the given type. @@ -248,7 +244,17 @@ public: void ConvertFromOldSavegame(); }; -void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord); +/** Compatibility struct to allow saveload of pool-based orders. */ +struct OldOrderSaveLoadItem { + uint32_t index = 0; ///< This order's index (1-based). + uint32_t next = 0; ///< The next order index (1-based). + Order order{}; ///< The order data. +}; + +OldOrderSaveLoadItem *GetOldOrder(size_t pool_index); +OldOrderSaveLoadItem &AllocateOldOrder(size_t pool_index); + +void InsertOrder(Vehicle *v, Order &&new_o, VehicleOrderID sel_ord); void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord); /** @@ -259,31 +265,49 @@ struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> { private: friend void AfterLoadVehiclesPhase1(bool part_of_load); ///< For instantiating the shared vehicle chain friend SaveLoadTable GetOrderListDescription(); ///< Saving and loading of order lists. + friend struct ORDLChunkHandler; + template + friend class SlOrders; - VehicleOrderID num_orders = INVALID_VEH_ORDER_ID; ///< NOSAVE: How many orders there are in the list. VehicleOrderID num_manual_orders = 0; ///< NOSAVE: How many manually added orders are there in the list. uint num_vehicles = 0; ///< NOSAVE: Number of vehicles that share this order list. Vehicle *first_shared = nullptr; ///< NOSAVE: pointer to the first vehicle in the shared order chain. - Order *first = nullptr; ///< First order of the order list. + std::vector orders; ///< Orders of the order list. + uint32_t old_order_index = 0; TimerGameTick::Ticks timetable_duration{}; ///< NOSAVE: Total timetabled duration of the order list. TimerGameTick::Ticks total_duration{}; ///< NOSAVE: Total (timetabled or not) duration of the order list. public: /** Default constructor producing an invalid order list. */ - OrderList(VehicleOrderID num_orders = INVALID_VEH_ORDER_ID) : num_orders(num_orders) { } + OrderList() {} /** * Create an order list with the given order chain for the given vehicle. * @param chain pointer to the first order of the order chain * @param v any vehicle using this orderlist */ - OrderList(Order *chain, Vehicle *v) { this->Initialize(chain, v); } + OrderList(Order &&order, Vehicle *v) + { + this->orders.emplace_back(std::move(order)); + this->Initialize(v); + } + + OrderList(std::vector &&orders, Vehicle *v) + { + this->orders = std::move(orders); + this->Initialize(v); + } + + OrderList(Vehicle *v) + { + this->Initialize(v); + } /** Destructor. Invalidates OrderList for re-usage by the pool. */ ~OrderList() {} - void Initialize(Order *chain, Vehicle *v); + void Initialize(Vehicle *v); void RecalculateTimetableDuration(); @@ -291,15 +315,33 @@ public: * Get the first order of the order chain. * @return the first order of the chain. */ - inline Order *GetFirstOrder() const { return this->first; } + inline VehicleOrderID GetFirstOrder() const { return this->orders.empty() ? INVALID_VEH_ORDER_ID : 0; } - Order *GetOrderAt(int index) const; + inline std::span GetOrders() const { return this->orders; } + inline std::span GetOrders() { return this->orders; } + + /** + * Get a certain order of the order chain. + * @param index zero-based index of the order within the chain. + * @return the order at position index. + */ + const Order *GetOrderAt(VehicleOrderID index) const + { + if (index >= this->GetNumOrders()) return nullptr; + return &this->orders[index]; + } + + Order *GetOrderAt(VehicleOrderID index) + { + if (index >= this->GetNumOrders()) return nullptr; + return &this->orders[index]; + } /** * Get the last order of the order chain. * @return the last order of the chain. */ - inline Order *GetLastOrder() const { return this->GetOrderAt(this->num_orders - 1); } + inline VehicleOrderID GetLastOrder() const { return this->orders.empty() ? INVALID_VEH_ORDER_ID : (this->GetNumOrders() - 1); } /** * Get the order after the given one or the first one, if the given one is the @@ -307,13 +349,17 @@ public: * @param curr Order to find the next one for. * @return Next order. */ - inline const Order *GetNext(const Order *curr) const { return (curr->next == nullptr) ? this->GetFirstOrder() : curr->next; } + inline VehicleOrderID GetNext(VehicleOrderID cur) const + { + if (this->orders.empty()) return INVALID_VEH_ORDER_ID; + return static_cast((cur + 1) % this->GetNumOrders()); + } /** * Get number of orders in the order list. * @return number of orders in the chain. */ - inline VehicleOrderID GetNumOrders() const { return this->num_orders; } + inline VehicleOrderID GetNumOrders() const { return static_cast(std::size(this->orders)); } /** * Get number of manually added orders in the order list. @@ -321,12 +367,12 @@ public: */ inline VehicleOrderID GetNumManualOrders() const { return this->num_manual_orders; } - StationIDStack GetNextStoppingStation(const Vehicle *v, const Order *first = nullptr, uint hops = 0) const; - const Order *GetNextDecisionNode(const Order *next, uint hops) const; + StationIDStack GetNextStoppingStation(const Vehicle *v, VehicleOrderID first = INVALID_VEH_ORDER_ID, uint hops = 0) const; + VehicleOrderID GetNextDecisionNode(VehicleOrderID next, uint hops) const; - void InsertOrderAt(Order *new_order, int index); - void DeleteOrderAt(int index); - void MoveOrder(int from, int to); + void InsertOrderAt(Order &&order, VehicleOrderID index); + void DeleteOrderAt(VehicleOrderID index); + void MoveOrder(VehicleOrderID from, VehicleOrderID to); /** * Is this a shared order list? diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 5ac05757b1..0e464f0206 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -38,24 +38,9 @@ static_assert(sizeof(DestinationID) >= sizeof(DepotID)); static_assert(sizeof(DestinationID) >= sizeof(StationID)); -OrderPool _order_pool("Order"); -INSTANTIATE_POOL_METHODS(Order) OrderListPool _orderlist_pool("OrderList"); INSTANTIATE_POOL_METHODS(OrderList) -/** Clean everything up. */ -Order::~Order() -{ - if (CleaningPool()) return; - - /* We can visit oil rigs and buoys that are not our own. They will be shown in - * the list of stations. So, we need to invalidate that window if needed. */ - if (this->IsType(OT_GOTO_STATION) || this->IsType(OT_GOTO_WAYPOINT)) { - BaseStation *bs = BaseStation::GetIfValid(this->GetDestination().ToStationID()); - if (bs != nullptr && bs->owner == OWNER_NONE) InvalidateWindowClassesData(WC_STATION_LIST, 0); - } -} - /** * 'Free' the order * @note ONLY use on "current_order" vehicle orders! @@ -65,7 +50,6 @@ void Order::Free() this->type = OT_NOTHING; this->flags = 0; this->dest = 0; - this->next = nullptr; } /** @@ -267,20 +251,17 @@ void Order::AssignOrder(const Order &other) * @param chain first order in the chain * @param v one of vehicle that is using this orderlist */ -void OrderList::Initialize(Order *chain, Vehicle *v) +void OrderList::Initialize(Vehicle *v) { - this->first = chain; this->first_shared = v; - this->num_orders = 0; this->num_manual_orders = 0; this->num_vehicles = 1; this->timetable_duration = 0; - for (Order *o = this->first; o != nullptr; o = o->next) { - ++this->num_orders; - if (!o->IsType(OT_IMPLICIT)) ++this->num_manual_orders; - this->total_duration += o->GetWaitTime() + o->GetTravelTime(); + for (const Order &o : this->orders) { + if (!o.IsType(OT_IMPLICIT)) ++this->num_manual_orders; + this->total_duration += o.GetWaitTime() + o.GetTravelTime(); } this->RecalculateTimetableDuration(); @@ -300,8 +281,8 @@ void OrderList::Initialize(Order *chain, Vehicle *v) void OrderList::RecalculateTimetableDuration() { this->timetable_duration = 0; - for (Order *o = this->first; o != nullptr; o = o->next) { - this->timetable_duration += o->GetTimetabledWait() + o->GetTimetabledTravel(); + for (const Order &o : this->orders) { + this->timetable_duration += o.GetTimetabledWait() + o.GetTimetabledTravel(); } } @@ -312,15 +293,20 @@ void OrderList::RecalculateTimetableDuration() */ void OrderList::FreeChain(bool keep_orderlist) { - Order *next; - for (Order *o = this->first; o != nullptr; o = next) { - next = o->next; - delete o; + /* We can visit oil rigs and buoys that are not our own. They will be shown in + * the list of stations. So, we need to invalidate that window if needed. */ + for (Order &order: this->orders) { + if (order.IsType(OT_GOTO_STATION) || order.IsType(OT_GOTO_WAYPOINT)) { + BaseStation *bs = BaseStation::GetIfValid(order.GetDestination().ToStationID()); + if (bs != nullptr && bs->owner == OWNER_NONE) { + InvalidateWindowClassesData(WC_STATION_LIST, 0); + break; + } + } } if (keep_orderlist) { - this->first = nullptr; - this->num_orders = 0; + this->orders.clear(); this->num_manual_orders = 0; this->timetable_duration = 0; } else { @@ -328,23 +314,6 @@ void OrderList::FreeChain(bool keep_orderlist) } } -/** - * Get a certain order of the order chain. - * @param index zero-based index of the order within the chain. - * @return the order at position index. - */ -Order *OrderList::GetOrderAt(int index) const -{ - if (index < 0) return nullptr; - - Order *order = this->first; - - while (order != nullptr && index-- > 0) { - order = order->next; - } - return order; -} - /** * Get the next order which will make the given vehicle stop at a station * or refit at a depot or evaluate a non-trivial condition. @@ -354,28 +323,29 @@ Order *OrderList::GetOrderAt(int index) const * \li a station order * \li a refitting depot order * \li a non-trivial conditional order - * \li nullptr if the vehicle won't stop anymore. + * \li INVALID_VEH_ORDER_ID if the vehicle won't stop anymore. */ -const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const +VehicleOrderID OrderList::GetNextDecisionNode(VehicleOrderID next, uint hops) const { - if (hops > this->GetNumOrders() || next == nullptr) return nullptr; + if (hops > this->GetNumOrders() || next >= this->GetNumOrders()) return INVALID_VEH_ORDER_ID; - if (next->IsType(OT_CONDITIONAL)) { - if (next->GetConditionVariable() != OCV_UNCONDITIONALLY) return next; + const Order &order_next = this->orders[next]; + if (order_next.IsType(OT_CONDITIONAL)) { + if (order_next.GetConditionVariable() != OCV_UNCONDITIONALLY) return next; /* We can evaluate trivial conditions right away. They're conceptually * the same as regular order progression. */ return this->GetNextDecisionNode( - this->GetOrderAt(next->GetConditionSkipToOrder()), + order_next.GetConditionSkipToOrder(), hops + 1); } - if (next->IsType(OT_GOTO_DEPOT)) { - if ((next->GetDepotActionType() & ODATFB_HALT) != 0) return nullptr; - if (next->IsRefit()) return next; + if (order_next.IsType(OT_GOTO_DEPOT)) { + if ((order_next.GetDepotActionType() & ODATFB_HALT) != 0) return INVALID_VEH_ORDER_ID; + if (order_next.IsRefit()) return next; } - if (!next->CanLoadOrUnload()) { + if (!order_next.CanLoadOrUnload()) { return this->GetNextDecisionNode(this->GetNext(next), hops + 1); } @@ -385,44 +355,42 @@ const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const /** * Recursively determine the next deterministic station to stop at. * @param v The vehicle we're looking at. - * @param first Order to start searching at or nullptr to start at cur_implicit_order_index + 1. + * @param first Order to start searching at or INVALID_VEH_ORDER_ID to start at cur_implicit_order_index + 1. * @param hops Number of orders we have already looked at. * @return Next stopping station or StationID::Invalid(). * @pre The vehicle is currently loading and v->last_station_visited is meaningful. * @note This function may draw a random number. Don't use it from the GUI. */ -StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order *first, uint hops) const +StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, VehicleOrderID first, uint hops) const { - - const Order *next = first; - if (first == nullptr) { - next = this->GetOrderAt(v->cur_implicit_order_index); - if (next == nullptr) { - next = this->GetFirstOrder(); - if (next == nullptr) return StationID::Invalid().base(); + VehicleOrderID next = first; + if (first == INVALID_VEH_ORDER_ID) { + next = v->cur_implicit_order_index; + if (next == INVALID_VEH_ORDER_ID) { + next = v->orders->GetFirstOrder(); + if (next == INVALID_VEH_ORDER_ID) return StationID::Invalid().base(); } else { - /* GetNext never returns nullptr if there is a valid station in the list. + /* GetNext never returns INVALID_VEH_ORDER_ID if there is a valid station in the list. * As the given "next" is already valid and a station in the list, we - * don't have to check for nullptr here. */ + * don't have to check for INVALID_VEH_ORDER_ID here. */ next = this->GetNext(next); - assert(next != nullptr); + assert(next != INVALID_VEH_ORDER_ID); } } + auto orders = v->Orders(); do { next = this->GetNextDecisionNode(next, ++hops); /* Resolve possibly nested conditionals by estimation. */ - while (next != nullptr && next->IsType(OT_CONDITIONAL)) { + while (next != INVALID_VEH_ORDER_ID && orders[next].IsType(OT_CONDITIONAL)) { /* We return both options of conditional orders. */ - const Order *skip_to = this->GetNextDecisionNode( - this->GetOrderAt(next->GetConditionSkipToOrder()), hops); - const Order *advance = this->GetNextDecisionNode( - this->GetNext(next), hops); - if (advance == nullptr || advance == first || skip_to == advance) { - next = (skip_to == first) ? nullptr : skip_to; - } else if (skip_to == nullptr || skip_to == first) { - next = (advance == first) ? nullptr : advance; + VehicleOrderID skip_to = this->GetNextDecisionNode(orders[next].GetConditionSkipToOrder(), hops); + VehicleOrderID advance = this->GetNextDecisionNode(this->GetNext(next), hops); + if (advance == INVALID_VEH_ORDER_ID || advance == first || skip_to == advance) { + next = (skip_to == first) ? INVALID_VEH_ORDER_ID : skip_to; + } else if (skip_to == INVALID_VEH_ORDER_ID || skip_to == first) { + next = (advance == first) ? INVALID_VEH_ORDER_ID : advance; } else { StationIDStack st1 = this->GetNextStoppingStation(v, skip_to, hops); StationIDStack st2 = this->GetNextStoppingStation(v, advance, hops); @@ -433,14 +401,14 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order * } /* Don't return a next stop if the vehicle has to unload everything. */ - if (next == nullptr || ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && - next->GetDestination() == v->last_station_visited && - (next->GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) { + if (next == INVALID_VEH_ORDER_ID || ((orders[next].IsType(OT_GOTO_STATION) || orders[next].IsType(OT_IMPLICIT)) && + orders[next].GetDestination() == v->last_station_visited && + (orders[next].GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) { return StationID::Invalid().base(); } - } while (next->IsType(OT_GOTO_DEPOT) || next->GetDestination() == v->last_station_visited); + } while (orders[next].IsType(OT_GOTO_DEPOT) || orders[next].GetDestination() == v->last_station_visited); - return next->GetDestination().ToStationID().base(); + return orders[next].GetDestination().ToStationID().base(); } /** @@ -448,26 +416,11 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order * * @param new_order is the order to insert into the chain. * @param index is the position where the order is supposed to be inserted. */ -void OrderList::InsertOrderAt(Order *new_order, int index) +void OrderList::InsertOrderAt(Order &&order, VehicleOrderID index) { - if (this->first == nullptr) { - this->first = new_order; - } else { - if (index == 0) { - /* Insert as first or only order */ - new_order->next = this->first; - this->first = new_order; - } else if (index >= this->num_orders) { - /* index is after the last order, add it to the end */ - this->GetLastOrder()->next = new_order; - } else { - /* Put the new order in between */ - Order *order = this->GetOrderAt(index - 1); - new_order->next = order->next; - order->next = new_order; - } - } - ++this->num_orders; + auto it = std::ranges::next(std::begin(this->orders), index, std::end(this->orders)); + auto new_order = this->orders.emplace(it, std::move(order)); + if (!new_order->IsType(OT_IMPLICIT)) ++this->num_manual_orders; this->timetable_duration += new_order->GetTimetabledWait() + new_order->GetTimetabledTravel(); this->total_duration += new_order->GetWaitTime() + new_order->GetTravelTime(); @@ -478,7 +431,6 @@ void OrderList::InsertOrderAt(Order *new_order, int index) BaseStation *bs = BaseStation::Get(new_order->GetDestination().ToStationID()); if (bs->owner == OWNER_NONE) InvalidateWindowClassesData(WC_STATION_LIST, 0); } - } @@ -486,25 +438,17 @@ void OrderList::InsertOrderAt(Order *new_order, int index) * Remove an order from the order list and delete it. * @param index is the position of the order which is to be deleted. */ -void OrderList::DeleteOrderAt(int index) +void OrderList::DeleteOrderAt(VehicleOrderID index) { - if (index >= this->num_orders) return; + auto to_remove = std::ranges::next(std::begin(this->orders), index, std::end(this->orders)); + if (to_remove == std::end(this->orders)) return; - Order *to_remove; - - if (index == 0) { - to_remove = this->first; - this->first = to_remove->next; - } else { - Order *prev = GetOrderAt(index - 1); - to_remove = prev->next; - prev->next = to_remove->next; - } - --this->num_orders; if (!to_remove->IsType(OT_IMPLICIT)) --this->num_manual_orders; + this->timetable_duration -= (to_remove->GetTimetabledWait() + to_remove->GetTimetabledTravel()); this->total_duration -= (to_remove->GetWaitTime() + to_remove->GetTravelTime()); - delete to_remove; + + this->orders.erase(to_remove); } /** @@ -512,30 +456,17 @@ void OrderList::DeleteOrderAt(int index) * @param from is the zero-based position of the order to move. * @param to is the zero-based position where the order is moved to. */ -void OrderList::MoveOrder(int from, int to) +void OrderList::MoveOrder(VehicleOrderID from, VehicleOrderID to) { - if (from >= this->num_orders || to >= this->num_orders || from == to) return; + if (from == to) return; + if (from >= this->GetNumOrders()) return; + if (to >= this->GetNumOrders()) return; - Order *moving_one; - - /* Take the moving order out of the pointer-chain */ - if (from == 0) { - moving_one = this->first; - this->first = moving_one->next; + auto it = std::begin(this->orders); + if (from < to) { + std::rotate(it + from, it + from + 1, it + to + 1); } else { - Order *one_before = GetOrderAt(from - 1); - moving_one = one_before->next; - one_before->next = moving_one->next; - } - - /* Insert the moving_order again in the pointer-chain */ - if (to == 0) { - moving_one->next = this->first; - this->first = moving_one; - } else { - Order *one_before = GetOrderAt(to - 1); - moving_one->next = one_before->next; - one_before->next = moving_one; + std::rotate(it + to, it + from, it + from + 1); } } @@ -556,10 +487,10 @@ void OrderList::RemoveVehicle(Vehicle *v) */ bool OrderList::IsCompleteTimetable() const { - for (Order *o = this->first; o != nullptr; o = o->next) { + for (const Order &o : this->orders) { /* Implicit orders are, by definition, not timetabled. */ - if (o->IsType(OT_IMPLICIT)) continue; - if (!o->IsCompletelyTimetabled()) return false; + if (o.IsType(OT_IMPLICIT)) continue; + if (!o.IsCompletelyTimetabled()) return false; } return true; } @@ -578,13 +509,13 @@ void OrderList::DebugCheckSanity() const Debug(misc, 6, "Checking OrderList {} for sanity...", this->index); - for (const Order *o = this->first; o != nullptr; o = o->next) { + for (const Order &o : this->orders) { ++check_num_orders; - if (!o->IsType(OT_IMPLICIT)) ++check_num_manual_orders; - check_timetable_duration += o->GetTimetabledWait() + o->GetTimetabledTravel(); - check_total_duration += o->GetWaitTime() + o->GetTravelTime(); + if (!o.IsType(OT_IMPLICIT)) ++check_num_manual_orders; + check_timetable_duration += o.GetTimetabledWait() + o.GetTimetabledTravel(); + check_total_duration += o.GetWaitTime() + o.GetTravelTime(); } - assert(this->num_orders == check_num_orders); + assert(this->GetNumOrders() == check_num_orders); assert(this->num_manual_orders == check_num_manual_orders); assert(this->timetable_duration == check_timetable_duration); assert(this->total_duration == check_total_duration); @@ -595,7 +526,7 @@ void OrderList::DebugCheckSanity() const } assert(this->num_vehicles == check_num_vehicles); Debug(misc, 6, "... detected {} orders ({} manual), {} vehicles, {} timetabled, {} total", - (uint)this->num_orders, (uint)this->num_manual_orders, + (uint)this->GetNumOrders(), (uint)this->num_manual_orders, this->num_vehicles, this->timetable_duration, this->total_duration); } #endif @@ -607,10 +538,10 @@ void OrderList::DebugCheckSanity() const * @param o the order to check * @return true if the destination is a station */ -static inline bool OrderGoesToStation(const Vehicle *v, const Order *o) +static inline bool OrderGoesToStation(const Vehicle *v, const Order &o) { - return o->IsType(OT_GOTO_STATION) || - (v->type == VEH_AIRCRAFT && o->IsType(OT_GOTO_DEPOT) && o->GetDestination() != StationID::Invalid()); + return o.IsType(OT_GOTO_STATION) || + (v->type == VEH_AIRCRAFT && o.IsType(OT_GOTO_DEPOT) && o.GetDestination() != StationID::Invalid()); } /** @@ -657,20 +588,24 @@ TileIndex Order::GetLocation(const Vehicle *v, bool airport) const * @param conditional_depth Internal param for resolving conditional orders. * @return Maximum distance between the two orders. */ -uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth) +uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth) { - if (cur->IsType(OT_CONDITIONAL)) { + assert(v->orders != nullptr); + const OrderList &orderlist = *v->orders; + auto orders = orderlist.GetOrders(); + + if (orders[cur].IsType(OT_CONDITIONAL)) { if (conditional_depth > v->GetNumOrders()) return 0; conditional_depth++; - int dist1 = GetOrderDistance(prev, v->GetOrder(cur->GetConditionSkipToOrder()), v, conditional_depth); - int dist2 = GetOrderDistance(prev, cur->next == nullptr ? v->orders->GetFirstOrder() : cur->next, v, conditional_depth); + int dist1 = GetOrderDistance(prev, orders[cur].GetConditionSkipToOrder(), v, conditional_depth); + int dist2 = GetOrderDistance(prev, orderlist.GetNext(cur), v, conditional_depth); return std::max(dist1, dist2); } - TileIndex prev_tile = prev->GetLocation(v, true); - TileIndex cur_tile = cur->GetLocation(v, true); + TileIndex prev_tile = orders[prev].GetLocation(v, true); + TileIndex cur_tile = orders[cur].GetLocation(v, true); if (prev_tile == INVALID_TILE || cur_tile == INVALID_TILE) return 0; return v->type == VEH_AIRCRAFT ? DistanceSquare(prev_tile, cur_tile) : DistanceManhattan(prev_tile, cur_tile); } @@ -882,13 +817,10 @@ CommandCost CmdInsertOrder(DoCommandFlags flags, VehicleID veh, VehicleOrderID s if (sel_ord > v->GetNumOrders()) return CMD_ERROR; if (v->GetNumOrders() >= MAX_VEH_ORDER_ID) return CommandCost(STR_ERROR_TOO_MANY_ORDERS); - if (!Order::CanAllocateItem()) return CommandCost(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS); if (v->orders == nullptr && !OrderList::CanAllocateItem()) return CommandCost(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS); if (flags.Test(DoCommandFlag::Execute)) { - Order *new_o = new Order(); - new_o->AssignOrder(new_order); - InsertOrder(v, new_o, sel_ord); + InsertOrder(v, Order(new_order), sel_ord); } return CommandCost(); @@ -900,13 +832,13 @@ CommandCost CmdInsertOrder(DoCommandFlags flags, VehicleID veh, VehicleOrderID s * @param new_o The new order. * @param sel_ord The position the order should be inserted at. */ -void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord) +void InsertOrder(Vehicle *v, Order &&new_o, VehicleOrderID sel_ord) { /* Create new order and link in list */ if (v->orders == nullptr) { - v->orders = new OrderList(new_o, v); + v->orders = new OrderList(std::move(new_o), v); } else { - v->orders->InsertOrderAt(new_o, sel_ord); + v->orders->InsertOrderAt(std::move(new_o), sel_ord); } Vehicle *u = v->FirstShared(); @@ -948,14 +880,14 @@ void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord) /* As we insert an order, the order to skip to will be 'wrong'. */ VehicleOrderID cur_order_id = 0; - for (Order *order : v->Orders()) { - if (order->IsType(OT_CONDITIONAL)) { - VehicleOrderID order_id = order->GetConditionSkipToOrder(); + for (Order &order : v->Orders()) { + if (order.IsType(OT_CONDITIONAL)) { + VehicleOrderID order_id = order.GetConditionSkipToOrder(); if (order_id >= sel_ord) { - order->SetConditionSkipToOrder(order_id + 1); + order.SetConditionSkipToOrder(order_id + 1); } if (order_id == cur_order_id) { - order->SetConditionSkipToOrder((order_id + 1) % v->GetNumOrders()); + order.SetConditionSkipToOrder((order_id + 1) % v->GetNumOrders()); } } cur_order_id++; @@ -1065,16 +997,16 @@ void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord) /* As we delete an order, the order to skip to will be 'wrong'. */ VehicleOrderID cur_order_id = 0; - for (Order *order : v->Orders()) { - if (order->IsType(OT_CONDITIONAL)) { - VehicleOrderID order_id = order->GetConditionSkipToOrder(); + for (Order &order : v->Orders()) { + if (order.IsType(OT_CONDITIONAL)) { + VehicleOrderID order_id = order.GetConditionSkipToOrder(); if (order_id >= sel_ord) { order_id = std::max(order_id - 1, 0); } if (order_id == cur_order_id) { order_id = (order_id + 1) % v->GetNumOrders(); } - order->SetConditionSkipToOrder(order_id); + order.SetConditionSkipToOrder(order_id); } cur_order_id++; } @@ -1193,9 +1125,9 @@ CommandCost CmdMoveOrder(DoCommandFlags flags, VehicleID veh, VehicleOrderID mov } /* As we move an order, the order to skip to will be 'wrong'. */ - for (Order *order : v->Orders()) { - if (order->IsType(OT_CONDITIONAL)) { - VehicleOrderID order_id = order->GetConditionSkipToOrder(); + for (Order &order : v->Orders()) { + if (order.IsType(OT_CONDITIONAL)) { + VehicleOrderID order_id = order.GetConditionSkipToOrder(); if (order_id == moving_order) { order_id = target_order; } else if (order_id > moving_order && order_id <= target_order) { @@ -1203,7 +1135,7 @@ CommandCost CmdMoveOrder(DoCommandFlags flags, VehicleID veh, VehicleOrderID mov } else if (order_id < moving_order && order_id >= target_order) { order_id++; } - order->SetConditionSkipToOrder(order_id); + order.SetConditionSkipToOrder(order_id); } } @@ -1480,19 +1412,24 @@ CommandCost CmdModifyOrder(DoCommandFlags flags, VehicleID veh, VehicleOrderID s * @param first First order in the source order list. * @return True if the aircraft has enough range for the orders, false otherwise. */ -static bool CheckAircraftOrderDistance(const Aircraft *v_new, const Vehicle *v_order, const Order *first) +static bool CheckAircraftOrderDistance(const Aircraft *v_new, const Vehicle *v_order) { - if (first == nullptr || v_new->acache.cached_max_range == 0) return true; + assert(v_order->orders != nullptr); + const OrderList &orderlist = *v_order->orders; + if (v_new->acache.cached_max_range == 0) return true; + if (orderlist.GetNumOrders() == 0) return true; + + auto orders = orderlist.GetOrders(); /* Iterate over all orders to check the distance between all * 'goto' orders and their respective next order (of any type). */ - for (const Order *o = first; o != nullptr; o = o->next) { - switch (o->GetType()) { + for (VehicleOrderID cur = 0; cur < orderlist.GetNumOrders(); ++cur) { + switch (orders[cur].GetType()) { case OT_GOTO_STATION: case OT_GOTO_DEPOT: case OT_GOTO_WAYPOINT: /* If we don't have a next order, we've reached the end and must check the first order instead. */ - if (GetOrderDistance(o, o->next != nullptr ? o->next : first, v_order) > v_new->acache.cached_max_range_sqr) return false; + if (GetOrderDistance(cur, orderlist.GetNext(cur), v_order) > v_new->acache.cached_max_range_sqr) return false; break; default: break; @@ -1536,20 +1473,20 @@ CommandCost CmdCloneOrder(DoCommandFlags flags, CloneOptions action, VehicleID v /* Is the vehicle already in the shared list? */ if (src->FirstShared() == dst->FirstShared()) return CMD_ERROR; - for (const Order *order : src->Orders()) { + for (const Order &order : src->Orders()) { if (!OrderGoesToStation(dst, order)) continue; /* Allow copying unreachable destinations if they were already unreachable for the source. * This is basically to allow cloning / autorenewing / autoreplacing vehicles, while the stations * are temporarily invalid due to reconstruction. */ - const Station *st = Station::Get(order->GetDestination().ToStationID()); + const Station *st = Station::Get(order.GetDestination().ToStationID()); if (CanVehicleUseStation(src, st) && !CanVehicleUseStation(dst, st)) { return CommandCost(STR_ERROR_CAN_T_COPY_SHARE_ORDER, GetVehicleCannotUseStationReason(dst, st)); } } /* Check for aircraft range limits. */ - if (dst->type == VEH_AIRCRAFT && !CheckAircraftOrderDistance(Aircraft::From(dst), src, src->GetFirstOrder())) { + if (dst->type == VEH_AIRCRAFT && !CheckAircraftOrderDistance(Aircraft::From(dst), src)) { return CommandCost(STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE); } @@ -1587,49 +1524,44 @@ CommandCost CmdCloneOrder(DoCommandFlags flags, CloneOptions action, VehicleID v /* Trucks can't copy all the orders from busses (and visa versa), * and neither can helicopters and aircraft. */ - for (const Order *order : src->Orders()) { + for (const Order &order : src->Orders()) { if (!OrderGoesToStation(dst, order)) continue; - Station *st = Station::Get(order->GetDestination().ToStationID()); + Station *st = Station::Get(order.GetDestination().ToStationID()); if (!CanVehicleUseStation(dst, st)) { return CommandCost(STR_ERROR_CAN_T_COPY_SHARE_ORDER, GetVehicleCannotUseStationReason(dst, st)); } } /* Check for aircraft range limits. */ - if (dst->type == VEH_AIRCRAFT && !CheckAircraftOrderDistance(Aircraft::From(dst), src, src->GetFirstOrder())) { + if (dst->type == VEH_AIRCRAFT && !CheckAircraftOrderDistance(Aircraft::From(dst), src)) { return CommandCost(STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE); } /* make sure there are orders available */ - if (!Order::CanAllocateItem(src->GetNumOrders()) || !OrderList::CanAllocateItem()) { + if (!OrderList::CanAllocateItem()) { return CommandCost(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS); } if (flags.Test(DoCommandFlag::Execute)) { - Order *first = nullptr; - Order **order_dst; - /* If the destination vehicle had an order list, destroy the chain but keep the OrderList. * We only reset the order indices, if the new orders are obviously different. * (We mainly do this to keep the order indices valid and in range.) */ DeleteVehicleOrders(dst, true, dst->GetNumOrders() != src->GetNumOrders()); - order_dst = &first; - for (const Order *order : src->Orders()) { - *order_dst = new Order(); - (*order_dst)->AssignOrder(*order); - order_dst = &(*order_dst)->next; + std::vector dst_orders; + for (const Order &order : src->Orders()) { + dst_orders.emplace_back(order); } - if (dst->orders == nullptr) { - dst->orders = new OrderList(first, dst); - } else { - assert(dst->orders->GetFirstOrder() == nullptr); + + if (dst->orders != nullptr) { + assert(dst->orders->GetNumOrders() == 0); assert(!dst->orders->IsShared()); delete dst->orders; - assert(OrderList::CanAllocateItem()); - dst->orders = new OrderList(first, dst); } + assert(OrderList::CanAllocateItem()); + dst->orders = new OrderList(std::move(dst_orders), dst); + InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS); InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0); @@ -1720,15 +1652,15 @@ void CheckOrders(const Vehicle *v) /* Check the order list */ int n_st = 0; - for (const Order *order : v->Orders()) { + for (const Order &order : v->Orders()) { /* Dummy order? */ - if (order->IsType(OT_DUMMY)) { + if (order.IsType(OT_DUMMY)) { message = STR_NEWS_VEHICLE_HAS_VOID_ORDER; break; } /* Does station have a load-bay for this vehicle? */ - if (order->IsType(OT_GOTO_STATION)) { - const Station *st = Station::Get(order->GetDestination().ToStationID()); + if (order.IsType(OT_GOTO_STATION)) { + const Station *st = Station::Get(order.GetDestination().ToStationID()); n_st++; if (!CanVehicleUseStation(v, st)) { @@ -1745,9 +1677,9 @@ void CheckOrders(const Vehicle *v) /* Check if the last and the first order are the same */ if (v->GetNumOrders() > 1) { - const Order *last = v->GetLastOrder(); + auto orders = v->Orders(); - if (v->orders->GetFirstOrder()->Equals(*last)) { + if (orders.front().Equals(orders.back())) { message = STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY; } } @@ -1788,12 +1720,12 @@ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool SetWindowDirty(WC_VEHICLE_VIEW, v->index); } - /* Clear the order from the order-list */ - int id = -1; - for (Order *order : v->Orders()) { - id++; -restart: + if (v->orders == nullptr) continue; + /* Clear the order from the order-list */ + for (VehicleOrderID id = 0, next_id = 0; id < v->GetNumOrders(); id = next_id) { + next_id = id + 1; + Order *order = v->orders->GetOrderAt(id); OrderType ot = order->GetType(); if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue; if (ot == OT_GOTO_DEPOT && hangar && v->type != VEH_AIRCRAFT) continue; // Not an aircraft? Can't have a hangar order. @@ -1803,10 +1735,9 @@ restart: * dummy orders. They should just vanish. Also check the actual order * type as ot is currently OT_GOTO_STATION. */ if (order->IsType(OT_IMPLICIT)) { - order = order->next; // DeleteOrder() invalidates current order DeleteOrder(v, id); - if (order != nullptr) goto restart; - break; + next_id = id; + continue; } /* Clear wait time */ @@ -1840,11 +1771,7 @@ restart: */ bool Vehicle::HasDepotOrder() const { - for (const Order *order : this->Orders()) { - if (order->IsType(OT_GOTO_DEPOT)) return true; - } - - return false; + return std::ranges::any_of(this->Orders(), [](const Order &order) { return order.IsType(OT_GOTO_DEPOT); }); } /** @@ -1910,19 +1837,7 @@ uint16_t GetServiceIntervalClamped(int interval, bool ispercent) */ static bool CheckForValidOrders(const Vehicle *v) { - for (const Order *order : v->Orders()) { - switch (order->GetType()) { - case OT_GOTO_STATION: - case OT_GOTO_DEPOT: - case OT_GOTO_WAYPOINT: - return true; - - default: - break; - } - } - - return false; + return std::ranges::any_of(v->Orders(), [](const Order &order) { return order.IsGotoOrder(); }); } /** diff --git a/src/order_func.h b/src/order_func.h index 12f7d4684a..ff7d865f1a 100644 --- a/src/order_func.h +++ b/src/order_func.h @@ -22,9 +22,9 @@ void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_ord bool ProcessOrders(Vehicle *v); bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false); VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v); -uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth = 0); +uint GetOrderDistance(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0); -void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int left, int middle, int right); +void DrawOrderString(const Vehicle *v, const Order *order, VehicleOrderID order_index, int y, bool selected, bool timetable, int left, int middle, int right); static const uint DEF_SERVINT_DAYS_TRAINS = 150; static const uint DEF_SERVINT_DAYS_ROADVEH = 150; diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 9a44e263c8..965d3e90d8 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -225,7 +225,7 @@ static StringID GetOrderGoToString(const Order &order) * @param middle X position between order index and order text * @param right Right border for text drawing */ -void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int left, int middle, int right) +void DrawOrderString(const Vehicle *v, const Order *order, VehicleOrderID order_index, int y, bool selected, bool timetable, int left, int middle, int right) { bool rtl = _current_text_dir == TD_RTL; @@ -355,8 +355,7 @@ void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int /* Check range for aircraft. */ if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->GetRange() > 0 && order->IsGotoOrder()) { - const Order *next = order->next != nullptr ? order->next : v->GetFirstOrder(); - if (GetOrderDistance(order, next, v) > Aircraft::From(v)->acache.cached_max_range_sqr) { + if (GetOrderDistance(order_index, v->orders->GetNext(order_index), v) > Aircraft::From(v)->acache.cached_max_range_sqr) { line += GetString(STR_ORDER_OUT_OF_RANGE); } } @@ -372,9 +371,7 @@ void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int */ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile) { - /* Override the index as it is not coming from a pool, so would not be initialised correctly. */ - Order order; - order.index = OrderID::Begin(); + Order order{}; /* check depot first */ if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsTileOwner(tile, _local_company)) { @@ -657,9 +654,7 @@ private: */ void OrderClick_NearestDepot() { - Order order; - order.next = nullptr; - order.index = OrderID::Begin(); + Order order{}; order.MakeGoToDepot(DepotID::Invalid(), ODTFB_PART_OF_ORDERS, _settings_client.gui.new_nonstop && this->vehicle->IsGroundVehicle() ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE); order.SetDepotActionType(ODATFB_NEAREST_DEPOT); @@ -816,10 +811,7 @@ public: if (_settings_client.gui.quick_goto && v->owner == _local_company) { /* If there are less than 2 station, make Go To active. */ - int station_orders = 0; - for (const Order *order : v->Orders()) { - if (order->IsType(OT_GOTO_STATION)) station_orders++; - } + int station_orders = std::ranges::count_if(v->Orders(), [](const Order &order) { return order.IsType(OT_GOTO_STATION); }); if (station_orders < 2) this->OrderClick_Goto(OPOS_GOTO); } @@ -1114,11 +1106,12 @@ public: int y = ir.top; int line_height = this->GetWidget(WID_O_ORDER_LIST)->resize_y; - int i = this->vscroll->GetPosition(); - const Order *order = this->vehicle->GetOrder(i); + VehicleOrderID i = this->vscroll->GetPosition(); + VehicleOrderID num_orders = this->vehicle->GetNumOrders(); + /* First draw the highlighting underground if it exists. */ if (this->order_over != INVALID_VEH_ORDER_ID) { - while (order != nullptr) { + while (i < num_orders) { /* Don't draw anything if it extends past the end of the window. */ if (!this->vscroll->IsVisible(i)) break; @@ -1133,25 +1126,22 @@ public: y += line_height; i++; - order = order->next; } /* Reset counters for drawing the orders. */ y = ir.top; i = this->vscroll->GetPosition(); - order = this->vehicle->GetOrder(i); } /* Draw the orders. */ - while (order != nullptr) { + while (i < num_orders) { /* Don't draw anything if it extends past the end of the window. */ if (!this->vscroll->IsVisible(i)) break; - DrawOrderString(this->vehicle, order, i, y, i == this->selected_order, false, ir.left, middle, ir.right); + DrawOrderString(this->vehicle, this->vehicle->GetOrder(i), i, y, i == this->selected_order, false, ir.left, middle, ir.right); y += line_height; i++; - order = order->next; } if (this->vscroll->IsVisible(i)) { @@ -1203,9 +1193,7 @@ public: if (this->goto_type == OPOS_CONDITIONAL) { VehicleOrderID order_id = this->GetOrderFromPt(_cursor.pos.y - this->top); if (order_id != INVALID_VEH_ORDER_ID) { - Order order; - order.next = nullptr; - order.index = OrderID::Begin(); + Order order{}; order.MakeConditional(order_id); Command::Post(STR_ERROR_CAN_T_INSERT_NEW_ORDER, this->vehicle->tile, this->vehicle->index, this->OrderGetSel(), order); diff --git a/src/order_type.h b/src/order_type.h index aa3bbbfccd..d52b33599b 100644 --- a/src/order_type.h +++ b/src/order_type.h @@ -16,7 +16,6 @@ #include "station_type.h" typedef uint8_t VehicleOrderID; ///< The index of an order within its current vehicle (not pool related) -using OrderID = PoolID; using OrderListID = PoolID; struct DestinationID { diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 50934f9814..4e6899ea36 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -76,6 +76,7 @@ #include "../safeguards.h" extern Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid()); +extern void ClearOldOrders(); /** * Makes a tile canal or water depending on the surroundings. @@ -818,6 +819,9 @@ bool AfterLoadGame() /* Update all vehicles: Phase 1 */ AfterLoadVehiclesPhase1(true); + /* Old orders are no longer needed. */ + ClearOldOrders(); + /* make sure there is a town in the game */ if (_game_mode == GM_NORMAL && Town::GetNumItems() == 0) { SetSaveLoadError(STR_ERROR_NO_TOWN_IN_SCENARIO); @@ -1485,8 +1489,10 @@ bool AfterLoadGame() /* Setting no refit flags to all orders in savegames from before refit in orders were added */ if (IsSavegameVersionBefore(SLV_36)) { - for (Order *order : Order::Iterate()) { - order->SetRefit(CARGO_NO_REFIT); + for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &order : orderlist->GetOrders()) { + order.SetRefit(CARGO_NO_REFIT); + } } for (Vehicle *v : Vehicle::Iterate()) { @@ -1781,25 +1787,31 @@ bool AfterLoadGame() if (IsSavegameVersionBefore(SLV_93)) { /* Rework of orders. */ - for (Order *order : Order::Iterate()) order->ConvertFromOldSavegame(); + for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &o : orderlist->GetOrders()) { + o.ConvertFromOldSavegame(); + } + } for (Vehicle *v : Vehicle::Iterate()) { - if (v->orders != nullptr && v->orders->GetFirstOrder() != nullptr && v->orders->GetFirstOrder()->IsType(OT_NOTHING)) { + if (v->orders != nullptr && v->GetFirstOrder() != nullptr && v->GetFirstOrder()->IsType(OT_NOTHING)) { v->orders->FreeChain(); v->orders = nullptr; } v->current_order.ConvertFromOldSavegame(); if (v->type == VEH_ROAD && v->IsPrimaryVehicle() && v->FirstShared() == v) { - for (Order *order : v->Orders()) order->SetNonStopType(ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS); + for (Order &order : v->Orders()) order.SetNonStopType(ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS); } } } else if (IsSavegameVersionBefore(SLV_94)) { /* Unload and transfer are now mutual exclusive. */ - for (Order *order : Order::Iterate()) { - if ((order->GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) == (OUFB_UNLOAD | OUFB_TRANSFER)) { - order->SetUnloadType(OUFB_TRANSFER); - order->SetLoadType(OLFB_NO_LOAD); + for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &order : orderlist->GetOrders()) { + if ((order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) == (OUFB_UNLOAD | OUFB_TRANSFER)) { + order.SetUnloadType(OUFB_TRANSFER); + order.SetLoadType(OLFB_NO_LOAD); + } } } @@ -1811,9 +1823,11 @@ bool AfterLoadGame() } } else if (IsSavegameVersionBefore(SLV_DEPOT_UNBUNCHING)) { /* OrderDepotActionFlags were moved, instead of starting at bit 4 they now start at bit 3. */ - for (Order *order : Order::Iterate()) { - if (!order->IsType(OT_GOTO_DEPOT)) continue; - order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() >> 1)); + for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &order : orderlist->GetOrders()) { + if (!order.IsType(OT_GOTO_DEPOT)) continue; + order.SetDepotActionType((OrderDepotActionFlags)(order.GetDepotActionType() >> 1)); + } } for (Vehicle *v : Vehicle::Iterate()) { @@ -2175,8 +2189,10 @@ bool AfterLoadGame() /* Trains could now stop in a specific location. */ if (IsSavegameVersionBefore(SLV_117)) { - for (Order *o : Order::Iterate()) { - if (o->IsType(OT_GOTO_STATION)) o->SetStopLocation(OSL_PLATFORM_FAR_END); + for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &o : orderlist->GetOrders()) { + if (o.IsType(OT_GOTO_STATION)) o.SetStopLocation(OSL_PLATFORM_FAR_END); + } } } @@ -3054,11 +3070,11 @@ bool AfterLoadGame() } if (IsSavegameVersionBefore(SLV_190)) { - for (Order *order : Order::Iterate()) { - order->SetTravelTimetabled(order->GetTravelTime() > 0); - order->SetWaitTimetabled(order->GetWaitTime() > 0); - } for (OrderList *orderlist : OrderList::Iterate()) { + for (Order &order : orderlist->GetOrders()) { + order.SetTravelTimetabled(order.GetTravelTime() > 0); + order.SetWaitTimetabled(order.GetWaitTime() > 0); + } orderlist->RecalculateTimetableDuration(); } } diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index bd08924953..46585f0eab 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -643,16 +643,14 @@ static bool LoadOldOrder(LoadgameState &ls, int num) { if (!LoadChunk(ls, nullptr, order_chunk)) return false; - Order *o = new (OrderID(num)) Order(); - o->AssignOrder(UnpackOldOrder(_old_order)); + OldOrderSaveLoadItem &o = AllocateOldOrder(num); + o.order.AssignOrder(UnpackOldOrder(_old_order)); - if (o->IsType(OT_NOTHING)) { - delete o; - } else { + if (!o.order.IsType(OT_NOTHING) && num > 0) { /* Relink the orders to each other (in the orders for one vehicle are behind each other, * with an invalid order (OT_NOTHING) as indication that it is the last order */ - Order *prev = Order::GetIfValid(num - 1); - if (prev != nullptr) prev->next = o; + OldOrderSaveLoadItem *prev = GetOldOrder(num + 1 - 1); + if (prev != nullptr) prev->next = num + 1; // next is 1-based. } return true; @@ -1364,7 +1362,7 @@ bool LoadOldVehicle(LoadgameState &ls, int num) if (_old_order_ptr != 0 && _old_order_ptr != 0xFFFFFFFF) { uint max = _savegame_type == SGT_TTO ? 3000 : 5000; uint old_id = RemapOrderIndex(_old_order_ptr); - if (old_id < max) v->old_orders = Order::Get(old_id); // don't accept orders > max number of orders + if (old_id < max) v->old_orders = old_id + 1; } v->current_order.AssignOrder(UnpackOldOrder(_old_order)); diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp index 4d84ba9c7a..6e3c20e1a3 100644 --- a/src/saveload/order_sl.cpp +++ b/src/saveload/order_sl.cpp @@ -102,35 +102,61 @@ Order UnpackOldOrder(uint16_t packed) return order; } +/** Temporary storage for conversion from old order pool. */ +static std::vector _old_order_saveload_pool; + +/** + * Clear all old orders. + */ +void ClearOldOrders() +{ + _old_order_saveload_pool.clear(); + _old_order_saveload_pool.shrink_to_fit(); +} + +/** + * Get a pointer to an old order with the given reference index. + * @param ref_index Reference index (one-based) to get. + * @return Pointer to old order, or nullptr if not present. + */ +OldOrderSaveLoadItem *GetOldOrder(size_t ref_index) +{ + if (ref_index == 0) return nullptr; + assert(ref_index <= _old_order_saveload_pool.size()); + return &_old_order_saveload_pool[ref_index - 1]; +} + +/** + * Allocate an old order with the given pool index. + * @param pool_index Pool index (zero-based) to allocate. + * @return Reference to allocated old order. + */ +OldOrderSaveLoadItem &AllocateOldOrder(size_t pool_index) +{ + assert(pool_index < UINT32_MAX); + if (pool_index >= _old_order_saveload_pool.size()) _old_order_saveload_pool.resize(pool_index + 1); + return _old_order_saveload_pool[pool_index]; +} + SaveLoadTable GetOrderDescription() { static const SaveLoad _order_desc[] = { - SLE_VAR(Order, type, SLE_UINT8), - SLE_VAR(Order, flags, SLE_UINT8), - SLE_VAR(Order, dest, SLE_UINT16), - SLE_REF(Order, next, REF_ORDER), - SLE_CONDVAR(Order, refit_cargo, SLE_UINT8, SLV_36, SL_MAX_VERSION), - SLE_CONDVAR(Order, wait_time, SLE_UINT16, SLV_67, SL_MAX_VERSION), - SLE_CONDVAR(Order, travel_time, SLE_UINT16, SLV_67, SL_MAX_VERSION), - SLE_CONDVAR(Order, max_speed, SLE_UINT16, SLV_172, SL_MAX_VERSION), + SLE_VARNAME(OldOrderSaveLoadItem, order.type, "type", SLE_UINT8), + SLE_VARNAME(OldOrderSaveLoadItem, order.flags, "flags", SLE_UINT8), + SLE_VARNAME(OldOrderSaveLoadItem, order.dest, "dest", SLE_UINT16), + SLE_CONDVARNAME(OldOrderSaveLoadItem, next, "next", SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_69), + SLE_CONDVARNAME(OldOrderSaveLoadItem, next, "next", SLE_UINT32, SLV_69, SL_MAX_VERSION), + SLE_CONDVARNAME(OldOrderSaveLoadItem, order.refit_cargo, "refit_cargo", SLE_UINT8, SLV_36, SL_MAX_VERSION), + SLE_CONDVARNAME(OldOrderSaveLoadItem, order.wait_time, "wait_time", SLE_UINT16, SLV_67, SL_MAX_VERSION), + SLE_CONDVARNAME(OldOrderSaveLoadItem, order.travel_time, "travel_time", SLE_UINT16, SLV_67, SL_MAX_VERSION), + SLE_CONDVARNAME(OldOrderSaveLoadItem, order.max_speed, "max_speed", SLE_UINT16, SLV_172, SL_MAX_VERSION), }; return _order_desc; } struct ORDRChunkHandler : ChunkHandler { - ORDRChunkHandler() : ChunkHandler('ORDR', CH_TABLE) {} - - void Save() const override - { - const SaveLoadTable slt = GetOrderDescription(); - SlTableHeader(slt); - - for (Order *order : Order::Iterate()) { - SlSetArrayIndex(order->index); - SlObject(order, slt); - } - } + ORDRChunkHandler() : ChunkHandler('ORDR', CH_READONLY) {} void Load() const override { @@ -148,8 +174,8 @@ struct ORDRChunkHandler : ChunkHandler { SlCopy(&orders[0], len, SLE_UINT16); for (size_t i = 0; i < len; ++i) { - Order *o = new (OrderID(static_cast(i))) Order(); - o->AssignOrder(UnpackVersion4Order(orders[i])); + auto &item = AllocateOldOrder(i); + item.order.AssignOrder(UnpackVersion4Order(orders[i])); } } else if (IsSavegameVersionBefore(SLV_5, 2)) { len /= sizeof(uint32_t); @@ -158,22 +184,19 @@ struct ORDRChunkHandler : ChunkHandler { SlCopy(&orders[0], len, SLE_UINT32); for (size_t i = 0; i < len; ++i) { - new (OrderID(static_cast(i))) Order(GB(orders[i], 0, 8), GB(orders[i], 8, 8), GB(orders[i], 16, 16)); + auto &item = AllocateOldOrder(i); + item.order = Order(GB(orders[i], 0, 8), GB(orders[i], 8, 8), GB(orders[i], 16, 16)); } } - /* Update all the next pointer */ - for (Order *o : Order::Iterate()) { - size_t order_index = o->index.base(); - /* Delete invalid orders */ - if (o->IsType(OT_NOTHING)) { - delete o; - continue; + /* Update all the next pointer. The orders were built like this: + * While the order is valid, the previous order will get its next pointer set */ + for (uint32_t num = 1; OldOrderSaveLoadItem item : _old_order_saveload_pool) { + if (!item.order.IsType(OT_NOTHING) && num > 1) { + OldOrderSaveLoadItem *prev = GetOldOrder(num - 1); + if (prev != nullptr) prev->next = num; } - /* The orders were built like this: - * While the order is valid, set the previous will get its next pointer set */ - Order *prev = Order::GetIfValid(order_index - 1); - if (prev != nullptr) prev->next = o; + ++num; } } else { const std::vector slt = SlCompatTableHeader(GetOrderDescription(), _order_sl_compat); @@ -181,27 +204,42 @@ struct ORDRChunkHandler : ChunkHandler { int index; while ((index = SlIterateArray()) != -1) { - Order *order = new (OrderID(index)) Order(); - SlObject(order, slt); + auto &item = AllocateOldOrder(index); + SlObject(&item, slt); } } } - - void FixPointers() const override - { - /* Orders from old savegames have pointers corrected in Load_ORDR */ - if (IsSavegameVersionBefore(SLV_5, 2)) return; - - for (Order *o : Order::Iterate()) { - SlObject(o, GetOrderDescription()); - } - } }; +template +class SlOrders : public VectorSaveLoadHandler, T, Order> { +public: + inline static const SaveLoad description[] = { + SLE_VAR(Order, type, SLE_UINT8), + SLE_VAR(Order, flags, SLE_UINT8), + SLE_VAR(Order, dest, SLE_UINT16), + SLE_VAR(Order, refit_cargo, SLE_UINT8), + SLE_VAR(Order, wait_time, SLE_UINT16), + SLE_VAR(Order, travel_time, SLE_UINT16), + SLE_VAR(Order, max_speed, SLE_UINT16), + }; + inline const static SaveLoadCompatTable compat_description = {}; + + std::vector &GetVector(T *container) const override { return container->orders; } + + void LoadCheck(T *container) const override { this->Load(container); } +}; + +/* Instantiate SlOrders classes. */ +template class SlOrders; +template class SlOrders; + SaveLoadTable GetOrderListDescription() { static const SaveLoad _orderlist_desc[] = { - SLE_REF(OrderList, first, REF_ORDER), + SLE_CONDVARNAME(OrderList, old_order_index, "first", SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_69), + SLE_CONDVARNAME(OrderList, old_order_index, "first", SLE_UINT32, SLV_69, SLV_ORDERS_OWNED_BY_ORDERLIST), + SLEG_CONDSTRUCTLIST("orders", SlOrders, SLV_ORDERS_OWNED_BY_ORDERLIST, SL_MAX_VERSION), }; return _orderlist_desc; @@ -228,8 +266,7 @@ struct ORDLChunkHandler : ChunkHandler { int index; while ((index = SlIterateArray()) != -1) { - /* set num_orders to 0 so it's a valid OrderList */ - OrderList *list = new (OrderListID(index)) OrderList(0); + OrderList *list = new (OrderListID(index)) OrderList(); SlObject(list, slt); } @@ -237,8 +274,18 @@ struct ORDLChunkHandler : ChunkHandler { void FixPointers() const override { + bool migrate_orders = IsSavegameVersionBefore(SLV_ORDERS_OWNED_BY_ORDERLIST); + for (OrderList *list : OrderList::Iterate()) { SlObject(list, GetOrderListDescription()); + + if (migrate_orders) { + std::vector orders; + for (OldOrderSaveLoadItem *old_order = GetOldOrder(list->old_order_index); old_order != nullptr; old_order = GetOldOrder(old_order->next)) { + orders.push_back(std::move(old_order->order)); + } + list->orders = std::move(orders); + } } } }; @@ -261,7 +308,9 @@ SaveLoadTable GetOrderBackupDescription() SLE_CONDVAR(OrderBackup, timetable_start, SLE_UINT64, SLV_TIMETABLE_START_TICKS_FIX, SL_MAX_VERSION), SLE_CONDVAR(OrderBackup, vehicle_flags, SLE_FILE_U8 | SLE_VAR_U16, SLV_176, SLV_180), SLE_CONDVAR(OrderBackup, vehicle_flags, SLE_UINT16, SLV_180, SL_MAX_VERSION), - SLE_REF(OrderBackup, orders, REF_ORDER), + SLE_CONDVARNAME(OrderBackup, old_order_index, "orders", SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_69), + SLE_CONDVARNAME(OrderBackup, old_order_index, "orders", SLE_UINT32, SLV_69, SLV_ORDERS_OWNED_BY_ORDERLIST), + SLEG_CONDSTRUCTLIST("orders", SlOrders, SLV_ORDERS_OWNED_BY_ORDERLIST, SL_MAX_VERSION), }; return _order_backup_desc; @@ -301,8 +350,18 @@ struct BKORChunkHandler : ChunkHandler { void FixPointers() const override { + bool migrate_orders = IsSavegameVersionBefore(SLV_ORDERS_OWNED_BY_ORDERLIST); + for (OrderBackup *ob : OrderBackup::Iterate()) { SlObject(ob, GetOrderBackupDescription()); + + if (migrate_orders) { + std::vector orders; + for (OldOrderSaveLoadItem *old_order = GetOldOrder(ob->old_order_index); old_order != nullptr; old_order = GetOldOrder(old_order->next)) { + orders.push_back(std::move(old_order->order)); + } + ob->orders = std::move(orders); + } } } }; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 761f2c4db4..f602bd1524 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -1222,7 +1222,6 @@ static size_t ReferenceToInt(const void *obj, SLRefType rt) case REF_VEHICLE: return ((const Vehicle*)obj)->index + 1; case REF_STATION: return ((const Station*)obj)->index + 1; case REF_TOWN: return ((const Town*)obj)->index + 1; - case REF_ORDER: return ((const Order*)obj)->index + 1; case REF_ROADSTOPS: return ((const RoadStop*)obj)->index + 1; case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1; case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1; @@ -1268,12 +1267,6 @@ static void *IntToReference(size_t index, SLRefType rt) if (OrderList::IsValidID(index)) return OrderList::Get(index); SlErrorCorrupt("Referencing invalid OrderList"); - case REF_ORDER: - if (Order::IsValidID(index)) return Order::Get(index); - /* in old versions, invalid order was used to mark end of order list */ - if (IsSavegameVersionBefore(SLV_5, 2)) return nullptr; - SlErrorCorrupt("Referencing invalid Order"); - case REF_VEHICLE_OLD: case REF_VEHICLE: if (Vehicle::IsValidID(index)) return Vehicle::Get(index); @@ -2907,11 +2900,14 @@ static void ResetSettings() } } +extern void ClearOldOrders(); + /** * Clear temporary data that is passed between various saveload phases. */ static void ResetSaveloadData() { + ClearOldOrders(); ResetTempEngineData(); ClearRailTypeLabelList(); ClearRoadTypeLabelList(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 1c896392de..ec78153db1 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -401,6 +401,7 @@ enum SaveLoadVersion : uint16_t { SLV_PROTECT_PLACED_HOUSES, ///< 351 PR#13270 Houses individually placed by players can be protected from town/AI removal. SLV_SCRIPT_SAVE_INSTANCES, ///< 352 PR#13556 Scripts are allowed to save instances. SLV_FIX_SCC_ENCODED_NEGATIVE, ///< 353 PR#14049 Fix encoding of negative parameters. + SLV_ORDERS_OWNED_BY_ORDERLIST, ///< 354 PR#13948 Orders stored in OrderList, pool removed. SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -602,7 +603,6 @@ public: /** Type of reference (#SLE_REF, #SLE_CONDREF). */ enum SLRefType : uint8_t { - REF_ORDER = 0, ///< Load/save a reference to an order. REF_VEHICLE = 1, ///< Load/save a reference to a vehicle. REF_STATION = 2, ///< Load/save a reference to a station. REF_TOWN = 3, ///< Load/save a reference to a town. diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 2ab144394e..7d3690d112 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -28,14 +28,14 @@ * Update the buoy orders to be waypoint orders. * @param o the order 'list' to check. */ -static void UpdateWaypointOrder(Order *o) +static void UpdateWaypointOrder(Order &o) { - if (!o->IsType(OT_GOTO_STATION)) return; + if (!o.IsType(OT_GOTO_STATION)) return; - const Station *st = Station::Get(o->GetDestination().ToStationID()); + const Station *st = Station::Get(o.GetDestination().ToStationID()); if ((st->had_vehicle_of_type & HVOT_WAYPOINT) == 0) return; - o->MakeGoToWaypoint(o->GetDestination().ToStationID()); + o.MakeGoToWaypoint(o.GetDestination().ToStationID()); } /** @@ -49,14 +49,14 @@ void MoveBuoysToWaypoints() VehicleType vt = ol->GetFirstSharedVehicle()->type; if (vt != VEH_SHIP && vt != VEH_TRAIN) continue; - for (Order *o = ol->GetFirstOrder(); o != nullptr; o = o->next) UpdateWaypointOrder(o); + for (Order &o : ol->GetOrders()) UpdateWaypointOrder(o); } for (Vehicle *v : Vehicle::Iterate()) { VehicleType vt = v->type; if (vt != VEH_SHIP && vt != VEH_TRAIN) continue; - UpdateWaypointOrder(&v->current_order); + UpdateWaypointOrder(v->current_order); } /* Now make the stations waypoints */ diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 300aa8233a..e9e6033911 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -274,10 +274,10 @@ void AfterLoadVehiclesPhase1(bool part_of_load) * a) both next_shared and previous_shared are not set for pre 5,2 games * b) both next_shared and previous_shared are set for later games */ - std::map mapping; + std::map mapping; for (Vehicle *v : Vehicle::Iterate()) { - if (v->old_orders != nullptr) { + if (v->orders != nullptr) { if (IsSavegameVersionBefore(SLV_105)) { // Pre-105 didn't save an OrderList if (mapping[v->old_orders] == nullptr) { /* This adds the whole shared vehicle chain for case b */ @@ -286,7 +286,11 @@ void AfterLoadVehiclesPhase1(bool part_of_load) * allowed in these savegames matches the number of OrderLists. As * such each vehicle can get an OrderList and it will (still) fit. */ assert(OrderList::CanAllocateItem()); - v->orders = mapping[v->old_orders] = new OrderList(v->old_orders, v); + std::vector orders; + for (const OldOrderSaveLoadItem *old_order = GetOldOrder(v->old_orders); old_order != nullptr; old_order = GetOldOrder(old_order->next)) { + orders.push_back(std::move(old_order->order)); + } + v->orders = mapping[v->old_orders] = new OrderList(std::move(orders), v); } else { v->orders = mapping[v->old_orders]; /* For old games (case a) we must create the shared vehicle chain */ @@ -296,7 +300,7 @@ void AfterLoadVehiclesPhase1(bool part_of_load) } } else { // OrderList was saved as such, only recalculate not saved values if (v->PreviousShared() == nullptr) { - v->orders->Initialize(v->orders->first, v); + v->orders->Initialize(v); } } } @@ -320,7 +324,8 @@ void AfterLoadVehiclesPhase1(bool part_of_load) /* As above, allocating OrderList here is safe. */ assert(OrderList::CanAllocateItem()); - v->orders = new OrderList(nullptr, v); + v->orders = new OrderList(); + v->orders->first_shared = v; for (Vehicle *u = v; u != nullptr; u = u->next_shared) { u->orders = v->orders; } @@ -715,7 +720,8 @@ public: SLE_CONDVAR(Vehicle, timetable_start, SLE_FILE_I32 | SLE_VAR_U64, SLV_129, SLV_TIMETABLE_START_TICKS), SLE_CONDVAR(Vehicle, timetable_start, SLE_UINT64, SLV_TIMETABLE_START_TICKS, SL_MAX_VERSION), - SLE_CONDREF(Vehicle, orders, REF_ORDER, SL_MIN_VERSION, SLV_105), + SLE_CONDVARNAME(Vehicle, old_orders, "orders", SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_69), + SLE_CONDVARNAME(Vehicle, old_orders, "orders", SLE_UINT32, SLV_69, SLV_105), SLE_CONDREF(Vehicle, orders, REF_ORDERLIST, SLV_105, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, age, SLE_FILE_U16 | SLE_VAR_I32, SL_MIN_VERSION, SLV_31), diff --git a/src/saveload/waypoint_sl.cpp b/src/saveload/waypoint_sl.cpp index ef8b401598..74c51befcb 100644 --- a/src/saveload/waypoint_sl.cpp +++ b/src/saveload/waypoint_sl.cpp @@ -49,14 +49,14 @@ static std::vector _old_waypoints; * Update the waypoint orders to get the new waypoint ID. * @param o the order 'list' to check. */ -static void UpdateWaypointOrder(Order *o) +static void UpdateWaypointOrder(Order &o) { - if (!o->IsType(OT_GOTO_WAYPOINT)) return; + if (!o.IsType(OT_GOTO_WAYPOINT)) return; for (OldWaypoint &wp : _old_waypoints) { - if (wp.index != o->GetDestination()) continue; + if (wp.index != o.GetDestination()) continue; - o->SetDestination(wp.new_index); + o.SetDestination(wp.new_index); return; } } @@ -147,13 +147,13 @@ void MoveWaypointsToBaseStations() for (OrderList *ol : OrderList::Iterate()) { if (ol->GetFirstSharedVehicle()->type != VEH_TRAIN) continue; - for (Order *o = ol->GetFirstOrder(); o != nullptr; o = o->next) UpdateWaypointOrder(o); + for (Order &o : ol->GetOrders()) UpdateWaypointOrder(o); } for (Vehicle *v : Vehicle::Iterate()) { if (v->type != VEH_TRAIN) continue; - UpdateWaypointOrder(&v->current_order); + UpdateWaypointOrder(v->current_order); } ResetOldWaypoints(); diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 6f9a018459..255bb8790a 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -8,6 +8,7 @@ /** @file script_order.cpp Implementation of ScriptOrder. */ #include "../../stdafx.h" +#include #include "script_order.hpp" #include "script_cargo.hpp" #include "script_map.hpp" @@ -67,15 +68,12 @@ static const Order *ResolveOrder(VehicleID vehicle_id, ScriptOrder::OrderPositio order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); if (order_position == ScriptOrder::ORDER_INVALID) return nullptr; } - const Order *order = v->GetFirstOrder(); - assert(order != nullptr); - while (order->GetType() == OT_IMPLICIT) order = order->next; - while (order_position > 0) { - order_position = (ScriptOrder::OrderPosition)(order_position - 1); - order = order->next; - while (order->GetType() == OT_IMPLICIT) order = order->next; - } - return order; + + auto real_orders = v->Orders() | std::views::filter([](const Order &order) { return !order.IsType(OT_IMPLICIT); }); + auto it = std::ranges::next(std::begin(real_orders), order_position, std::end(real_orders)); + if (it != std::end(real_orders)) return &*it; + + return nullptr; } /** @@ -91,16 +89,16 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr assert(ScriptOrder::IsValidVehicleOrder(vehicle_id, order_position)); + int pos = (int)order_position; int res = (int)order_position; - const Order *order = v->orders->GetFirstOrder(); - assert(order != nullptr); - for (; order->GetType() == OT_IMPLICIT; order = order->next) res++; - while (order_position > 0) { - order_position = (ScriptOrder::OrderPosition)(order_position - 1); - order = order->next; - for (; order->GetType() == OT_IMPLICIT; order = order->next) res++; + for (const Order &order : v->Orders()) { + if (order.IsType(OT_IMPLICIT)) { + ++res; + } else { + if (pos == 0) break; + --pos; + } } - return res; } @@ -111,12 +109,18 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr */ static ScriptOrder::OrderPosition RealOrderPositionToScriptOrderPosition(VehicleID vehicle_id, int order_position) { - const Order *order = ::Vehicle::Get(vehicle_id)->GetFirstOrder(); - assert(order != nullptr); + const Vehicle *v = ::Vehicle::Get(vehicle_id); + int num_implicit_orders = 0; - for (int i = 0; i < order_position; i++) { - if (order->GetType() == OT_IMPLICIT) num_implicit_orders++; - order = order->next; + int pos = order_position; + for (const Order &order : v->Orders()) { + if (order.IsType(OT_IMPLICIT)) { + ++num_implicit_orders; + } else { + if (pos == 0) break; + --pos; + } + } return static_cast(order_position - num_implicit_orders); } diff --git a/src/script/api/script_stationlist.cpp b/src/script/api/script_stationlist.cpp index 564fbe5766..459c9100f5 100644 --- a/src/script/api/script_stationlist.cpp +++ b/src/script/api/script_stationlist.cpp @@ -34,8 +34,8 @@ ScriptStationList_Vehicle::ScriptStationList_Vehicle(VehicleID vehicle_id) const Vehicle *v = ::Vehicle::Get(vehicle_id); - for (Order *o = v->GetFirstOrder(); o != nullptr; o = o->next) { - if (o->IsType(OT_GOTO_STATION)) this->AddItem(o->GetDestination().ToStationID().base()); + for (const Order &o : v->Orders()) { + if (o.IsType(OT_GOTO_STATION)) this->AddItem(o.GetDestination().ToStationID().base()); } } diff --git a/src/script/api/script_waypointlist.cpp b/src/script/api/script_waypointlist.cpp index 1fb0f4bf8c..ad6ed72513 100644 --- a/src/script/api/script_waypointlist.cpp +++ b/src/script/api/script_waypointlist.cpp @@ -34,7 +34,7 @@ ScriptWaypointList_Vehicle::ScriptWaypointList_Vehicle(VehicleID vehicle_id) const Vehicle *v = ::Vehicle::Get(vehicle_id); - for (const Order *o = v->GetFirstOrder(); o != nullptr; o = o->next) { - if (o->IsType(OT_GOTO_WAYPOINT)) this->AddItem(o->GetDestination().ToStationID().base()); + for (const Order &o : v->Orders()) { + if (o.IsType(OT_GOTO_WAYPOINT)) this->AddItem(o.GetDestination().ToStationID().base()); } } diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index be16b3698a..d997e37080 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2752,8 +2752,8 @@ bool HasStationInUse(StationID station, bool include_company, CompanyID company) assert(v != nullptr); if ((v->owner == company) != include_company) continue; - for (const Order *order = orderlist->GetFirstOrder(); order != nullptr; order = order->next) { - if (order->GetDestination() == station && (order->IsType(OT_GOTO_STATION) || order->IsType(OT_GOTO_WAYPOINT))) { + for (const Order &order : orderlist->GetOrders()) { + if (order.GetDestination() == station && (order.IsType(OT_GOTO_STATION) || order.IsType(OT_GOTO_WAYPOINT))) { return true; } } @@ -4045,15 +4045,15 @@ void DeleteStaleLinks(Station *from) /* Have all vehicles refresh their next hops before deciding to * remove the node. */ std::vector vehicles; - for (OrderList *l : OrderList::Iterate()) { + for (const OrderList *l : OrderList::Iterate()) { bool found_from = false; bool found_to = false; - for (Order *order = l->GetFirstOrder(); order != nullptr; order = order->next) { - if (!order->IsType(OT_GOTO_STATION) && !order->IsType(OT_IMPLICIT)) continue; - if (order->GetDestination() == from->index) { + for (const Order &order : l->GetOrders()) { + if (!order.IsType(OT_GOTO_STATION) && !order.IsType(OT_IMPLICIT)) continue; + if (order.GetDestination() == from->index) { found_from = true; if (found_to) break; - } else if (order->GetDestination() == to->index) { + } else if (order.GetDestination() == to->index) { found_to = true; if (found_from) break; } diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index 48e11988b9..8022137ba6 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -479,7 +479,8 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling) assert(real_current_order != nullptr); VehicleOrderID first_manual_order = 0; - for (Order *o = v->GetFirstOrder(); o != nullptr && o->IsType(OT_IMPLICIT); o = o->next) { + for (const Order &o : v->Orders()) { + if (!o.IsType(OT_IMPLICIT)) break; ++first_manual_order; } diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp index b22369bc5d..4eafeb4fb4 100644 --- a/src/timetable_gui.cpp +++ b/src/timetable_gui.cpp @@ -102,15 +102,15 @@ bool VehicleIsAboveLatenessThreshold(TimerGameTick::Ticks ticks, bool round_to_d * @param travelling whether we are interested in the travel or the wait part. * @return true if the travel/wait time can be used. */ -static bool CanDetermineTimeTaken(const Order *order, bool travelling) +static bool CanDetermineTimeTaken(const Order &order, bool travelling) { /* Current order is conditional */ - if (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT)) return false; + if (order.IsType(OT_CONDITIONAL) || order.IsType(OT_IMPLICIT)) return false; /* No travel time and we have not already finished travelling */ - if (travelling && !order->IsTravelTimetabled()) return false; + if (travelling && !order.IsTravelTimetabled()) return false; /* No wait time but we are loading at this timetabled station */ - if (!travelling && !order->IsWaitTimetabled() && order->IsType(OT_GOTO_STATION) && - !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) { + if (!travelling && !order.IsWaitTimetabled() && order.IsType(OT_GOTO_STATION) && + !(order.GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) { return false; } @@ -139,7 +139,7 @@ static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID TimerGameTick::Ticks sum = offset; VehicleOrderID i = start; - const Order *order = v->GetOrder(i); + auto orders = v->Orders(); /* Cyclically loop over all orders until we reach the current one again. * As we may start at the current order, do a post-checking loop */ @@ -147,32 +147,26 @@ static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID /* Automatic orders don't influence the overall timetable; * they just add some untimetabled entries, but the time till * the next non-implicit order can still be known. */ - if (!order->IsType(OT_IMPLICIT)) { + if (!orders[i].IsType(OT_IMPLICIT)) { if (travelling || i != start) { - if (!CanDetermineTimeTaken(order, true)) return; - sum += order->GetTimetabledTravel(); + if (!CanDetermineTimeTaken(orders[i], true)) return; + sum += orders[i].GetTimetabledTravel(); table[i].arrival = sum; } - if (!CanDetermineTimeTaken(order, false)) return; - sum += order->GetTimetabledWait(); + if (!CanDetermineTimeTaken(orders[i], false)) return; + sum += orders[i].GetTimetabledWait(); table[i].departure = sum; } - ++i; - order = order->next; - if (i >= v->GetNumOrders()) { - i = 0; - assert(order == nullptr); - order = v->orders->GetFirstOrder(); - } + i = v->orders->GetNext(i); } while (i != start); /* When loading at a scheduled station we still have to treat the * travelling part of the first order. */ if (!travelling) { - if (!CanDetermineTimeTaken(order, true)) return; - sum += order->GetTimetabledTravel(); + if (!CanDetermineTimeTaken(orders[i], true)) return; + sum += orders[i].GetTimetabledTravel(); table[i].arrival = sum; } } @@ -443,25 +437,18 @@ struct TimetableWindow : Window { int index_column_width = GetStringBoundingBox(GetString(STR_ORDER_INDEX, GetParamMaxValue(v->GetNumOrders(), 2))).width + 2 * GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + WidgetDimensions::scaled.hsep_normal; int middle = rtl ? tr.right - index_column_width : tr.left + index_column_width; - const Order *order = v->GetOrder(order_id); - while (order != nullptr) { + auto orders = v->Orders(); + while (true) { /* Don't draw anything if it extends past the end of the window. */ if (!this->vscroll->IsVisible(i)) break; if (i % 2 == 0) { - DrawOrderString(v, order, order_id, tr.top, i == selected, true, tr.left, middle, tr.right); - - order_id++; - - if (order_id >= v->GetNumOrders()) { - order = v->GetOrder(0); - final_order = true; - } else { - order = order->next; - } + DrawOrderString(v, &orders[order_id], order_id, tr.top, i == selected, true, tr.left, middle, tr.right); + if (order_id > v->orders->GetNext(order_id)) final_order = true; + order_id = v->orders->GetNext(order_id); } else { TextColour colour; - std::string string = GetTimetableTravelString(*order, i, colour); + std::string string = GetTimetableTravelString(orders[order_id], i, colour); DrawString(rtl ? tr.left : middle, rtl ? middle : tr.right, tr.top, string, colour); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 99fc6bedf7..e688e19c6e 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2144,24 +2144,23 @@ void Vehicle::DeleteUnreachedImplicitOrders() } } - const Order *order = this->GetOrder(this->cur_implicit_order_index); - while (order != nullptr) { + auto orders = this->Orders(); + VehicleOrderID cur = this->cur_implicit_order_index; + while (cur != INVALID_VEH_ORDER_ID) { if (this->cur_implicit_order_index == this->cur_real_order_index) break; - if (order->IsType(OT_IMPLICIT)) { + if (orders[cur].IsType(OT_IMPLICIT)) { DeleteOrder(this, this->cur_implicit_order_index); /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */ - order = this->GetOrder(this->cur_implicit_order_index); } else { /* Skip non-implicit orders, e.g. service-orders */ - order = order->next; - this->cur_implicit_order_index++; - } - - /* Wrap around */ - if (order == nullptr) { - order = this->GetOrder(0); - this->cur_implicit_order_index = 0; + if (cur < this->orders->GetNext(cur)) { + this->cur_implicit_order_index++; + } else { + /* Wrapped around. */ + this->cur_implicit_order_index = 0; + } + cur = this->orders->GetNext(cur); } } } @@ -2201,7 +2200,7 @@ void Vehicle::BeginLoading() in_list->GetDestination() != this->last_station_visited)) { bool suppress_implicit_orders = HasBit(this->GetGroundVehicleFlags(), GVF_SUPPRESS_IMPLICIT_ORDERS); /* Do not create consecutive duplicates of implicit orders */ - Order *prev_order = this->cur_implicit_order_index > 0 ? this->GetOrder(this->cur_implicit_order_index - 1) : (this->GetNumOrders() > 1 ? this->GetLastOrder() : nullptr); + const Order *prev_order = this->cur_implicit_order_index > 0 ? this->GetOrder(this->cur_implicit_order_index - 1) : (this->GetNumOrders() > 1 ? this->GetLastOrder() : nullptr); if (prev_order == nullptr || (!prev_order->IsType(OT_IMPLICIT) && !prev_order->IsType(OT_GOTO_STATION)) || prev_order->GetDestination() != this->last_station_visited) { @@ -2238,33 +2237,30 @@ void Vehicle::BeginLoading() InvalidateVehicleOrder(this, 0); } else { /* Delete all implicit orders up to the station we just reached */ - const Order *order = this->GetOrder(this->cur_implicit_order_index); - while (!order->IsType(OT_IMPLICIT) || order->GetDestination() != this->last_station_visited) { - if (order->IsType(OT_IMPLICIT)) { + VehicleOrderID cur = this->cur_implicit_order_index; + auto orders = this->Orders(); + while (!orders[cur].IsType(OT_IMPLICIT) || orders[cur].GetDestination() != this->last_station_visited) { + if (orders[cur].IsType(OT_IMPLICIT)) { DeleteOrder(this, this->cur_implicit_order_index); /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */ - order = this->GetOrder(this->cur_implicit_order_index); } else { /* Skip non-implicit orders, e.g. service-orders */ - order = order->next; - this->cur_implicit_order_index++; + if (cur < this->orders->GetNext(cur)) { + this->cur_implicit_order_index++; + } else { + /* Wrapped around. */ + this->cur_implicit_order_index = 0; + } + cur = this->orders->GetNext(cur); } - - /* Wrap around */ - if (order == nullptr) { - order = this->GetOrder(0); - this->cur_implicit_order_index = 0; - } - assert(order != nullptr); } } } else if (!suppress_implicit_orders && - ((this->orders == nullptr ? OrderList::CanAllocateItem() : this->orders->GetNumOrders() < MAX_VEH_ORDER_ID)) && - Order::CanAllocateItem()) { + (this->orders == nullptr ? OrderList::CanAllocateItem() : this->orders->GetNumOrders() < MAX_VEH_ORDER_ID)) { /* Insert new implicit order */ - Order *implicit_order = new Order(); - implicit_order->MakeImplicit(this->last_station_visited); - InsertOrder(this, implicit_order, this->cur_implicit_order_index); + Order implicit_order{}; + implicit_order.MakeImplicit(this->last_station_visited); + InsertOrder(this, std::move(implicit_order), this->cur_implicit_order_index); if (this->cur_implicit_order_index > 0) --this->cur_implicit_order_index; /* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order. @@ -2434,10 +2430,9 @@ void Vehicle::HandleLoading(bool mode) */ bool Vehicle::HasFullLoadOrder() const { - for (Order *o : this->Orders()) { - if (o->IsType(OT_GOTO_STATION) && o->GetLoadType() & (OLFB_FULL_LOAD | OLF_FULL_LOAD_ANY)) return true; - } - return false; + return std::ranges::any_of(this->Orders(), [](const Order &o) { + return o.IsType(OT_GOTO_STATION) && o.GetLoadType() & (OLFB_FULL_LOAD | OLF_FULL_LOAD_ANY); + }); } /** @@ -2446,10 +2441,7 @@ bool Vehicle::HasFullLoadOrder() const */ bool Vehicle::HasConditionalOrder() const { - for (Order *o : this->Orders()) { - if (o->IsType(OT_CONDITIONAL)) return true; - } - return false; + return std::ranges::any_of(this->Orders(), [](const Order &o) { return o.IsType(OT_CONDITIONAL); }); } /** @@ -2458,10 +2450,9 @@ bool Vehicle::HasConditionalOrder() const */ bool Vehicle::HasUnbunchingOrder() const { - for (Order *o : this->Orders()) { - if (o->IsType(OT_GOTO_DEPOT) && o->GetDepotActionType() & ODATFB_UNBUNCH) return true; - } - return false; + return std::ranges::any_of(this->Orders(), [](const Order &o) { + return o.IsType(OT_GOTO_DEPOT) && (o.GetDepotActionType() & ODATFB_UNBUNCH); + }); } /** @@ -2472,7 +2463,7 @@ static bool PreviousOrderIsUnbunching(const Vehicle *v) { /* If we are headed for the first order, we must wrap around back to the last order. */ bool is_first_order = (v->GetOrder(v->cur_implicit_order_index) == v->GetFirstOrder()); - Order *previous_order = (is_first_order) ? v->GetLastOrder() : v->GetOrder(v->cur_implicit_order_index - 1); + const Order *previous_order = (is_first_order) ? v->GetLastOrder() : v->GetOrder(v->cur_implicit_order_index - 1); if (previous_order == nullptr || !previous_order->IsType(OT_GOTO_DEPOT)) return false; return (previous_order->GetDepotActionType() & ODATFB_UNBUNCH) != 0; @@ -2942,7 +2933,7 @@ void Vehicle::AddToShared(Vehicle *shared_chain) if (shared_chain->orders == nullptr) { assert(shared_chain->previous_shared == nullptr); assert(shared_chain->next_shared == nullptr); - this->orders = shared_chain->orders = new OrderList(nullptr, shared_chain); + this->orders = shared_chain->orders = new OrderList(shared_chain); } this->next_shared = shared_chain->next_shared; @@ -3258,13 +3249,5 @@ bool VehiclesHaveSameEngineList(const Vehicle *v1, const Vehicle *v2) */ bool VehiclesHaveSameOrderList(const Vehicle *v1, const Vehicle *v2) { - const Order *o1 = v1->GetFirstOrder(); - const Order *o2 = v2->GetFirstOrder(); - while (true) { - if (o1 == nullptr && o2 == nullptr) return true; - if (o1 == nullptr || o2 == nullptr) return false; - if (!o1->Equals(*o2)) return false; - o1 = o1->next; - o2 = o2->next; - } + return std::ranges::equal(v1->Orders(), v2->Orders(), [](const Order &o1, const Order &o2) { return o1.Equals(o2); }); } diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 6595f03d99..58c311e804 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -337,7 +337,7 @@ public: union { OrderList *orders = nullptr; ///< Pointer to the order list for this vehicle - Order *old_orders; ///< Only used during conversion of old save games + uint32_t old_orders; ///< Only used during conversion of old save games }; NewGRFCache grf_cache{}; ///< Cache of often used calculated NewGRF values @@ -682,7 +682,19 @@ public: * Get the first order of the vehicles order list. * @return first order of order list. */ - inline Order *GetFirstOrder() const { return (this->orders == nullptr) ? nullptr : this->orders->GetFirstOrder(); } + inline const Order *GetFirstOrder() const { return (this->orders == nullptr) ? nullptr : this->GetOrder(this->orders->GetFirstOrder()); } + + inline std::span Orders() const + { + if (this->orders == nullptr) return {}; + return this->orders->GetOrders(); + } + + inline std::span Orders() + { + if (this->orders == nullptr) return {}; + return this->orders->GetOrders(); + } void AddToShared(Vehicle *shared_chain); void RemoveFromShared(); @@ -908,9 +920,9 @@ public: * Returns the last order of a vehicle, or nullptr if it doesn't exists * @return last order of a vehicle, if available */ - inline Order *GetLastOrder() const + inline const Order *GetLastOrder() const { - return (this->orders == nullptr) ? nullptr : this->orders->GetLastOrder(); + return (this->orders == nullptr) ? nullptr : this->orders->GetOrderAt(this->orders->GetLastOrder()); } bool IsEngineCountable() const; @@ -1014,54 +1026,6 @@ public: return v; } - /** - * Iterator to iterate orders - * Supports deletion of current order - */ - struct OrderIterator { - typedef Order value_type; - typedef Order *pointer; - typedef Order &reference; - typedef size_t difference_type; - typedef std::forward_iterator_tag iterator_category; - - explicit OrderIterator(OrderList *list) : list(list), prev(nullptr) - { - this->order = (this->list == nullptr) ? nullptr : this->list->GetFirstOrder(); - } - - bool operator==(const OrderIterator &other) const { return this->order == other.order; } - Order * operator*() const { return this->order; } - OrderIterator & operator++() - { - this->prev = (this->prev == nullptr) ? this->list->GetFirstOrder() : this->prev->next; - this->order = (this->prev == nullptr) ? nullptr : this->prev->next; - return *this; - } - - private: - OrderList *list; - Order *order; - Order *prev; - }; - - /** - * Iterable ensemble of orders - */ - struct IterateWrapper { - OrderList *list; - IterateWrapper(OrderList *list = nullptr) : list(list) {} - OrderIterator begin() { return OrderIterator(this->list); } - OrderIterator end() { return OrderIterator(nullptr); } - bool empty() { return this->begin() == this->end(); } - }; - - /** - * Returns an iterable ensemble of orders of a vehicle - * @return an iterable ensemble of orders of a vehicle - */ - IterateWrapper Orders() const { return IterateWrapper(this->orders); } - uint32_t GetDisplayMaxWeight() const; uint32_t GetDisplayMinPowerToWeight() const; }; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 2352e8ec62..ba13b43cdf 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -248,10 +248,7 @@ CommandCost CmdSellVehicle(DoCommandFlags flags, VehicleID v_id, bool sell_chain if (!front->IsStoppedInDepot()) return CommandCost(STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT + front->type); /* Can we actually make the order backup, i.e. are there enough orders? */ - if (backup_order && - front->orders != nullptr && - !front->orders->IsShared() && - !Order::CanAllocateItem(front->orders->GetNumOrders())) { + if (backup_order && front->orders != nullptr && !front->orders->IsShared()) { /* Only happens in exceptional cases when there aren't enough orders anyhow. * Thus it should be safe to just drop the orders in that case. */ backup_order = false; diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index ba389608c5..d0aff8f8ea 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -1658,8 +1658,8 @@ static constexpr NWidgetPart _nested_vehicle_list[] = { static void DrawSmallOrderList(const Vehicle *v, int left, int right, int y, uint order_arrow_width, VehicleOrderID start) { - const Order *order = v->GetOrder(start); - if (order == nullptr) return; + auto orders = v->Orders(); + if (orders.empty()) return; bool rtl = _current_text_dir == TD_RTL; int l_offset = rtl ? 0 : order_arrow_width; @@ -1670,37 +1670,32 @@ static void DrawSmallOrderList(const Vehicle *v, int left, int right, int y, uin do { if (oid == v->cur_real_order_index) DrawString(left, right, y, rtl ? STR_JUST_LEFT_ARROW : STR_JUST_RIGHT_ARROW, TC_BLACK, SA_LEFT, false, FS_SMALL); - if (order->IsType(OT_GOTO_STATION)) { - DrawString(left + l_offset, right - r_offset, y, GetString(STR_STATION_NAME, order->GetDestination()), TC_BLACK, SA_LEFT, false, FS_SMALL); + if (orders[oid].IsType(OT_GOTO_STATION)) { + DrawString(left + l_offset, right - r_offset, y, GetString(STR_STATION_NAME, orders[oid].GetDestination()), TC_BLACK, SA_LEFT, false, FS_SMALL); y += GetCharacterHeight(FS_SMALL); if (++i == 4) break; } - oid++; - order = order->next; - if (order == nullptr) { - order = v->orders->GetFirstOrder(); - oid = 0; - } + oid = v->orders->GetNext(oid); } while (oid != start); } /** Draw small order list in the vehicle GUI, but without the little black arrow. This is used for shared order groups. */ -static void DrawSmallOrderList(const Order *order, int left, int right, int y, uint order_arrow_width) +static void DrawSmallOrderList(const OrderList &orderlist, int left, int right, int y, uint order_arrow_width) { bool rtl = _current_text_dir == TD_RTL; int l_offset = rtl ? 0 : order_arrow_width; int r_offset = rtl ? order_arrow_width : 0; int i = 0; - while (order != nullptr) { - if (order->IsType(OT_GOTO_STATION)) { - DrawString(left + l_offset, right - r_offset, y, GetString(STR_STATION_NAME, order->GetDestination()), TC_BLACK, SA_LEFT, false, FS_SMALL); + + for (const Order &order : orderlist.GetOrders()) { + if (order.IsType(OT_GOTO_STATION)) { + DrawString(left + l_offset, right - r_offset, y, GetString(STR_STATION_NAME, order.GetDestination()), TC_BLACK, SA_LEFT, false, FS_SMALL); y += GetCharacterHeight(FS_SMALL); if (++i == 4) break; } - order = order->next; } } @@ -1849,7 +1844,7 @@ void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int DrawVehicleImage(vehgroup.vehicles_begin[i], {image_left + WidgetDimensions::scaled.hsep_wide * i, ir.top, image_right, ir.bottom}, selected_vehicle, EIT_IN_LIST, 0); } - if (show_orderlist) DrawSmallOrderList((vehgroup.vehicles_begin[0])->GetFirstOrder(), olr.left, olr.right, ir.top + GetCharacterHeight(FS_SMALL), this->order_arrow_width); + if (show_orderlist) DrawSmallOrderList(*(vehgroup.vehicles_begin[0])->orders, olr.left, olr.right, ir.top + GetCharacterHeight(FS_SMALL), this->order_arrow_width); DrawString(ir.left, ir.right, ir.top + WidgetDimensions::scaled.framerect.top, GetString(STR_JUST_COMMA, vehgroup.NumVehicles()), TC_BLACK); break; diff --git a/src/vehiclelist_func.h b/src/vehiclelist_func.h index 3e44610164..2a78fb0470 100644 --- a/src/vehiclelist_func.h +++ b/src/vehiclelist_func.h @@ -30,9 +30,9 @@ void FindVehiclesWithOrder(VehiclePredicate veh_pred, OrderPredicate ord_pred, V if (!veh_pred(v)) continue; /* Vehicle is a candidate, search for a matching order. */ - for (const Order *order = orderlist->GetFirstOrder(); order != nullptr; order = order->next) { + for (const Order &order : orderlist->GetOrders()) { - if (!ord_pred(order)) continue; + if (!ord_pred(&order)) continue; /* An order matches, we can add all shared vehicles to the list. */ for (; v != nullptr; v = v->NextShared()) {