/* $Id$ */ /* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** @file cargopacket.cpp Implementation of the cargo packets. */ #include "stdafx.h" #include "core/pool_func.hpp" #include "economy_base.h" #include "station_base.h" #include "cargodest_func.h" #include "cargodest_base.h" #include "settings_type.h" /* Initialize the cargopacket-pool */ CargoPacketPool _cargopacket_pool("CargoPacket"); INSTANTIATE_POOL_METHODS(CargoPacket) /** * Create a new packet for savegame loading. */ CargoPacket::CargoPacket() { this->source_type = ST_INDUSTRY; this->source_id = INVALID_SOURCE; this->dest_xy = INVALID_TILE; this->dest_id = INVALID_SOURCE; this->dest_type = ST_INDUSTRY; this->flags = 0; this->next_order = INVALID_ORDER; this->next_station = INVALID_STATION; } /** * Creates a new cargo packet. * @param source Source station of the packet. * @param source_xy Source location of the packet. * @param count Number of cargo entities to put in this packet. * @param source_type 'Type' of source the packet comes from (for subsidies). * @param source_id Actual source of the packet (for subsidies). * @param dest_xy Destination location of the packet. * @param dest_type 'Type' of the destination. * @param dest_id Actual destination of the packet. * @param next_order Desired next hop of the packet. * @param next_station Station to unload the packet next. * @param flags Routing flags of the packet. * @pre count != 0 * @note We have to zero memory ourselves here because we are using a 'new' * that, in contrary to all other pools, does not memset to 0. */ CargoPacket::CargoPacket(StationID source, TileIndex source_xy, uint16 count, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) : feeder_share(0), count(count), days_in_transit(0), source_id(source_id), source(source), source_xy(source_xy), loaded_at_xy(0), dest_xy(dest_xy), dest_id(dest_id), flags(flags), next_order(next_order), next_station(next_station) { assert(count != 0); this->source_type = source_type; this->dest_type = dest_type; } /** * Creates a new cargo packet. Initializes the fields that cannot be changed later. * Used when loading or splitting packets. * @param count Number of cargo entities to put in this packet. * @param days_in_transit Number of days the cargo has been in transit. * @param source Station the cargo was initially loaded. * @param source_xy Station location the cargo was initially loaded. * @param loaded_at_xy Location the cargo was loaded last. * @param feeder_share Feeder share the packet has already accumulated. * @param source_type 'Type' of source the packet comes from (for subsidies). * @param source_id Actual source of the packet (for subsidies). * @param dest_xy Destination location of the packet. * @param dest_type 'Type' of the destination. * @param dest_id Actual destination of the packet. * @param next_order Desired next hop of the packet. * @param next_station Station to unload the packet next. * @param flags Routing flags of the packet. * @note We have to zero memory ourselves here because we are using a 'new' * that, in contrary to all other pools, does not memset to 0. */ CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, TileIndex source_xy, TileIndex loaded_at_xy, Money feeder_share, SourceType source_type, SourceID source_id, TileIndex dest_xy, SourceType dest_type, SourceID dest_id, OrderID next_order, StationID next_station, byte flags) : feeder_share(feeder_share), count(count), days_in_transit(days_in_transit), source_id(source_id), source(source), source_xy(source_xy), loaded_at_xy(loaded_at_xy), dest_xy(dest_xy), dest_id(dest_id), flags(flags), next_order(next_order), next_station(next_station) { assert(count != 0); this->source_type = source_type; this->dest_type = dest_type; } /** * Split this packet in two and return the split off part. * @param new_size Size of the remaining part. * @return Split off part, or NULL if no packet could be allocated! */ inline CargoPacket *CargoPacket::Split(uint new_size) { if (!CargoPacket::CanAllocateItem()) return NULL; Money fs = this->feeder_share * new_size / static_cast(this->count); CargoPacket *cp_new = new CargoPacket(new_size, this->days_in_transit, this->source, this->source_xy, this->loaded_at_xy, fs, this->source_type, this->source_id, this->dest_xy, this->dest_type, this->dest_id, this->next_order, this->next_station, this->flags); this->feeder_share -= fs; this->count -= new_size; return cp_new; } /** * Merge another packet into this one. * @param cp Packet to be merged in. */ inline void CargoPacket::Merge(CargoPacket *cp) { this->count += cp->count; this->feeder_share += cp->feeder_share; delete cp; } /** * Invalidates (sets source_id to INVALID_SOURCE) all cargo packets from given source. * @param src_type Type of source. * @param src Index of source. */ /* static */ void CargoPacket::InvalidateAllFrom(SourceType src_type, SourceID src) { /* Clear next hop of those packets that loose their destination. */ StationCargoList::InvalidateAllTo(src_type, src); CargoPacket *cp; FOR_ALL_CARGOPACKETS(cp) { if (cp->source_type == src_type && cp->source_id == src) cp->source_id = INVALID_SOURCE; if (cp->dest_type == src_type && cp->dest_id == src) { cp->dest_id = INVALID_SOURCE; cp->dest_xy = INVALID_TILE; } } } /** * Invalidates (sets source to INVALID_STATION) all cargo packets from given station. * @param sid Station that gets removed. */ /* static */ void CargoPacket::InvalidateAllFrom(StationID sid) { CargoPacket *cp; FOR_ALL_CARGOPACKETS(cp) { if (cp->source == sid) cp->source = INVALID_STATION; if (cp->next_station == sid) cp->next_station = INVALID_STATION; } } /* * * Cargo list implementation * */ /** * Destroy the cargolist ("frees" all cargo packets). */ template CargoList::~CargoList() { for (Iterator it(this->packets.begin()); it != this->packets.end(); ++it) { delete *it; } } /** * Empty the cargo list, but don't free the cargo packets; * the cargo packets are cleaned by CargoPacket's CleanPool. */ template void CargoList::OnCleanPool() { this->packets.clear(); } /** * Update the cached values to reflect the removal of this packet. * Decreases count and days_in_transit. * @param cp Packet to be removed from cache. */ template void CargoList::RemoveFromCache(const CargoPacket *cp) { this->count -= cp->count; this->cargo_days_in_transit -= cp->days_in_transit * cp->count; } /** * Update the cache to reflect adding of this packet. * Increases count and days_in_transit. * @param cp New packet to be inserted. */ template void CargoList::AddToCache(const CargoPacket *cp) { this->count += cp->count; this->cargo_days_in_transit += cp->days_in_transit * cp->count; } /** * Appends the given cargo packet. Tries to merge it with another one in the * packets list. If no fitting packet is found, appends it. * @warning After appending this packet may not exist anymore! * @note Do not use the cargo packet anymore after it has been appended to this CargoList! * @param cp Cargo packet to add. * @pre cp != NULL */ template void CargoList::Append(CargoPacket *cp) { assert(cp != NULL); static_cast(this)->AddToCache(cp); for (List::reverse_iterator it(this->packets.rbegin()); it != this->packets.rend(); it++) { CargoPacket *icp = *it; if (Tinst::AreMergable(icp, cp) && icp->count + cp->count <= CargoPacket::MAX_COUNT) { icp->Merge(cp); return; } } /* The packet could not be merged with another one */ this->packets.push_back(cp); } /** * Truncates the cargo in this list to the given amount. It leaves the * first count cargo entities and removes the rest. * @param max_remaining Maximum amount of entities to be in the list after the command. */ template void CargoList::Truncate(uint max_remaining) { for (Iterator it(packets.begin()); it != packets.end(); /* done during loop*/) { CargoPacket *cp = *it; if (max_remaining == 0) { /* Nothing should remain, just remove the packets. */ it = this->packets.erase(it); static_cast(this)->RemoveFromCache(cp); delete cp; continue; } uint local_count = cp->count; if (local_count > max_remaining) { uint diff = local_count - max_remaining; this->count -= diff; this->cargo_days_in_transit -= cp->days_in_transit * diff; static_cast(this)->RemoveFromCacheLocal(cp, diff); cp->count = max_remaining; max_remaining = 0; } else { max_remaining -= local_count; } ++it; } } /** * Moves the given amount of cargo to another list. * Depending on the value of mta the side effects of this function differ: * - MTA_FINAL_DELIVERY: Destroys the packets that do not originate from a specific station. * - MTA_CARGO_LOAD: Sets the loaded_at_xy value of the moved packets. * - MTA_TRANSFER: Just move without side effects. * - MTA_UNLOAD: Just move without side effects. * - MTA_NO_ACTION: Does nothing for packets without destination, otherwise either like MTA_TRANSFER or MTA_FINAL_DELIVERY. * @param dest Destination to move the cargo to. * @param max_move Amount of cargo entities to move. * @param mta How to handle the moving (side effects). * @param st Station ID where we are loading/unloading or STATION_INVALID for move from vehicle to vehicle. * @param payment The payment helper. * @param cur_order The current order of the loading vehicle. * @param did_transfer Set to true if some cargo was transfered. * * @pre mta == MTA_FINAL_DELIVERY || dest != NULL * @pre mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL * @pre st != INVALID_STATION || (mta != MTA_CARGO_LOAD && payment == NULL) * @return True if there are still packets that might be moved from this cargo list. */ template template bool CargoList::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer) { assert(mta == MTA_FINAL_DELIVERY || dest != NULL); assert(mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL); assert(st != INVALID_STATION || (mta != MTA_CARGO_LOAD && payment == NULL)); restart:; Iterator it(this->packets.begin()); while (it != this->packets.end() && max_move > 0) { CargoPacket *cp = *it; MoveToAction cp_mta = mta; OrderID current_next_order = cp->NextHop(); StationID current_next_unload = cp->NextStation(); if (cp_mta == MTA_CARGO_LOAD) { /* Invalid next hop but valid destination? Recompute next hop. */ if (current_next_order == INVALID_ORDER && cp->DestinationID() != INVALID_SOURCE) { if (!this->UpdateCargoNextHop(cp, Station::Get(st), cid)) { /* Failed to find destination, drop packet. */ it = this->packets.erase(it); continue; } current_next_order = cp->NextHop(); current_next_unload = cp->NextStation(); } /* Loading and not for the current vehicle? Skip. */ if (current_next_order != cur_order) { ++it; continue; } } /* Has this packet a destination and are we unloading to a station (not autoreplace)? */ if (cp->DestinationID() != INVALID_SOURCE && cp_mta != MTA_CARGO_LOAD && payment != NULL) { /* Not forced unload and not for unloading at this station? Skip the packet. */ if (cp_mta != MTA_UNLOAD && cp->NextStation() != INVALID_STATION && cp->NextStation() != st) { ++it; continue; } Station *station = Station::Get(st); bool found; StationID next_unload; RouteLink *link = FindRouteLinkForCargo(station, cid, cp, &next_unload, cur_order, &found); if (!found) { /* Sorry, link to destination vanished, make cargo disappear. */ static_cast(this)->RemoveFromCache(cp); delete cp; it = this->packets.erase(it); continue; } if (link != NULL) { /* Not final destination. */ if (link->GetOriginOrderId() == cur_order && cp_mta != MTA_UNLOAD) { /* Cargo should stay on the vehicle and not forced unloading? Skip. */ ++it; continue; } /* Force transfer and update next hop. */ cp_mta = MTA_TRANSFER; current_next_order = link->GetOriginOrderId(); current_next_unload = next_unload; } else { /* Final destination, deliver. */ cp_mta = MTA_FINAL_DELIVERY; } } else if (cp_mta == MTA_NO_ACTION || (cp->source == st && cp_mta == MTA_FINAL_DELIVERY)) { /* Skip cargo that is not accepted or originated from this station. */ ++it; continue; } if (did_transfer != NULL && cp_mta == MTA_TRANSFER) *did_transfer = true; if (cp->count <= max_move) { /* Can move the complete packet */ max_move -= cp->count; it = this->packets.erase(it); static_cast(this)->RemoveFromCache(cp); cp->next_order = current_next_order; cp->next_station = current_next_unload; switch (cp_mta) { case MTA_FINAL_DELIVERY: payment->PayFinalDelivery(cp, cp->count); delete cp; continue; // of the loop case MTA_CARGO_LOAD: cp->loaded_at_xy = Station::Get(st)->xy; break; case MTA_TRANSFER: cp->feeder_share += payment->PayTransfer(cp, cp->count); break; default: break; } dest->Append(cp); continue; } /* Can move only part of the packet */ if (cp_mta == MTA_FINAL_DELIVERY) { /* Final delivery doesn't need package splitting. */ payment->PayFinalDelivery(cp, max_move); /* Remove the delivered data from the cache */ uint left = cp->count - max_move; cp->count = max_move; static_cast(this)->RemoveFromCache(cp); /* Final delivery payment pays the feeder share, so we have to * reset that so it is not 'shown' twice for partial unloads. */ cp->feeder_share = 0; cp->count = left; } else { /* But... the rest needs package splitting. */ CargoPacket *cp_new = cp->Split(max_move); /* We could not allocate a CargoPacket? Is the map that full? */ if (cp_new == NULL) return false; static_cast(this)->RemoveFromCache(cp_new); // this reflects the changes in cp. cp_new->next_order = current_next_order; cp_new->next_station = current_next_unload; if (cp_mta == MTA_TRANSFER) { /* Add the feeder share before inserting in dest. */ cp_new->feeder_share += payment->PayTransfer(cp_new, max_move); } else if (cp_mta == MTA_CARGO_LOAD) { cp_new->loaded_at_xy = Station::Get(st)->xy; } dest->Append(cp_new); } max_move = 0; } if (max_move > 0 && mta == MTA_CARGO_LOAD && cur_order != INVALID_ORDER && Station::Get(st)->goods[cid].cargo.CountForNextHop(INVALID_ORDER) > 0) { /* We loaded all packets for the next hop, now load all packets without destination. */ cur_order = INVALID_ORDER; goto restart; } return it != packets.end(); } /** Invalidates the cached data and rebuilds it. */ template void CargoList::InvalidateCache() { this->count = 0; this->cargo_days_in_transit = 0; for (ConstIterator it(this->packets.begin()); it != this->packets.end(); it++) { static_cast(this)->AddToCache(*it); } } /** * Update the cached values to reflect the removal of this packet. * Decreases count, feeder share and days_in_transit. * @param cp Packet to be removed from cache. */ void VehicleCargoList::RemoveFromCache(const CargoPacket *cp) { this->feeder_share -= cp->feeder_share; this->Parent::RemoveFromCache(cp); } /** * Update the cache to reflect adding of this packet. * Increases count, feeder share and days_in_transit. * @param cp New packet to be inserted. */ void VehicleCargoList::AddToCache(const CargoPacket *cp) { this->feeder_share += cp->feeder_share; this->Parent::AddToCache(cp); } /** * Ages the all cargo in this list. */ void VehicleCargoList::AgeCargo() { for (ConstIterator it(this->packets.begin()); it != this->packets.end(); it++) { CargoPacket *cp = *it; /* If we're at the maximum, then we can't increase no more. */ if (cp->days_in_transit == 0xFF) continue; cp->days_in_transit++; this->cargo_days_in_transit += cp->count; } } /** Invalidates the cached data and rebuild it. */ void VehicleCargoList::InvalidateCache() { this->feeder_share = 0; this->Parent::InvalidateCache(); } /** Invalidate next unload station of all cargo packets. */ void VehicleCargoList::InvalidateNextStation() { for (VehicleCargoList::ConstIterator it = this->packets.begin(); it != this->packets.end(); ++it) { (*it)->next_station = INVALID_STATION; } } /** * Update the local next-hop count cache. * @param cp Packet the be removed. * @param amount Cargo amount to be removed. */ void StationCargoList::RemoveFromCacheLocal(const CargoPacket *cp, uint amount) { this->order_cache[cp->next_order] -= amount; if (this->order_cache[cp->next_order] == 0) this->order_cache.erase(cp->next_order); } /** * Update the cached values to reflect the removal of this packet. * Decreases count and days_in_transit. * @param cp Packet to be removed from cache. */ void StationCargoList::RemoveFromCache(const CargoPacket *cp) { this->RemoveFromCacheLocal(cp, cp->count); this->Parent::RemoveFromCache(cp); } /** * Update the cache to reflect adding of this packet. * Increases count and days_in_transit. * @param cp New packet to be inserted. */ void StationCargoList::AddToCache(const CargoPacket *cp) { this->order_cache[cp->next_order] += cp->count; this->Parent::AddToCache(cp); } /** Invalidates the cached data and rebuild it. */ void StationCargoList::InvalidateCache() { this->order_cache.clear(); this->Parent::InvalidateCache(); } /** * Recompute the desired next hop of a cargo packet. * @param cp Cargo packet to update. * @param st Station of this list. * @param cid Cargo type of this list. * @return False if the packet was deleted, true otherwise. */ bool StationCargoList::UpdateCargoNextHop(CargoPacket *cp, Station *st, CargoID cid) { StationID next_unload; RouteLink *l = FindRouteLinkForCargo(st, cid, cp, &next_unload); if (l == NULL) { /* No link to destination, drop packet. */ this->RemoveFromCache(cp); delete cp; return false; } /* Update next hop info. */ this->RemoveFromCache(cp); cp->next_station = next_unload; cp->next_order = l->GetOriginOrderId(); this->AddToCache(cp); return true; } /** * Recompute the desired next hop of all cargo packets. * @param st Station of this list. * @param cid Cargo type of this list. */ void StationCargoList::UpdateCargoNextHop(Station *st, CargoID cid) { uint count = 0; StationCargoList::Iterator iter; for (iter = this->packets.begin(); count < this->next_start + _settings_game.economy.cargodest.route_recalc_chunk && iter != this->packets.end(); count++) { if (count < this->next_start) continue; if ((*iter)->DestinationID() != INVALID_SOURCE) { if (this->UpdateCargoNextHop(*iter, st, cid)) { ++iter; } else { iter = this->packets.erase(iter); } } else { ++iter; } } /* Update start counter for next loop. */ this->next_start = count; if (this->next_start >= this->packets.size()) this->next_start = 0; } /** * Invalidates the next hop info of all cargo packets with a given next order or unload station. * @param order Next order to invalidate. * @param st_unload Unload station to invalidate. */ /* static */ void StationCargoList::InvalidateAllTo(OrderID order, StationID st_unload) { Station *st; FOR_ALL_STATIONS(st) { for (CargoID cid = 0; cid < NUM_CARGO; cid++) { for (StationCargoList::Iterator it = st->goods[cid].cargo.packets.begin(); it != st->goods[cid].cargo.packets.end(); ++it) { CargoPacket *cp = *it; if (cp->next_order == order || cp->next_station == st_unload) { /* Invalidate both order and unload station as both likely * don't make sense anymore. */ st->goods[cid].cargo.RemoveFromCache(cp); cp->next_order = INVALID_ORDER; cp->next_station = INVALID_STATION; st->goods[cid].cargo.AddToCache(cp); } } } } } /** * Invalidates the next hop info of all cargo packets for a given destination. * @param order Next order to invalidate. */ /* static */ void StationCargoList::InvalidateAllTo(SourceType type, SourceID dest) { Station *st; FOR_ALL_STATIONS(st) { for (CargoID cid = 0; cid < NUM_CARGO; cid++) { for (StationCargoList::Iterator it = st->goods[cid].cargo.packets.begin(); it != st->goods[cid].cargo.packets.end(); ++it) { CargoPacket *cp = *it; if (cp->dest_id == dest && cp->dest_type == type) { /* Invalidate both next order and unload station as we * want the packets to be not routed anymore. */ st->goods[cid].cargo.RemoveFromCache(cp); cp->next_order = INVALID_ORDER; cp->next_station = INVALID_STATION; st->goods[cid].cargo.AddToCache(cp); } } } } } /* * We have to instantiate everything we want to be usable. */ template class CargoList; template class CargoList; /** Autoreplace Vehicle -> Vehicle 'transfer'. */ template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); /** Cargo unloading at a station. */ template bool CargoList::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer); /** Cargo loading at a station. */ template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, StationID st, OrderID cur_order, CargoID cid, bool *did_transfer);