diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 22f6b66be6..a3e9a3c776 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -1387,7 +1387,7 @@ static void MaybeCrashAirplane(Aircraft *v) /* Crash the airplane. Remove all goods stored at the station. */ for (GoodsEntry &ge : st->goods) { ge.rating = 1; - ge.cargo.Truncate(); + if (ge.HasData()) ge.GetData().cargo.Truncate(); } CrashAirplane(v); diff --git a/src/cachecheck.cpp b/src/cachecheck.cpp index a68fa7871c..d971bbb878 100644 --- a/src/cachecheck.cpp +++ b/src/cachecheck.cpp @@ -170,11 +170,14 @@ void CheckCaches() for (Station *st : Station::Iterate()) { for (GoodsEntry &ge : st->goods) { - [[maybe_unused]] const auto a = ge.cargo.PeriodsInTransit(); - [[maybe_unused]] const auto b = ge.cargo.TotalCount(); - ge.cargo.InvalidateCache(); - assert(a == ge.cargo.PeriodsInTransit()); - assert(b == ge.cargo.TotalCount()); + if (!ge.HasData()) continue; + + StationCargoList &cargo_list = ge.GetData().cargo; + [[maybe_unused]] const auto a = cargo_list.PeriodsInTransit(); + [[maybe_unused]] const auto b = cargo_list.TotalCount(); + cargo_list.InvalidateCache(); + assert(a == cargo_list.PeriodsInTransit()); + assert(b == cargo_list.TotalCount()); } /* Check docking tiles */ diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp index 02145491fd..ce4afa0abf 100644 --- a/src/cargopacket.cpp +++ b/src/cargopacket.cpp @@ -446,6 +446,9 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID Iterator it = this->packets.begin(); uint sum = 0; + static const FlowStatMap EMPTY_FLOW_STAT_MAP = {}; + const FlowStatMap &flows = ge->HasData() ? ge->GetData().flows : EMPTY_FLOW_STAT_MAP; + bool force_keep = (order_flags & OUFB_NO_UNLOAD) != 0; bool force_unload = (order_flags & OUFB_UNLOAD) != 0; bool force_transfer = (order_flags & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0; @@ -464,8 +467,8 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID action = MTA_TRANSFER; /* We cannot send the cargo to any of the possible next hops and * also not to the current station. */ - FlowStatMap::const_iterator flow_it(ge->flows.find(cp->first_station)); - if (flow_it == ge->flows.end()) { + FlowStatMap::const_iterator flow_it(flows.find(cp->first_station)); + if (flow_it == flows.end()) { cargo_next = INVALID_STATION; } else { FlowStat new_shares = flow_it->second; @@ -483,12 +486,12 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID } else { /* Rewrite an invalid source station to some random other one to * avoid keeping the cargo in the vehicle forever. */ - if (cp->first_station == INVALID_STATION && !ge->flows.empty()) { - cp->first_station = ge->flows.begin()->first; + if (cp->first_station == INVALID_STATION && !flows.empty()) { + cp->first_station = flows.begin()->first; } bool restricted = false; - FlowStatMap::const_iterator flow_it(ge->flows.find(cp->first_station)); - if (flow_it == ge->flows.end()) { + FlowStatMap::const_iterator flow_it(flows.find(cp->first_station)); + if (flow_it == flows.end()) { cargo_next = INVALID_STATION; } else { cargo_next = flow_it->second.GetViaWithRestricted(restricted); diff --git a/src/economy.cpp b/src/economy.cpp index 2f214ce5cc..df36851f44 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1444,7 +1444,7 @@ struct ReturnCargoAction */ bool operator()(Vehicle *v) { - v->cargo.Return(UINT_MAX, &this->st->goods[v->cargo_type].cargo, this->next_hop, v->GetCargoTile()); + v->cargo.Return(UINT_MAX, &this->st->goods[v->cargo_type].GetOrCreateData().cargo, this->next_hop, v->GetCargoTile()); return true; } }; @@ -1478,7 +1478,7 @@ struct FinalizeRefitAction bool operator()(Vehicle *v) { if (this->do_reserve) { - this->st->goods[v->cargo_type].cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), + this->st->goods[v->cargo_type].GetOrCreateData().cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), &v->cargo, this->next_station, v->GetCargoTile()); } this->consist_capleft[v->cargo_type] += v->cargo_cap - v->cargo.RemainingCount(); @@ -1511,7 +1511,7 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station /* Get a refittable cargo type with waiting cargo for next_station or INVALID_STATION. */ new_cid = v_start->cargo_type; for (CargoID cid : SetCargoBitIterator(refit_mask)) { - if (st->goods[cid].cargo.HasCargoFor(next_station)) { + if (st->goods[cid].HasData() && st->goods[cid].GetData().cargo.HasCargoFor(next_station)) { /* Try to find out if auto-refitting would succeed. In case the refit is allowed, * the returned refit capacity will be greater than zero. */ auto [cc, refit_capacity, mail_capacity, cargo_capacities] = Command::Do(DC_QUERY_COST, v_start->index, cid, 0xFF, true, false, 1); // Auto-refit and only this vehicle including artic parts. @@ -1523,7 +1523,7 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station * of 0 for all cargoes. */ if (refit_capacity > 0 && (consist_capleft[cid] < consist_capleft[new_cid] || (consist_capleft[cid] == consist_capleft[new_cid] && - st->goods[cid].cargo.AvailableCount() > st->goods[new_cid].cargo.AvailableCount()))) { + st->goods[cid].GetData().cargo.AvailableCount() > st->goods[new_cid].GetData().cargo.AvailableCount()))) { new_cid = cid; } } @@ -1569,7 +1569,7 @@ struct ReserveCargoAction { bool operator()(Vehicle *v) { if (v->cargo_cap > v->cargo.RemainingCount() && MayLoadUnderExclusiveRights(st, v)) { - st->goods[v->cargo_type].cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), + st->goods[v->cargo_type].GetOrCreateData().cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), &v->cargo, *next_station, v->GetCargoTile()); } @@ -1701,7 +1701,7 @@ static void LoadUnloadVehicle(Vehicle *front) uint new_remaining = v->cargo.RemainingCount() + v->cargo.ActionCount(VehicleCargoList::MTA_DELIVER); if (v->cargo_cap < new_remaining) { /* Return some of the reserved cargo to not overload the vehicle. */ - v->cargo.Return(new_remaining - v->cargo_cap, &ge->cargo, INVALID_STATION, v->GetCargoTile()); + v->cargo.Return(new_remaining - v->cargo_cap, &ge->GetOrCreateData().cargo, INVALID_STATION, v->GetCargoTile()); } /* Keep instead of delivering. This may lead to no cargo being unloaded, so ...*/ @@ -1729,7 +1729,7 @@ static void LoadUnloadVehicle(Vehicle *front) } assert(payment != nullptr); - amount_unloaded = v->cargo.Unload(amount_unloaded, &ge->cargo, v->cargo_type, payment, v->GetCargoTile()); + amount_unloaded = v->cargo.Unload(amount_unloaded, &ge->GetOrCreateData().cargo, v->cargo_type, payment, v->GetCargoTile()); remaining = v->cargo.UnloadCount() > 0; if (amount_unloaded > 0) { dirty_vehicle = true; @@ -1795,11 +1795,11 @@ static void LoadUnloadVehicle(Vehicle *front) /* If there's goods waiting at the station, and the vehicle * has capacity for it, load it on the vehicle. */ - if ((v->cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0 || ge->cargo.AvailableCount() > 0) && MayLoadUnderExclusiveRights(st, v)) { + if ((v->cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0 || (ge->HasData() && ge->GetData().cargo.AvailableCount() > 0)) && MayLoadUnderExclusiveRights(st, v)) { if (v->cargo.StoredCount() == 0) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO); if (_settings_game.order.gradual_loading) cap_left = std::min(cap_left, GetLoadAmount(v)); - uint loaded = ge->cargo.Load(cap_left, &v->cargo, next_station, v->GetCargoTile()); + uint loaded = ge->GetOrCreateData().cargo.Load(cap_left, &v->cargo, next_station, v->GetCargoTile()); if (v->cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0) { /* Remember if there are reservations left so that we don't stop * loading before they're loaded. */ @@ -1827,7 +1827,7 @@ static void LoadUnloadVehicle(Vehicle *front) st->time_since_load = 0; st->last_vehicle_type = v->type; - if (ge->cargo.TotalCount() == 0) { + if (ge->GetData().cargo.TotalCount() == 0) { TriggerStationRandomisation(st, st->xy, SRT_CARGO_TAKEN, v->cargo_type); TriggerStationAnimation(st, st->xy, SAT_CARGO_TAKEN, v->cargo_type); AirportAnimationTrigger(st, AAT_STATION_CARGO_TAKEN, v->cargo_type); diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index 100e6d6273..04cb1fda8f 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -223,7 +223,7 @@ void LinkGraphOverlay::AddLinks(const Station *from, const Station *to) if (lg[ge.node].HasEdgeTo(to->goods[c].node)) { ConstEdge &edge = lg[ge.node][to->goods[c].node]; this->AddStats(c, lg.Monthly(edge.capacity), lg.Monthly(edge.usage), - ge.flows.GetFlowVia(to->index), + ge.HasData() ? ge.GetData().flows.GetFlowVia(to->index) : 0, edge.TravelTime() / Ticks::DAY_TICKS, from->owner == OWNER_NONE || to->owner == OWNER_NONE, this->cached_links[from->index][to->index]); diff --git a/src/linkgraph/linkgraphjob.cpp b/src/linkgraph/linkgraphjob.cpp index e50935bd27..33f474fb28 100644 --- a/src/linkgraph/linkgraphjob.cpp +++ b/src/linkgraph/linkgraphjob.cpp @@ -122,6 +122,7 @@ LinkGraphJob::~LinkGraphJob() LinkGraph *lg = LinkGraph::Get(ge.link_graph); FlowStatMap &flows = from.flows; + FlowStatMap &geflows = ge.GetOrCreateData().flows; for (const auto &edge : from.edges) { if (edge.Flow() == 0) continue; @@ -137,7 +138,7 @@ LinkGraphJob::~LinkGraphJob() /* Delete old flows for source stations which have been deleted * from the new flows. This avoids flow cycles between old and * new flows. */ - while (!erased.IsEmpty()) ge.flows.erase(erased.Pop()); + while (!erased.IsEmpty()) geflows.erase(erased.Pop()); } else if ((*lg)[node_id][dest_id].last_unrestricted_update == EconomyTime::INVALID_DATE) { /* Edge is fully restricted. */ flows.RestrictFlows(to); @@ -148,7 +149,7 @@ LinkGraphJob::~LinkGraphJob() * really delete them as we could then end up with unroutable cargo * somewhere. Do delete them and also reroute relevant cargo if * automatic distribution has been turned off for that cargo. */ - for (FlowStatMap::iterator it(ge.flows.begin()); it != ge.flows.end();) { + for (FlowStatMap::iterator it(geflows.begin()); it != geflows.end();) { FlowStatMap::iterator new_it = flows.find(it->first); if (new_it == flows.end()) { if (_settings_game.linkgraph.GetDistributionType(this->Cargo()) != DT_MANUAL) { @@ -157,7 +158,7 @@ LinkGraphJob::~LinkGraphJob() } else { FlowStat shares(INVALID_STATION, 1); it->second.SwapShares(shares); - ge.flows.erase(it++); + geflows.erase(it++); for (FlowStat::SharesMap::const_iterator shares_it(shares.GetShares()->begin()); shares_it != shares.GetShares()->end(); ++shares_it) { RerouteCargo(st, this->Cargo(), shares_it->second, st->index); @@ -169,7 +170,8 @@ LinkGraphJob::~LinkGraphJob() ++it; } } - ge.flows.insert(flows.begin(), flows.end()); + geflows.insert(flows.begin(), flows.end()); + if (ge.GetData().IsEmpty()) ge.ClearData(); InvalidateWindowData(WC_STATION_VIEW, st->index, this->Cargo()); } } diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp index b2d949b6fc..fd9ce9873f 100644 --- a/src/newgrf_roadstop.cpp +++ b/src/newgrf_roadstop.cpp @@ -232,7 +232,7 @@ RoadStopResolverObject::RoadStopResolverObject(const RoadStopSpec *roadstopspec, /* Pick the first cargo that we have waiting */ for (const CargoSpec *cs : CargoSpec::Iterate()) { if (roadstopspec->grf_prop.spritegroup[cs->Index()] != nullptr && - station->goods[cs->Index()].cargo.TotalCount() > 0) { + station->goods[cs->Index()].HasData() && station->goods[cs->Index()].GetData().cargo.TotalCount() > 0) { ctype = cs->Index(); break; } diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index a1caf62e40..e1547c8ff3 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -434,10 +434,10 @@ uint32_t Station::GetNewGRFVariable(const ResolverObject &object, uint8_t variab const GoodsEntry *ge = &this->goods[c]; switch (variable) { - case 0x60: return std::min(ge->cargo.TotalCount(), 4095u); + case 0x60: return ge->HasData() ? std::min(ge->GetData().cargo.TotalCount(), 4095u) : 0; case 0x61: return ge->HasVehicleEverTriedLoading() ? ge->time_since_pickup : 0; case 0x62: return ge->HasRating() ? ge->rating : 0xFFFFFFFF; - case 0x63: return ge->cargo.PeriodsInTransit(); + case 0x63: return ge->HasData() ? ge->GetData().cargo.PeriodsInTransit() : 0; case 0x64: return ge->HasVehicleEverTriedLoading() ? ge->last_speed | (ge->last_age << 8) : 0xFF00; case 0x65: return GB(ge->status, GoodsEntry::GES_ACCEPTANCE, 1) << 3; case 0x69: { @@ -453,12 +453,12 @@ uint32_t Station::GetNewGRFVariable(const ResolverObject &object, uint8_t variab if (variable >= 0x8C && variable <= 0xEC) { const GoodsEntry *g = &this->goods[GB(variable - 0x8C, 3, 4)]; switch (GB(variable - 0x8C, 0, 3)) { - case 0: return g->cargo.TotalCount(); - case 1: return GB(std::min(g->cargo.TotalCount(), 4095u), 0, 4) | (GB(g->status, GoodsEntry::GES_ACCEPTANCE, 1) << 7); + case 0: return g->HasData() ? g->GetData().cargo.TotalCount() : 0; + case 1: return GB(g->HasData() ? std::min(g->GetData().cargo.TotalCount(), 4095u) : 0, 0, 4) | (GB(g->status, GoodsEntry::GES_ACCEPTANCE, 1) << 7); case 2: return g->time_since_pickup; case 3: return g->rating; - case 4: return g->cargo.GetFirstStation(); - case 5: return g->cargo.PeriodsInTransit(); + case 4: return g->HasData() ? g->GetData().cargo.GetFirstStation() : INVALID_STATION; + case 5: return g->HasData() ? g->GetData().cargo.PeriodsInTransit() : 0; case 6: return g->last_speed; case 7: return g->last_age; } @@ -520,13 +520,16 @@ uint32_t Waypoint::GetNewGRFVariable(const ResolverObject &, uint8_t variable, [ case SpriteGroupCargo::SG_DEFAULT: for (const GoodsEntry &ge : st->goods) { - cargo += ge.cargo.TotalCount(); + if (!ge.HasData()) continue; + cargo += ge.GetData().cargo.TotalCount(); } break; - default: - cargo = st->goods[this->station_scope.cargo_type].cargo.TotalCount(); + default: { + const GoodsEntry &ge = st->goods[this->station_scope.cargo_type]; + cargo = ge.HasData() ? ge.GetData().cargo.TotalCount() : 0; break; + } } if (HasBit(this->station_scope.statspec->flags, SSF_DIV_BY_STATION_SIZE)) cargo /= (st->train_station.w + st->train_station.h); @@ -584,7 +587,7 @@ StationResolverObject::StationResolverObject(const StationSpec *statspec, BaseSt /* Pick the first cargo that we have waiting */ for (const CargoSpec *cs : CargoSpec::Iterate()) { if (this->station_scope.statspec->grf_prop.spritegroup[cs->Index()] != nullptr && - st->goods[cs->Index()].cargo.TotalCount() > 0) { + st->goods[cs->Index()].HasData() && st->goods[cs->Index()].GetData().cargo.TotalCount() > 0) { ctype = cs->Index(); break; } diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 0faf5335f6..87e4e21240 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -1730,7 +1730,7 @@ bool AfterLoadGame() for (Station *st : Station::Iterate()) { for (GoodsEntry &ge : st->goods) { ge.last_speed = 0; - if (ge.cargo.AvailableCount() != 0) SetBit(ge.status, GoodsEntry::GES_RATING); + if (ge.HasData() && ge.GetData().cargo.AvailableCount() != 0) SetBit(ge.status, GoodsEntry::GES_RATING); } } } diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp index ba0988e0e8..c2766e8cfa 100644 --- a/src/saveload/cargopacket_sl.cpp +++ b/src/saveload/cargopacket_sl.cpp @@ -44,7 +44,8 @@ * station */ for (Station *st : Station::Iterate()) { for (GoodsEntry &ge : st->goods) { - const StationCargoPacketMap *packets = ge.cargo.Packets(); + if (!ge.HasData()) continue; + const StationCargoPacketMap *packets = ge.GetData().cargo.Packets(); for (StationCargoList::ConstIterator it(packets->begin()); it != packets->end(); it++) { CargoPacket *cp = *it; cp->source_xy = Station::IsValidID(cp->first_station) ? Station::Get(cp->first_station)->xy : st->xy; @@ -67,7 +68,10 @@ for (Vehicle *v : Vehicle::Iterate()) v->cargo.InvalidateCache(); for (Station *st : Station::Iterate()) { - for (GoodsEntry &ge : st->goods) ge.cargo.InvalidateCache(); + for (GoodsEntry &ge : st->goods) { + if (!ge.HasData()) continue; + ge.GetData().cargo.InvalidateCache(); + } } } @@ -80,7 +84,9 @@ /* Update the cargo-traveled in stations as if they arrived from the source tile. */ for (Station *st : Station::Iterate()) { for (GoodsEntry &ge : st->goods) { - for (auto it = ge.cargo.Packets()->begin(); it != ge.cargo.Packets()->end(); ++it) { + if (!ge.HasData()) continue; + StationCargoList &cargo_list = ge.GetData().cargo; + for (auto it = cargo_list.Packets()->begin(); it != cargo_list.Packets()->end(); ++it) { for (CargoPacket *cp : it->second) { if (cp->source_xy != INVALID_TILE && cp->source_xy != st->xy) { cp->travelled.x = TileX(cp->source_xy) - TileX(st->xy); diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index fa77ca7df6..2ebd7ed755 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -726,7 +726,7 @@ static bool LoadOldGood(LoadgameState *ls, int num) AssignBit(ge->status, GoodsEntry::GES_ACCEPTANCE, HasBit(_waiting_acceptance, 15)); AssignBit(ge->status, GoodsEntry::GES_RATING, _cargo_source != 0xFF); if (GB(_waiting_acceptance, 0, 12) != 0 && CargoPacket::CanAllocateItem()) { - ge->cargo.Append(new CargoPacket(GB(_waiting_acceptance, 0, 12), _cargo_periods, (_cargo_source == 0xFF) ? INVALID_STATION : _cargo_source, INVALID_TILE, 0), + ge->GetOrCreateData().cargo.Append(new CargoPacket(GB(_waiting_acceptance, 0, 12), _cargo_periods, (_cargo_source == 0xFF) ? INVALID_STATION : _cargo_source, INVALID_TILE, 0), INVALID_STATION); } diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 00c670c338..aaa713db3a 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -191,7 +191,7 @@ static OldPersistentStorage _old_st_persistent_storage; */ static void SwapPackets(GoodsEntry *ge) { - StationCargoPacketMap &ge_packets = const_cast(*ge->cargo.Packets()); + StationCargoPacketMap &ge_packets = const_cast(*ge->GetOrCreateData().cargo.Packets()); if (_packets.empty()) { std::map >::iterator it(ge_packets.find(INVALID_STATION)); @@ -240,8 +240,14 @@ public: void Save(GoodsEntry *ge) const override { - SlSetStructListLength(ge->cargo.Packets()->MapSize()); - for (StationCargoPacketMap::ConstMapIterator it(ge->cargo.Packets()->begin()); it != ge->cargo.Packets()->end(); ++it) { + if (!ge->HasData()) { + SlSetStructListLength(0); + return; + } + + const auto *packets = ge->GetData().cargo.Packets(); + SlSetStructListLength(packets->MapSize()); + for (StationCargoPacketMap::ConstMapIterator it(packets->begin()); it != packets->end(); ++it) { SlObject(const_cast(&(*it)), this->GetDescription()); } } @@ -249,18 +255,22 @@ public: void Load(GoodsEntry *ge) const override { size_t num_dests = IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? _old_num_dests : SlGetStructListLength(UINT32_MAX); + if (num_dests == 0) return; + GoodsEntry::GoodsEntryData &data = ge->GetOrCreateData(); StationCargoPair pair; for (uint j = 0; j < num_dests; ++j) { SlObject(&pair, this->GetLoadDescription()); - const_cast(*(ge->cargo.Packets()))[pair.first].swap(pair.second); + const_cast(*(data.cargo.Packets()))[pair.first].swap(pair.second); assert(pair.second.empty()); } } void FixPointers(GoodsEntry *ge) const override { - for (StationCargoPacketMap::ConstMapIterator it = ge->cargo.Packets()->begin(); it != ge->cargo.Packets()->end(); ++it) { + if (!ge->HasData()) return; + + for (StationCargoPacketMap::ConstMapIterator it = ge->GetData().cargo.Packets()->begin(); it != ge->GetData().cargo.Packets()->end(); ++it) { SlObject(const_cast(&(*it)), this->GetDescription()); } } @@ -278,13 +288,18 @@ public: void Save(GoodsEntry *ge) const override { + if (!ge->HasData()) { + SlSetStructListLength(0); + return; + } + size_t num_flows = 0; - for (const auto &it : ge->flows) { + for (const auto &it : ge->GetData().flows) { num_flows += it.second.GetShares()->size(); } SlSetStructListLength(num_flows); - for (const auto &outer_it : ge->flows) { + for (const auto &outer_it : ge->GetData().flows) { const FlowStat::SharesMap *shares = outer_it.second.GetShares(); uint32_t sum_shares = 0; FlowSaveLoad flow; @@ -303,14 +318,16 @@ public: void Load(GoodsEntry *ge) const override { size_t num_flows = IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? _old_num_flows : SlGetStructListLength(UINT32_MAX); + if (num_flows == 0) return; + GoodsEntry::GoodsEntryData &data = ge->GetOrCreateData(); FlowSaveLoad flow; FlowStat *fs = nullptr; StationID prev_source = INVALID_STATION; for (uint32_t j = 0; j < num_flows; ++j) { SlObject(&flow, this->GetLoadDescription()); if (fs == nullptr || prev_source != flow.source) { - fs = &(ge->flows.emplace(flow.source, FlowStat(flow.via, flow.share, flow.restricted))).first->second; + fs = &(data.flows.emplace(flow.source, FlowStat(flow.via, flow.share, flow.restricted))).first->second; } else { fs->AppendShare(flow.via, flow.share, flow.restricted); } @@ -321,6 +338,8 @@ public: class SlStationGoods : public DefaultSaveLoadHandler { public: + static inline uint cargo_reserved_count; + inline static const SaveLoad description[] = { SLEG_CONDVAR("waiting_acceptance", _waiting_acceptance, SLE_UINT16, SL_MIN_VERSION, SLV_68), SLE_CONDVAR(GoodsEntry, status, SLE_UINT8, SLV_68, SL_MAX_VERSION), @@ -337,7 +356,7 @@ public: SLE_CONDVAR(GoodsEntry, amount_fract, SLE_UINT8, SLV_150, SL_MAX_VERSION), SLEG_CONDREFLIST("packets", _packets, REF_CARGO_PACKET, SLV_68, SLV_183), SLEG_CONDVAR("old_num_dests", _old_num_dests, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), - SLE_CONDVAR(GoodsEntry, cargo.reserved_count, SLE_UINT, SLV_181, SL_MAX_VERSION), + SLEG_CONDVAR("cargo.reserved_count", SlStationGoods::cargo_reserved_count, SLE_UINT, SLV_181, SL_MAX_VERSION), SLE_CONDVAR(GoodsEntry, link_graph, SLE_UINT16, SLV_183, SL_MAX_VERSION), SLE_CONDVAR(GoodsEntry, node, SLE_UINT16, SLV_183, SL_MAX_VERSION), SLEG_CONDVAR("old_num_flows", _old_num_flows, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), @@ -368,6 +387,7 @@ public: SlSetStructListLength(NUM_CARGO); for (GoodsEntry &ge : st->goods) { + SlStationGoods::cargo_reserved_count = ge.HasData() ? ge.GetData().cargo.reserved_count : 0; SlObject(&ge, this->GetDescription()); } } @@ -388,6 +408,9 @@ public: for (auto it = std::begin(st->goods); it != end; ++it) { GoodsEntry &ge = *it; SlObject(&ge, this->GetLoadDescription()); + if (!IsSavegameVersionBefore(SLV_181) && SlStationGoods::cargo_reserved_count != 0) { + ge.GetOrCreateData().cargo.reserved_count = SlStationGoods::cargo_reserved_count; + } if (IsSavegameVersionBefore(SLV_183)) { SwapPackets(&ge); } @@ -405,7 +428,7 @@ public: /* Don't construct the packet with station here, because that'll fail with old savegames */ CargoPacket *cp = new CargoPacket(GB(_waiting_acceptance, 0, 12), _cargo_periods, source, TileIndex{_cargo_source_xy}, _cargo_feeder_share); - ge.cargo.Append(cp, INVALID_STATION); + ge.GetOrCreateData().cargo.Append(cp, INVALID_STATION); SetBit(ge.status, GoodsEntry::GES_RATING); } } diff --git a/src/script/api/script_station.cpp b/src/script/api/script_station.cpp index ee3ff0d5d6..26a44f6b9b 100644 --- a/src/script/api/script_station.cpp +++ b/src/script/api/script_station.cpp @@ -59,7 +59,10 @@ template return -1; } - const StationCargoList &cargo_list = ::Station::Get(station_id)->goods[cargo_id].cargo; + const ::GoodsEntry &goods = ::Station::Get(station_id)->goods[cargo_id]; + if (!goods.HasData()) return 0; + + const StationCargoList &cargo_list = goods.GetData().cargo; if (!Tfrom && !Tvia) return cargo_list.TotalCount(); uint16_t cargo_count = 0; @@ -107,7 +110,10 @@ template return -1; } - const FlowStatMap &flows = ::Station::Get(station_id)->goods[cargo_id].flows; + const ::GoodsEntry &goods = ::Station::Get(station_id)->goods[cargo_id]; + if (!goods.HasData()) return 0; + + const FlowStatMap &flows = goods.GetData().flows; if (Tfrom) { return Tvia ? flows.GetFlowFromVia(from_station_id, via_station_id) : flows.GetFlowFrom(from_station_id); diff --git a/src/script/api/script_stationlist.cpp b/src/script/api/script_stationlist.cpp index 04bb8c07b1..7def5a2928 100644 --- a/src/script/api/script_stationlist.cpp +++ b/src/script/api/script_stationlist.cpp @@ -179,9 +179,10 @@ void ScriptStationList_CargoWaiting::Add(StationID station_id, CargoID cargo, St { CargoCollector collector(this, station_id, cargo, other_station); if (collector.GE() == nullptr) return; + if (!collector.GE()->HasData()) return; - StationCargoList::ConstIterator iter = collector.GE()->cargo.Packets()->begin(); - StationCargoList::ConstIterator end = collector.GE()->cargo.Packets()->end(); + StationCargoList::ConstIterator iter = collector.GE()->GetData().cargo.Packets()->begin(); + StationCargoList::ConstIterator end = collector.GE()->GetData().cargo.Packets()->end(); for (; iter != end; ++iter) { collector.Update((*iter)->GetFirstStation(), iter.GetKey(), (*iter)->Count()); } @@ -193,9 +194,10 @@ void ScriptStationList_CargoPlanned::Add(StationID station_id, CargoID cargo, St { CargoCollector collector(this, station_id, cargo, other_station); if (collector.GE() == nullptr) return; + if (!collector.GE()->HasData()) return; - FlowStatMap::const_iterator iter = collector.GE()->flows.begin(); - FlowStatMap::const_iterator end = collector.GE()->flows.end(); + FlowStatMap::const_iterator iter = collector.GE()->GetData().flows.begin(); + FlowStatMap::const_iterator end = collector.GE()->GetData().flows.end(); for (; iter != end; ++iter) { const FlowStat::SharesMap *shares = iter->second.GetShares(); uint prev = 0; @@ -218,9 +220,10 @@ ScriptStationList_CargoWaitingViaByFrom::ScriptStationList_CargoWaitingViaByFrom { CargoCollector collector(this, station_id, cargo, via); if (collector.GE() == nullptr) return; + if (!collector.GE()->HasData()) return; std::pair range = - collector.GE()->cargo.Packets()->equal_range(via); + collector.GE()->GetData().cargo.Packets()->equal_range(via); for (StationCargoList::ConstIterator iter = range.first; iter != range.second; ++iter) { collector.Update((*iter)->GetFirstStation(), iter.GetKey(), (*iter)->Count()); } @@ -264,9 +267,10 @@ ScriptStationList_CargoPlannedFromByVia::ScriptStationList_CargoPlannedFromByVia { CargoCollector collector(this, station_id, cargo, from); if (collector.GE() == nullptr) return; + if (!collector.GE()->HasData()) return; - FlowStatMap::const_iterator iter = collector.GE()->flows.find(from); - if (iter == collector.GE()->flows.end()) return; + FlowStatMap::const_iterator iter = collector.GE()->GetData().flows.find(from); + if (iter == collector.GE()->GetData().flows.end()) return; const FlowStat::SharesMap *shares = iter->second.GetShares(); uint prev = 0; for (FlowStat::SharesMap::const_iterator flow_iter = shares->begin(); diff --git a/src/station.cpp b/src/station.cpp index 802ccd236b..d822b10324 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -84,7 +84,8 @@ Station::~Station() { if (CleaningPool()) { for (GoodsEntry &ge : this->goods) { - ge.cargo.OnCleanPool(); + if (!ge.HasData()) continue; + ge.GetData().cargo.OnCleanPool(); } return; } @@ -104,9 +105,10 @@ Station::~Station() for (NodeID node = 0; node < lg->Size(); ++node) { Station *st = Station::Get((*lg)[node].station); - st->goods[c].flows.erase(this->index); + if (!st->goods[c].HasData()) continue; + st->goods[c].GetData().flows.erase(this->index); if ((*lg)[node].HasEdgeTo(this->goods[c].node) && (*lg)[node][this->goods[c].node].LastUpdate() != EconomyTime::INVALID_DATE) { - st->goods[c].flows.DeleteFlows(this->index); + st->goods[c].GetData().flows.DeleteFlows(this->index); RerouteCargo(st, c, this->index, st->index); } } @@ -149,7 +151,8 @@ Station::~Station() DeleteStationNews(this->index); for (GoodsEntry &ge : this->goods) { - ge.cargo.Truncate(); + if (!ge.HasData()) continue; + ge.GetData().cargo.Truncate(); } CargoPacket::InvalidateAllFrom(this->index); diff --git a/src/station_base.h b/src/station_base.h index 6dda3ca843..474a20a5e4 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -207,8 +207,15 @@ struct GoodsEntry { GES_ACCEPTED_BIGTICK, }; - StationCargoList cargo{}; ///< The cargo packets of cargo waiting in this station - FlowStatMap flows{}; ///< Planned flows through this station. + struct GoodsEntryData { + StationCargoList cargo{}; ///< The cargo packets of cargo waiting in this station + FlowStatMap flows{}; ///< Planned flows through this station. + + bool IsEmpty() const + { + return this->cargo.TotalCount() == 0 && this->flows.empty(); + } + }; uint max_waiting_cargo = 0; ///< Max cargo from this station waiting at any station. NodeID node = INVALID_NODE; ///< ID of node in link graph referring to this goods entry. @@ -267,8 +274,10 @@ struct GoodsEntry { */ inline StationID GetVia(StationID source) const { - FlowStatMap::const_iterator flow_it(this->flows.find(source)); - return flow_it != this->flows.end() ? flow_it->second.GetVia() : INVALID_STATION; + if (!this->HasData()) return INVALID_STATION; + + FlowStatMap::const_iterator flow_it(this->GetData().flows.find(source)); + return flow_it != this->GetData().flows.end() ? flow_it->second.GetVia() : INVALID_STATION; } /** @@ -281,9 +290,57 @@ struct GoodsEntry { */ inline StationID GetVia(StationID source, StationID excluded, StationID excluded2 = INVALID_STATION) const { - FlowStatMap::const_iterator flow_it(this->flows.find(source)); - return flow_it != this->flows.end() ? flow_it->second.GetVia(excluded, excluded2) : INVALID_STATION; + if (!this->HasData()) return INVALID_STATION; + + FlowStatMap::const_iterator flow_it(this->GetData().flows.find(source)); + return flow_it != this->GetData().flows.end() ? flow_it->second.GetVia(excluded, excluded2) : INVALID_STATION; } + + /** + * Test if this goods entry has optional cargo packet/flow data. + * @returns true iff optional data is present. + */ + debug_inline bool HasData() const { return this->data != nullptr; } + + /** + * Clear optional cargo packet/flow data. + */ + void ClearData() { this->data.reset(); } + + /** + * Get optional cargo packet/flow data. + * @pre HasData() + * @returns cargo packet/flow data. + */ + debug_inline const GoodsEntryData &GetData() const + { + assert(this->HasData()); + return *this->data; + } + + /** + * Get non-const optional cargo packet/flow data. + * @pre HasData() + * @returns non-const cargo packet/flow data. + */ + debug_inline GoodsEntryData &GetData() + { + assert(this->HasData()); + return *this->data; + } + + /** + * Get optional cargo packet/flow data. The data is create if it is not already present. + * @returns cargo packet/flow data. + */ + inline GoodsEntryData &GetOrCreateData() + { + if (!this->HasData()) this->data = std::make_unique(); + return *this->data; + } + +private: + std::unique_ptr data = nullptr; ///< Optional cargo packet and flow data. }; /** All airport-related information. Only valid if tile != INVALID_TILE. */ diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 1fb0b84664..ff639c4fb5 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -518,7 +518,7 @@ CargoTypes GetEmptyMask(const Station *st) CargoTypes mask = 0; for (auto it = std::begin(st->goods); it != std::end(st->goods); ++it) { - if (it->cargo.TotalCount() == 0) SetBit(mask, std::distance(std::begin(st->goods), it)); + if (!it->HasData() || it->GetData().cargo.TotalCount() == 0) SetBit(mask, std::distance(std::begin(st->goods), it)); } return mask; } @@ -3819,8 +3819,10 @@ static void TruncateCargo(const CargoSpec *cs, GoodsEntry *ge, uint amount = UIN /* If truncating also punish the source stations' ratings to * decrease the flow of incoming cargo. */ + if (!ge->HasData()) return; + StationCargoAmountMap waiting_per_source; - ge->cargo.Truncate(amount, &waiting_per_source); + ge->GetData().cargo.Truncate(amount, &waiting_per_source); for (StationCargoAmountMap::iterator i(waiting_per_source.begin()); i != waiting_per_source.end(); ++i) { Station *source_station = Station::GetIfValid(i->first); if (source_station == nullptr) continue; @@ -3859,12 +3861,12 @@ static void UpdateStationRating(Station *st) bool skip = false; int rating = 0; - uint waiting = ge->cargo.AvailableCount(); + uint waiting = ge->HasData() ? ge->GetData().cargo.AvailableCount() : 0; /* num_dests is at least 1 if there is any cargo as * INVALID_STATION is also a destination. */ - uint num_dests = (uint)ge->cargo.Packets()->MapSize(); + uint num_dests = ge->HasData() ? static_cast(ge->GetData().cargo.Packets()->MapSize()) : 0; /* Average amount of cargo per next hop, but prefer solitary stations * with only one or two next hops. They are allowed to have more @@ -3967,12 +3969,12 @@ static void UpdateStationRating(Station *st) /* We can't truncate cargo that's already reserved for loading. * Thus StoredCount() here. */ - if (waiting_changed && waiting < ge->cargo.AvailableCount()) { + if (waiting_changed && waiting < (ge->HasData() ? ge->GetData().cargo.AvailableCount() : 0)) { /* Feed back the exact own waiting cargo at this station for the * next rating calculation. */ ge->max_waiting_cargo = 0; - TruncateCargo(cs, ge, ge->cargo.AvailableCount() - waiting); + TruncateCargo(cs, ge, ge->GetData().cargo.AvailableCount() - waiting); } else { /* If the average number per next hop is low, be more forgiving. */ ge->max_waiting_cargo = waiting_avg; @@ -4002,7 +4004,7 @@ void RerouteCargo(Station *st, CargoID c, StationID avoid, StationID avoid2) GoodsEntry &ge = st->goods[c]; /* Reroute cargo in station. */ - ge.cargo.Reroute(UINT_MAX, &ge.cargo, avoid, avoid2, &ge); + if (ge.HasData()) ge.GetData().cargo.Reroute(UINT_MAX, &ge.GetData().cargo, avoid, avoid2, &ge); /* Reroute cargo staged to be transferred. */ for (Vehicle *v : st->loading_vehicles) { @@ -4085,12 +4087,12 @@ void DeleteStaleLinks(Station *from) if (!updated) { /* If it's still considered dead remove it. */ to_remove.emplace_back(to->goods[c].node); - ge.flows.DeleteFlows(to->index); + if (ge.HasData()) ge.GetData().flows.DeleteFlows(to->index); RerouteCargo(from, c, to->index, from->index); } } else if (edge.last_unrestricted_update != EconomyTime::INVALID_DATE && TimerGameEconomy::date - edge.last_unrestricted_update > timeout) { edge.Restrict(); - ge.flows.RestrictFlows(to->index); + if (ge.HasData()) ge.GetData().flows.RestrictFlows(to->index); RerouteCargo(from, c, to->index, from->index); } else if (edge.last_restricted_update != EconomyTime::INVALID_DATE && TimerGameEconomy::date - edge.last_restricted_update > timeout) { edge.Release(); @@ -4261,7 +4263,7 @@ static uint UpdateStationWaiting(Station *st, CargoID type, uint amount, SourceT if (amount == 0) return 0; StationID next = ge.GetVia(st->index); - ge.cargo.Append(new CargoPacket(st->index, amount, source_type, source_id), next); + ge.GetOrCreateData().cargo.Append(new CargoPacket(st->index, amount, source_type, source_id), next); LinkGraph *lg = nullptr; if (ge.link_graph == INVALID_LINK_GRAPH) { if (LinkGraph::CanAllocateItem()) { diff --git a/src/station_gui.cpp b/src/station_gui.cpp index d2b7e9b546..c2d7bbcbd8 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -362,7 +362,7 @@ protected: int diff = 0; for (CargoID j : SetCargoBitIterator(cargo_filter)) { - diff += a->goods[j].cargo.TotalCount() - b->goods[j].cargo.TotalCount(); + diff += (a->goods[j].HasData() ? a->goods[j].GetData().cargo.TotalCount() : 0) - (b->goods[j].HasData() ? b->goods[j].GetData().cargo.TotalCount() : 0); } return diff < 0; @@ -374,7 +374,7 @@ protected: int diff = 0; for (CargoID j : SetCargoBitIterator(cargo_filter)) { - diff += a->goods[j].cargo.AvailableCount() - b->goods[j].cargo.AvailableCount(); + diff += (a->goods[j].HasData() ? a->goods[j].GetData().cargo.AvailableCount() : 0) - (b->goods[j].HasData() ? b->goods[j].GetData().cargo.AvailableCount() : 0); } return diff < 0; @@ -536,7 +536,7 @@ public: x -= rating_width + rating_spacing; if (x < tr.left) break; } - StationsWndShowStationRating(x, x + rating_width, tr.top, cid, st->goods[cid].cargo.TotalCount(), st->goods[cid].rating); + StationsWndShowStationRating(x, x + rating_width, tr.top, cid, st->goods[cid].HasData() ? st->goods[cid].GetData().cargo.TotalCount() : 0, st->goods[cid].rating); if (!rtl) { x += rating_width + rating_spacing; if (x > tr.right) break; @@ -1534,7 +1534,9 @@ struct StationViewWindow : public Window { CargoDataEntry *cargo_entry = cached_destinations.InsertOrRetrieve(i); cargo_entry->Clear(); - for (const auto &it : st->goods[i].flows) { + if (!st->goods[i].HasData()) return; + + for (const auto &it : st->goods[i].GetData().flows) { StationID from = it.first; CargoDataEntry *source_entry = cargo_entry->InsertOrRetrieve(from); uint32_t prev_count = 0; @@ -1563,8 +1565,11 @@ struct StationViewWindow : public Window { void EstimateDestinations(CargoID cargo, StationID source, StationID next, uint count, CargoDataEntry *dest) { if (Station::IsValidID(next) && Station::IsValidID(source)) { + GoodsEntry &ge = Station::Get(next)->goods[cargo]; + if (!ge.HasData()) return; + CargoDataEntry tmp; - const FlowStatMap &flowmap = Station::Get(next)->goods[cargo].flows; + const FlowStatMap &flowmap = ge.GetData().flows; FlowStatMap::const_iterator map_it = flowmap.find(source); if (map_it != flowmap.end()) { const FlowStat::SharesMap *shares = map_it->second.GetShares(); @@ -1691,10 +1696,13 @@ struct StationViewWindow : public Window { this->RecalcDestinations(i); } + const GoodsEntry &ge = st->goods[i]; + if (!ge.HasData()) continue; + if (this->current_mode == MODE_WAITING) { - this->BuildCargoList(i, st->goods[i].cargo, cargo); + this->BuildCargoList(i, ge.GetData().cargo, cargo); } else { - this->BuildFlowList(i, st->goods[i].flows, cargo); + this->BuildFlowList(i, ge.GetData().flows, cargo); } } } @@ -1856,9 +1864,12 @@ struct StationViewWindow : public Window { sym = "+"; } else { /* Only draw '+' if there is something to be shown. */ - const StationCargoList &list = Station::Get(this->window_number)->goods[cargo].cargo; - if (grouping == GR_CARGO && (list.ReservedCount() > 0 || cd->HasTransfers())) { - sym = "+"; + const GoodsEntry &ge = Station::Get(this->window_number)->goods[cargo]; + if (ge.HasData()) { + const StationCargoList &cargo_list = ge.GetData().cargo; + if (grouping == GR_CARGO && (cargo_list.ReservedCount() > 0 || cd->HasTransfers())) { + sym = "+"; + } } } if (sym != nullptr) DrawString(shrink.left, shrink.right, y, sym, TC_YELLOW); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 06876853d9..10443db5bc 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2337,7 +2337,7 @@ void Vehicle::CancelReservation(StationID next, Station *st) VehicleCargoList &cargo = v->cargo; if (cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0) { Debug(misc, 1, "cancelling cargo reservation"); - cargo.Return(UINT_MAX, &st->goods[v->cargo_type].cargo, next, v->tile); + cargo.Return(UINT_MAX, &st->goods[v->cargo_type].GetOrCreateData().cargo, next, v->tile); } cargo.KeepAll(); }