diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 5536d0387e..3f75d131a0 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -380,6 +380,8 @@ CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine * u->SetNext(w); w->UpdatePosition(); } + + InvalidateVehicleTickCaches(); } return CommandCost(); @@ -2131,8 +2133,6 @@ bool Aircraft::Tick() { if (!this->IsNormalAircraft()) return true; - PerformanceAccumulator framerate(PFE_GL_AIRCRAFT); - this->tick_counter++; if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp index 154aca3d9a..7a4d628308 100644 --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -568,6 +568,7 @@ static bool DisasterTick_Big_Ufo(DisasterVehicle *v) DisasterVehicle *u = new DisasterVehicle(-6 * (int)TILE_SIZE, v->y_pos, DIR_SW, ST_BIG_UFO_DESTROYER, v->index); DisasterVehicle *w = new DisasterVehicle(-6 * (int)TILE_SIZE, v->y_pos, DIR_SW, ST_BIG_UFO_DESTROYER_SHADOW); u->SetNext(w); + InvalidateVehicleTickCaches(); } else if (v->current_order.GetDestination() == 0) { int x = TileX(v->dest_tile) * TILE_SIZE; int y = TileY(v->dest_tile) * TILE_SIZE; @@ -727,6 +728,8 @@ static void Disaster_Zeppeliner_Init() /* Allocate shadow */ DisasterVehicle *u = new DisasterVehicle(x, 0, DIR_SE, ST_ZEPPELINER_SHADOW); v->SetNext(u); + + InvalidateVehicleTickCaches(); } @@ -745,6 +748,8 @@ static void Disaster_Small_Ufo_Init() /* Allocate shadow */ DisasterVehicle *u = new DisasterVehicle(x, 0, DIR_SE, ST_SMALL_UFO_SHADOW); v->SetNext(u); + + InvalidateVehicleTickCaches(); } @@ -771,6 +776,8 @@ static void Disaster_Airplane_Init() DisasterVehicle *v = new DisasterVehicle(x, y, DIR_NE, ST_AIRPLANE); DisasterVehicle *u = new DisasterVehicle(x, y, DIR_NE, ST_AIRPLANE_SHADOW); v->SetNext(u); + + InvalidateVehicleTickCaches(); } @@ -799,6 +806,8 @@ static void Disaster_Helicopter_Init() DisasterVehicle *w = new DisasterVehicle(x, y, DIR_SW, ST_HELICOPTER_ROTORS); u->SetNext(w); + + InvalidateVehicleTickCaches(); } @@ -817,6 +826,8 @@ static void Disaster_Big_Ufo_Init() /* Allocate shadow */ DisasterVehicle *u = new DisasterVehicle(x, y, DIR_NW, ST_BIG_UFO_SHADOW); v->SetNext(u); + + InvalidateVehicleTickCaches(); } @@ -840,6 +851,8 @@ static void Disaster_Submarine_Init(DisasterSubType subtype) if (!IsWaterTile(TileVirtXY(x, y))) return; new DisasterVehicle(x, y, dir, subtype); + + InvalidateVehicleTickCaches(); } /* Curious submarine #1, just floats around */ diff --git a/src/effectvehicle.cpp b/src/effectvehicle.cpp index 3aff4cacdc..080dc074b7 100644 --- a/src/effectvehicle.cpp +++ b/src/effectvehicle.cpp @@ -19,6 +19,8 @@ #include "effectvehicle_func.h" #include "effectvehicle_base.h" +#include + #include "safeguards.h" @@ -632,6 +634,8 @@ EffectVehicle *CreateEffectVehicle(int x, int y, int z, EffectVehicleType type) v->UpdatePositionAndViewport(); + v->AddEffectVehicleToTickCache(); + return v; } @@ -686,3 +690,23 @@ TransparencyOption EffectVehicle::GetTransparencyOption() const { return _effect_transparency_options[this->subtype]; } + +extern std::vector _tick_other_veh_cache; +extern bool _tick_caches_valid; + +void EffectVehicle::AddEffectVehicleToTickCache() +{ + if (!_tick_caches_valid) return; + _tick_other_veh_cache.erase(std::remove(_tick_other_veh_cache.begin(), _tick_other_veh_cache.end(), nullptr), _tick_other_veh_cache.end()); + _tick_other_veh_cache.insert(std::upper_bound(_tick_other_veh_cache.begin(), _tick_other_veh_cache.end(), this, [&](const Vehicle *a, const Vehicle *b) { + return a->index < b->index; + }), this); +} + +void EffectVehicle::RemoveEffectVehicleFromTickCache() +{ + if (!_tick_caches_valid) return; + for (auto &v : _tick_other_veh_cache) { + if (v == this) v = nullptr; + } +} diff --git a/src/effectvehicle_base.h b/src/effectvehicle_base.h index d809657fab..9d62caa013 100644 --- a/src/effectvehicle_base.h +++ b/src/effectvehicle_base.h @@ -30,11 +30,13 @@ struct EffectVehicle FINAL : public SpecializedVehicleRemoveEffectVehicleFromTickCache(); } void UpdateDeltaXY(); bool Tick(); TransparencyOption GetTransparencyOption() const; + void AddEffectVehicleToTickCache(); + void RemoveEffectVehicleFromTickCache(); }; /** diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index 61eae64d06..12d493d567 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -252,6 +252,8 @@ int GroundVehicle::GetAcceleration() !(Train::From(this)->flags & (VRF_IS_BROKEN | (1 << VRF_TRAIN_STUCK))) && this->cur_speed < 3 && accel < 5) { SetBit(Train::From(this)->flags, VRF_TOO_HEAVY); + extern std::vector _tick_train_too_heavy_cache; + _tick_train_too_heavy_cache.push_back(Train::From(this)); } } diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp index 78e539dbb7..8d9acf3924 100644 --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -366,6 +366,24 @@ struct GroundVehicle : public SpecializedVehicle { */ inline bool IsRearDualheaded() const { return this->IsMultiheaded() && !this->IsEngine(); } + /** + * Check if the vehicle is a front engine. + * @return Returns true if the vehicle is a front engine. + */ + inline bool IsFrontEngine() const + { + return HasBit(this->subtype, GVSF_FRONT); + } + + /** + * Check if the vehicle is an articulated part of an engine. + * @return Returns true if the vehicle is an articulated part. + */ + inline bool IsArticulatedPart() const + { + return HasBit(this->subtype, GVSF_ARTICULATED_PART); + } + /** * Update the GUI variant of the current speed of the vehicle. * Also mark the widget dirty when that is needed, i.e. when diff --git a/src/misc.cpp b/src/misc.cpp index a299f46328..4cb5fdd0d7 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -131,6 +131,9 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin InitializeEconomy(); + InvalidateVehicleTickCaches(); + ClearVehicleTickCaches(); + ResetObjectToPlace(); ResetRailPlacementSnapping(); diff --git a/src/openttd.cpp b/src/openttd.cpp index a74d5b9603..26d426f01c 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -374,6 +374,8 @@ static void ShutdownGame() ViewportMapClearTunnelCache(); ViewportClearStationSignCache(); + InvalidateVehicleTickCaches(); + ClearVehicleTickCaches(); ClearCommandLog(); } diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index d964876d9a..2d9d082a2e 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -332,6 +332,8 @@ CommandCost CmdBuildRoadVehicle(TileIndex tile, DoCommandFlag flags, const Engin v->UpdatePosition(); CheckConsistencyOfArticulatedVehicle(v); + + InvalidateVehicleTickCaches(); } return CommandCost(); @@ -1665,10 +1667,6 @@ Money RoadVehicle::GetRunningCost() const bool RoadVehicle::Tick() { - PerformanceAccumulator framerate(PFE_GL_ROADVEHS); - - this->tick_counter++; - if (this->IsFrontEngine()) { if (!(this->IsRoadVehicleStopped())) this->running_ticks++; return RoadVehController(this); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 74e412342c..bb134266b7 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3620,6 +3620,9 @@ bool AfterLoadGame() AfterLoadTraceRestrict(); AfterLoadTemplateVehiclesUpdateImage(); + InvalidateVehicleTickCaches(); + ClearVehicleTickCaches(); + /* Show this message last to avoid covering up an error message if we bail out part way */ switch (gcf_res) { case GLC_COMPATIBLE: ShowErrorMessage(STR_NEWGRF_COMPATIBLE_LOAD_WARNING, INVALID_STRING_ID, WL_CRITICAL); break; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index d8e788dfa5..900b3021d6 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -839,8 +839,6 @@ reverse_direction: bool Ship::Tick() { - PerformanceAccumulator framerate(PFE_GL_SHIPS); - if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; ShipController(this); @@ -924,6 +922,7 @@ CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, const Engine *e, u v->InvalidateNewGRFCacheOfChain(); v->UpdatePosition(); + InvalidateVehicleTickCaches(); } return CommandCost(); diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 8afff5d67b..a2f83bb74a 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -172,6 +172,8 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes) assert(this->IsFrontEngine() || this->IsFreeWagon()); + InvalidateVehicleTickCaches(); + const RailVehicleInfo *rvi_v = RailVehInfo(this->engine_type); EngineID first_engine = this->IsFrontEngine() ? this->engine_type : INVALID_ENGINE; this->gcache.cached_total_length = 0; @@ -4941,10 +4943,6 @@ Money Train::GetRunningCost() const */ bool Train::Tick() { - PerformanceAccumulator framerate(PFE_GL_TRAINS); - - this->tick_counter++; - if (this->IsFrontEngine()) { if (!(this->vehstatus & VS_STOPPED) || this->cur_speed > 0) this->running_ticks++; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 5ec46de1af..b98ef2ea02 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1019,6 +1019,8 @@ Vehicle::~Vehicle() return; } + if (this->type != VEH_EFFECT) InvalidateVehicleTickCaches(); + if (this->breakdowns_since_last_service) _vehicles_to_pay_repair.erase(this); if (this->type < VEH_BEGIN || this->type >= VEH_COMPANY_END) { @@ -1122,6 +1124,91 @@ static void ShowAutoReplaceAdviceMessage(const CommandCost &res, const Vehicle * AddVehicleAdviceNewsItem(message, v->index); } +bool _tick_caches_valid = false; +std::vector _tick_train_too_heavy_cache; +std::vector _tick_train_front_cache; +std::vector _tick_road_veh_front_cache; +std::vector _tick_aircraft_front_cache; +std::vector _tick_ship_cache; +std::vector _tick_other_veh_cache; + +void ClearVehicleTickCaches() +{ + _tick_train_too_heavy_cache.clear(); + _tick_train_front_cache.clear(); + _tick_road_veh_front_cache.clear(); + _tick_aircraft_front_cache.clear(); + _tick_ship_cache.clear(); + _tick_other_veh_cache.clear(); +} + +void RebuildVehicleTickCaches() +{ + Vehicle *v = NULL; + SCOPE_INFO_FMT([&v], "RebuildVehicleTickCaches: %s", scope_dumper().VehicleInfo(v)); + + ClearVehicleTickCaches(); + + FOR_ALL_VEHICLES(v) { + switch (v->type) { + default: + _tick_other_veh_cache.push_back(v); + break; + + case VEH_TRAIN: + if (HasBit(Train::From(v)->flags, VRF_TOO_HEAVY)) _tick_train_too_heavy_cache.push_back(Train::From(v)); + if (v->Previous() == nullptr) _tick_train_front_cache.push_back(Train::From(v)); + break; + + case VEH_ROAD: + if (v->Previous() == nullptr) _tick_road_veh_front_cache.push_back(RoadVehicle::From(v)); + break; + + case VEH_AIRCRAFT: + if (v->Previous() == nullptr) _tick_aircraft_front_cache.push_back(Aircraft::From(v)); + break; + + case VEH_SHIP: + _tick_ship_cache.push_back(Ship::From(v)); + break; + } + } + _tick_caches_valid = true; +} + +void VehicleTickCargoAging(Vehicle *v) +{ + if (v->vcache.cached_cargo_age_period != 0) { + v->cargo_age_counter = min(v->cargo_age_counter, v->vcache.cached_cargo_age_period); + if (--v->cargo_age_counter == 0) { + v->cargo.AgeCargo(); + v->cargo_age_counter = v->vcache.cached_cargo_age_period; + } + } +} + +void VehicleTickMotion(Vehicle *v, Vehicle *front) +{ + /* Do not play any sound when crashed */ + if (front->vehstatus & VS_CRASHED) return; + + /* Do not play any sound when in depot or tunnel */ + if (v->vehstatus & VS_HIDDEN) return; + + v->motion_counter += front->cur_speed; + if (_settings_client.sound.vehicle) { + /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */ + if (GB(v->motion_counter, 0, 8) < front->cur_speed) PlayVehicleSound(v, VSE_RUNNING); + + /* Play an alternating running sound every 16 ticks */ + if (GB(v->tick_counter, 0, 4) == 0) { + /* Play running sound when speed > 0 and not braking */ + bool running = (front->cur_speed > 0) && !(front->vehstatus & (VS_STOPPED | VS_TRAIN_SLOWING)); + PlayVehicleSound(v, running ? VSE_RUNNING_16 : VSE_STOPPED_16); + } + } +} + void CallVehicleTicks() { _vehicles_to_autoreplace.Clear(); @@ -1137,98 +1224,72 @@ void CallVehicleTicks() SCOPE_INFO_FMT([&st], "CallVehicleTicks: LoadUnloadStation: %s", scope_dumper().StationInfo(st)); FOR_ALL_STATIONS(st) LoadUnloadStation(st); } - PerformanceAccumulator::Reset(PFE_GL_TRAINS); - PerformanceAccumulator::Reset(PFE_GL_ROADVEHS); - PerformanceAccumulator::Reset(PFE_GL_SHIPS); - PerformanceAccumulator::Reset(PFE_GL_AIRCRAFT); + + if (!_tick_caches_valid) RebuildVehicleTickCaches(); Vehicle *v = NULL; SCOPE_INFO_FMT([&v], "CallVehicleTicks: %s", scope_dumper().VehicleInfo(v)); - FOR_ALL_VEHICLES(v) { - /* Vehicle could be deleted in this tick */ - auto tick = [](Vehicle *v) -> bool { - /* De-virtualise most common cases */ - if (v->type == VEH_TRAIN) return Train::From(v)->Train::Tick(); - if (v->type == VEH_ROAD) return RoadVehicle::From(v)->RoadVehicle::Tick(); - if (v->type == VEH_AIRCRAFT) return Aircraft::From(v)->Aircraft::Tick(); - return v->Tick(); - }; - if (!tick(v)) { - assert(Vehicle::Get(vehicle_index) == NULL); - continue; - } - - assert(Vehicle::Get(vehicle_index) == v); - - switch (v->type) { - default: break; - - case VEH_TRAIN: - if (HasBit(Train::From(v)->flags, VRF_TOO_HEAVY)) { - if (v->owner == _local_company) { - SetDParam(0, v->index); - SetDParam(1, STR_ERROR_TRAIN_TOO_HEAVY); - AddVehicleNewsItem(STR_ERROR_TRAIN_TOO_HEAVY, NT_ADVICE, v->index); - } - ClrBit(Train::From(v)->flags, VRF_TOO_HEAVY); + { + PerformanceMeasurer framerate(PFE_GL_TRAINS); + for (Train *t : _tick_train_too_heavy_cache) { + if (HasBit(t->flags, VRF_TOO_HEAVY)) { + if (t->owner == _local_company) { + SetDParam(0, t->index); + SetDParam(1, STR_ERROR_TRAIN_TOO_HEAVY); + AddVehicleNewsItem(STR_ERROR_TRAIN_TOO_HEAVY, NT_ADVICE, t->index); } - /* FALL THROUGH */ - case VEH_ROAD: - case VEH_AIRCRAFT: - case VEH_SHIP: { - Vehicle *front = v->First(); - - if (v->vcache.cached_cargo_age_period != 0) { - v->cargo_age_counter = min(v->cargo_age_counter, v->vcache.cached_cargo_age_period); - if (--v->cargo_age_counter == 0) { - v->cargo.AgeCargo(); - v->cargo_age_counter = v->vcache.cached_cargo_age_period; - } - } - - /* Do not play any sound when crashed */ - if (front->vehstatus & VS_CRASHED) continue; - - /* Do not play any sound when in depot or tunnel */ - if (v->vehstatus & VS_HIDDEN) continue; - - /* Do not play any sound when stopped */ - if ((front->vehstatus & VS_STOPPED) && (front->type != VEH_TRAIN || front->cur_speed == 0)) continue; - - /* Check vehicle type specifics */ - switch (v->type) { - case VEH_TRAIN: - if (Train::From(v)->IsWagon()) continue; - break; - - case VEH_ROAD: - if (!RoadVehicle::From(v)->IsFrontEngine()) continue; - break; - - case VEH_AIRCRAFT: - if (!Aircraft::From(v)->IsNormalAircraft()) continue; - break; - - default: - break; - } - - v->motion_counter += front->cur_speed; - if (_settings_client.sound.vehicle) { - /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */ - if (GB(v->motion_counter, 0, 8) < front->cur_speed) PlayVehicleSound(v, VSE_RUNNING); - - /* Play an alternating running sound every 16 ticks */ - if (GB(v->tick_counter, 0, 4) == 0) { - /* Play running sound when speed > 0 and not braking */ - bool running = (front->cur_speed > 0) && !(front->vehstatus & (VS_STOPPED | VS_TRAIN_SLOWING)); - PlayVehicleSound(v, running ? VSE_RUNNING_16 : VSE_STOPPED_16); - } - } - - break; + ClrBit(t->flags, VRF_TOO_HEAVY); } } + _tick_train_too_heavy_cache.clear(); + for (Train *front : _tick_train_front_cache) { + v = front; + if (!front->Train::Tick()) continue; + for (Train *u = front; u != nullptr; u = u->Next()) { + u->tick_counter++; + VehicleTickCargoAging(u); + if (!u->IsWagon() && !((front->vehstatus & VS_STOPPED) && front->cur_speed == 0)) VehicleTickMotion(u, front); + } + } + } + { + PerformanceMeasurer framerate(PFE_GL_ROADVEHS); + for (RoadVehicle *front : _tick_road_veh_front_cache) { + v = front; + if (!front->RoadVehicle::Tick()) continue; + for (RoadVehicle *u = front; u != nullptr; u = u->Next()) { + u->tick_counter++; + VehicleTickCargoAging(u); + } + if (!(front->vehstatus & VS_STOPPED)) VehicleTickMotion(front, front); + } + } + { + PerformanceMeasurer framerate(PFE_GL_AIRCRAFT); + for (Aircraft *front : _tick_aircraft_front_cache) { + v = front; + if (!front->Aircraft::Tick()) continue; + for (Aircraft *u = front; u != nullptr; u = u->Next()) { + VehicleTickCargoAging(u); + } + if (!(front->vehstatus & VS_STOPPED)) VehicleTickMotion(front, front); + } + } + { + PerformanceMeasurer framerate(PFE_GL_SHIPS); + for (Ship *s : _tick_ship_cache) { + v = s; + if (!s->Ship::Tick()) continue; + VehicleTickCargoAging(s); + if (!(s->vehstatus & VS_STOPPED)) VehicleTickMotion(s, s); + } + } + { + for (Vehicle *u : _tick_other_veh_cache) { + if (!u) continue; + v = u; + u->Tick(); + } } v = NULL; diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 9d40b4946c..5caa0747ac 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -1280,4 +1280,12 @@ struct FreeUnitIDGenerator { /** Sentinel for an invalid coordinate. */ static const int32 INVALID_COORD = 0x7fffffff; +inline void InvalidateVehicleTickCaches() +{ + extern bool _tick_caches_valid; + _tick_caches_valid = false; +} + +void ClearVehicleTickCaches(); + #endif /* VEHICLE_BASE_H */