Codechange: Split GoodsEntry cargo and flows data to unique_ptr. (#13058)

This allows cargo packets and cargo flow data to be empty if not in use, which is the case for the majority of station goods entries, and data is allocated when needed.

This reduces the initial size of a Station from 9192 bytes to 2024 bytes (on 64 bit platforms), although an allocation of 120 bytes is made for each active cargo type at a station.

Based on similar changes in JGRPP.
This commit is contained in:
Peter Nelson 2025-01-02 20:48:23 +00:00 committed by GitHub
parent 810dc23215
commit 810e442203
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 216 additions and 93 deletions

View File

@ -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);

View File

@ -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 */

View File

@ -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);

View File

@ -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<CMD_REFIT_VEHICLE>::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);

View File

@ -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]);

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -191,7 +191,7 @@ static OldPersistentStorage _old_st_persistent_storage;
*/
static void SwapPackets(GoodsEntry *ge)
{
StationCargoPacketMap &ge_packets = const_cast<StationCargoPacketMap &>(*ge->cargo.Packets());
StationCargoPacketMap &ge_packets = const_cast<StationCargoPacketMap &>(*ge->GetOrCreateData().cargo.Packets());
if (_packets.empty()) {
std::map<StationID, std::list<CargoPacket *> >::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<StationCargoPacketMap::value_type *>(&(*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<StationCargoPacketMap &>(*(ge->cargo.Packets()))[pair.first].swap(pair.second);
const_cast<StationCargoPacketMap &>(*(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<StationCargoPair *>(&(*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<SlStationGoods, BaseStation> {
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);
}
}

View File

@ -59,7 +59,10 @@ template<bool Tfrom, bool Tvia>
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<bool Tfrom, bool Tvia>
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);

View File

@ -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<Tselector>((*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<StationCargoList::ConstIterator, StationCargoList::ConstIterator> 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<CS_VIA_BY_FROM>((*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();

View File

@ -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);

View File

@ -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<GoodsEntryData>();
return *this->data;
}
private:
std::unique_ptr<GoodsEntryData> data = nullptr; ///< Optional cargo packet and flow data.
};
/** All airport-related information. Only valid if tile != INVALID_TILE. */

View File

@ -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<uint>(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()) {

View File

@ -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);

View File

@ -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();
}