# HG changeset patch # User Peter Nelson # Date 2024-02-25 12:36:13 # Node ID c7d0936f4cf0e778b7456281a406653b26f51215 # Parent dd35d3d734acfbd792f7d6a1b7a979c11623a6c5 Change: Use bitmap for free unit ID generation. (#12165) This improves performance of finding the next free unit number for a vehicle. Based loosely on pool's used slot bitmap. diff --git a/src/company_base.h b/src/company_base.h --- a/src/company_base.h +++ b/src/company_base.h @@ -49,6 +49,19 @@ struct CompanyInfrastructure { uint32_t GetTramTotal() const; }; +class FreeUnitIDGenerator { +public: + UnitID NextID() const; + UnitID UseID(UnitID index); + void ReleaseID(UnitID index); + +private: + using BitmapStorage = size_t; + static constexpr size_t BITMAP_SIZE = std::numeric_limits::digits; + + std::vector used_bitmap; +}; + typedef Pool CompanyPool; extern CompanyPool _company_pool; @@ -129,6 +142,8 @@ struct Company : CompanyProperties, Comp CompanyInfrastructure infrastructure; ///< NOSAVE: Counts of company owned infrastructure. + FreeUnitIDGenerator freeunits[VEH_COMPANY_END]; + Money GetMaxLoan() const; /** diff --git a/src/economy.cpp b/src/economy.cpp --- a/src/economy.cpp +++ b/src/economy.cpp @@ -430,17 +430,13 @@ void ChangeOwnershipOfCompanyItems(Owner } { - FreeUnitIDGenerator unitidgen[] = { - FreeUnitIDGenerator(VEH_TRAIN, new_owner), FreeUnitIDGenerator(VEH_ROAD, new_owner), - FreeUnitIDGenerator(VEH_SHIP, new_owner), FreeUnitIDGenerator(VEH_AIRCRAFT, new_owner) - }; + Company *new_company = new_owner == INVALID_OWNER ? nullptr : Company::Get(new_owner); /* Override company settings to new company defaults in case we need to convert them. * This is required as the CmdChangeServiceInt doesn't copy the supplied value when it is non-custom */ if (new_owner != INVALID_OWNER) { Company *old_company = Company::Get(old_owner); - Company *new_company = Company::Get(new_owner); old_company->settings.vehicle.servint_aircraft = new_company->settings.vehicle.servint_aircraft; old_company->settings.vehicle.servint_trains = new_company->settings.vehicle.servint_trains; @@ -457,8 +453,6 @@ void ChangeOwnershipOfCompanyItems(Owner * This prevents invalid values on mismatching company defaults being accepted. */ if (!v->ServiceIntervalIsCustom()) { - Company *new_company = Company::Get(new_owner); - /* Technically, passing the interval is not needed as the command will query the default value itself. * However, do not rely on that behaviour. */ @@ -477,7 +471,8 @@ void ChangeOwnershipOfCompanyItems(Owner } if (v->IsPrimaryVehicle()) { GroupStatistics::CountVehicle(v, 1); - v->unitnumber = unitidgen[v->type].NextID(); + auto &unitidgen = new_company->freeunits[v->type]; + v->unitnumber = unitidgen.UseID(unitidgen.NextID()); } /* Invalidate the vehicle's cargo payment "owner cache". */ diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -517,6 +517,10 @@ void AfterLoadVehicles(bool part_of_load default: break; } + if (part_of_load && v->unitnumber != 0) { + Company::Get(v->owner)->freeunits[v->type].UseID(v->unitnumber); + } + v->UpdateDeltaXY(); v->coord.left = INVALID_COORD; v->sprite_cache.old_coord.left = INVALID_COORD; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1178,7 +1178,7 @@ static void NormaliseTrainHead(Train *he /* If we don't have a unit number yet, set one. */ if (head->unitnumber != 0) return; - head->unitnumber = GetFreeUnitNumber(VEH_TRAIN); + head->unitnumber = Company::Get(head->owner)->freeunits[head->type].UseID(GetFreeUnitNumber(VEH_TRAIN)); } /** @@ -1336,6 +1336,7 @@ CommandCost CmdMoveRailVehicle(DoCommand } /* Remove stuff not valid anymore for non-front engines. */ DeleteVehicleOrders(src); + Company::Get(src->owner)->freeunits[src->type].ReleaseID(src->unitnumber); src->unitnumber = 0; src->name.clear(); } diff --git a/src/vehicle.cpp b/src/vehicle.cpp --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -846,6 +846,8 @@ void Vehicle::PreDestructor() DeleteGroupHighlightOfVehicle(this); } + Company::Get(this->owner)->freeunits[this->type].ReleaseID(this->unitnumber); + if (this->type == VEH_AIRCRAFT && this->IsPrimaryVehicle()) { Aircraft *a = Aircraft::From(this); Station *st = GetTargetAirportIfValid(a); @@ -1816,44 +1818,50 @@ VehicleEnterTileStatus VehicleEnterTile( } /** - * Initializes the structure. Vehicle unit numbers are supposed not to change after - * struct initialization, except after each call to this->NextID() the returned value - * is assigned to a vehicle. - * @param type type of vehicle - * @param owner owner of vehicles + * Find first unused unit number. + * This does not mark the unit number as used. + * @returns First unused unit number. */ -FreeUnitIDGenerator::FreeUnitIDGenerator(VehicleType type, CompanyID owner) : cache(nullptr), maxid(0), curid(0) +UnitID FreeUnitIDGenerator::NextID() const { - /* Find maximum */ - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->type == type && v->owner == owner) { - this->maxid = std::max(this->maxid, v->unitnumber); - } + for (auto it = std::begin(this->used_bitmap); it != std::end(this->used_bitmap); ++it) { + BitmapStorage available = ~(*it); + if (available == 0) continue; + return static_cast(std::distance(std::begin(this->used_bitmap), it) * BITMAP_SIZE + FindFirstBit(available) + 1); } - - if (this->maxid == 0) return; - - /* Reserving 'maxid + 2' because we need: - * - space for the last item (with v->unitnumber == maxid) - * - one free slot working as loop terminator in FreeUnitIDGenerator::NextID() */ - this->cache = CallocT(this->maxid + 2); - - /* Fill the cache */ - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->type == type && v->owner == owner) { - this->cache[v->unitnumber] = true; - } - } + return static_cast(this->used_bitmap.size() * BITMAP_SIZE + 1); } -/** Returns next free UnitID. Supposes the last returned value was assigned to a vehicle. */ -UnitID FreeUnitIDGenerator::NextID() +/** + * Use a unit number. If the unit number is not valid it is ignored. + * @param index Unit number to use. + * @returns Unit number used. + */ +UnitID FreeUnitIDGenerator::UseID(UnitID index) { - if (this->maxid <= this->curid) return ++this->curid; - - while (this->cache[++this->curid]) { } // it will stop, we reserved more space than needed - - return this->curid; + if (index == 0 || index == UINT16_MAX) return index; + + index--; + + size_t slot = index / BITMAP_SIZE; + if (slot >= this->used_bitmap.size()) this->used_bitmap.resize(slot + 1); + SetBit(this->used_bitmap[index / BITMAP_SIZE], index % BITMAP_SIZE); + + return index + 1; +} + +/** + * Release a unit number. If the unit number is not valid it is ignored. + * @param index Unit number to release. + */ +void FreeUnitIDGenerator::ReleaseID(UnitID index) +{ + if (index == 0 || index == UINT16_MAX) return; + + index--; + + assert(index / BITMAP_SIZE < this->used_bitmap.size()); + ClrBit(this->used_bitmap[index / BITMAP_SIZE], index % BITMAP_SIZE); } /** @@ -1876,9 +1884,7 @@ UnitID GetFreeUnitNumber(VehicleType typ const Company *c = Company::Get(_current_company); if (c->group_all[type].num_vehicle >= max_veh) return UINT16_MAX; // Currently already at the limit, no room to make a new one. - FreeUnitIDGenerator gen(type, _current_company); - - return gen.NextID(); + return c->freeunits[type].NextID(); } diff --git a/src/vehicle_base.h b/src/vehicle_base.h --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -755,7 +755,7 @@ public: * and that shall not be resetted for the new vehicle. * @param src The old vehicle */ - inline void CopyVehicleConfigAndStatistics(const Vehicle *src) + inline void CopyVehicleConfigAndStatistics(Vehicle *src) { this->CopyConsistPropertiesFrom(src); @@ -766,6 +766,8 @@ public: this->profit_this_year = src->profit_this_year; this->profit_last_year = src->profit_last_year; + + src->unitnumber = 0; } @@ -1270,19 +1272,6 @@ struct SpecializedVehicle : public Vehic static Pool::IterateWrapper Iterate(size_t from = 0) { return Pool::IterateWrapper(from); } }; -/** Generates sequence of free UnitID numbers */ -struct FreeUnitIDGenerator { - bool *cache; ///< array of occupied unit id numbers - UnitID maxid; ///< maximum ID at the moment of constructor call - UnitID curid; ///< last ID returned; 0 if none - - FreeUnitIDGenerator(VehicleType type, CompanyID owner); - UnitID NextID(); - - /** Releases allocated memory */ - ~FreeUnitIDGenerator() { free(this->cache); } -}; - /** Sentinel for an invalid coordinate. */ static const int32_t INVALID_COORD = 0x7fffffff; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -194,6 +194,8 @@ std::tupleowner)->freeunits[v->type].UseID(v->unitnumber); }