From a15e26f3697832f3d49570e8edd1399de2f8c798 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 18 May 2021 19:07:44 +0100 Subject: [PATCH] NewGRF: Elide unmasked vehicle callbacks where possible This includes: * CBID_VEHICLE_32DAY_CALLBACK * CBID_VEHICLE_REFIT_COST * CBID_VEHICLE_MODIFY_PROPERTY This is on a per-property basis The main benefit of this is to avoid callbacks not handled by the vehicle's current sprite group from using the full graphics chain as the "default" branch in the callback switch. In the case where the graphics chain is long/expensive, a lot of work had to be done before a callback failure result was eventually returned. --- src/engine_base.h | 6 ++ src/engine_func.h | 1 + src/newgrf_commons.h | 8 ++ src/newgrf_engine.cpp | 49 +++++++++- src/newgrf_spritegroup.cpp | 156 +++++++++++++++++++++++++++++++ src/newgrf_spritegroup.h | 20 ++++ src/saveload/afterload.cpp | 3 + src/saveload/engine_sl.cpp | 6 ++ src/saveload/saveload_internal.h | 1 + src/table/newgrf_debug_data.h | 34 +++++++ src/vehicle.cpp | 2 +- src/vehicle_cmd.cpp | 2 +- 12 files changed, 285 insertions(+), 3 deletions(-) diff --git a/src/engine_base.h b/src/engine_base.h index de26c8a06c..3eabf67382 100644 --- a/src/engine_base.h +++ b/src/engine_base.h @@ -16,6 +16,8 @@ #include "core/tinystring_type.hpp" #include "newgrf_commons.h" +#include "3rdparty/cpp-btree/btree_map.h" + typedef Pool EnginePool; extern EnginePool _engine_pool; @@ -61,6 +63,10 @@ struct Engine : EnginePool::PoolItem<&_engine_pool> { struct WagonOverride *overrides; uint16 list_position; + SpriteGroupCallbacksUsed callbacks_used = SGCU_ALL; + uint64 cb36_properties_used = UINT64_MAX; + btree::btree_map sprite_group_cb36_properties_used; + Engine(); Engine(VehicleType type, EngineID base); ~Engine(); diff --git a/src/engine_func.h b/src/engine_func.h index b046490c1f..b778956171 100644 --- a/src/engine_func.h +++ b/src/engine_func.h @@ -17,6 +17,7 @@ void SetupEngines(); void StartupEngines(); void CheckEngines(); +void AnalyseEngineCallbacks(); /* Original engine data counts and offsets */ extern const uint8 _engine_counts[4]; diff --git a/src/newgrf_commons.h b/src/newgrf_commons.h index ed18aaa9e2..7ffb05e11c 100644 --- a/src/newgrf_commons.h +++ b/src/newgrf_commons.h @@ -333,4 +333,12 @@ struct GRFFileProps : GRFFilePropsBase<1> { uint16 override; ///< id of the entity been replaced by }; +enum SpriteGroupCallbacksUsed : uint8 { + SGCU_NONE = 0, + SGCU_ALL = 0xFF, + SGCU_VEHICLE_32DAY_CALLBACK = 1 << 0, + SGCU_VEHICLE_REFIT_COST = 1 << 1, +}; +DECLARE_ENUM_AS_BIT_SET(SpriteGroupCallbacksUsed) + #endif /* NEWGRF_COMMONS_H */ diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index da1ed5261e..ce49977563 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -24,6 +24,7 @@ #include "newgrf_roadtype.h" #include "newgrf_cache_check.h" #include "ship.h" +#include "scope_info.h" #include "safeguards.h" @@ -1355,7 +1356,17 @@ uint GetVehicleProperty(const Vehicle *v, PropertyID property, uint orig_value) uint GetEngineProperty(EngineID engine, PropertyID property, uint orig_value, const Vehicle *v) { - uint16 callback = GetVehicleCallback(CBID_VEHICLE_MODIFY_PROPERTY, property, 0, engine, v); + const Engine *e = Engine::Get(engine); + if (property < 64 && !HasBit(e->cb36_properties_used, property)) return orig_value; + + VehicleResolverObject object(engine, v, VehicleResolverObject::WO_UNCACHED, false, CBID_VEHICLE_MODIFY_PROPERTY, property, 0); + if (property < 64 && !e->sprite_group_cb36_properties_used.empty()) { + auto iter = e->sprite_group_cb36_properties_used.find(object.root_spritegroup); + if (iter != e->sprite_group_cb36_properties_used.end()) { + if (!HasBit(iter->second, property)) return orig_value; + } + } + uint16 callback = object.ResolveCallback(); if (callback != CALLBACK_FAILED) return callback; return orig_value; @@ -1562,3 +1573,39 @@ void FillNewGRFVehicleCache(const Vehicle *v) /* Make sure really all bits are set. */ assert(v->grf_cache.cache_valid == (1 << NCVV_END) - 1); } + +void AnalyseEngineCallbacks() +{ + btree::btree_map sg_cb36; + for (Engine *e : Engine::Iterate()) { + sg_cb36.clear(); + e->sprite_group_cb36_properties_used.clear(); + + SpriteGroupCallbacksUsed callbacks_used = SGCU_NONE; + uint64 cb36_properties_used = 0; + auto process_sg = [&](const SpriteGroup *sg) { + if (sg == nullptr) return; + + AnalyseCallbackOperation op; + sg->AnalyseCallbacks(op); + callbacks_used |= op.callbacks_used; + cb36_properties_used |= op.properties_used; + sg_cb36[sg] = op.properties_used; + }; + + AnalyseCallbackOperation op; + for (uint i = 0; i < NUM_CARGO + 2; i++) { + process_sg(e->grf_prop.spritegroup[i]); + } + for (uint i = 0; i < e->overrides_count; i++) { + process_sg(e->overrides[i].group); + } + e->callbacks_used = callbacks_used; + e->cb36_properties_used = cb36_properties_used; + for (auto iter : sg_cb36) { + if (iter.second != cb36_properties_used) { + e->sprite_group_cb36_properties_used[iter.first] = iter.second; + } + } + } +} diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index 4ce93e1459..f26b30062c 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -256,6 +256,162 @@ const SpriteGroup *DeterministicSpriteGroup::Resolve(ResolverObject &object) con return SpriteGroup::Resolve(this->default_group, object, false); } +void DeterministicSpriteGroup::AnalyseCallbacks(AnalyseCallbackOperation &op) const +{ + auto res = op.seen.insert(this); + if (!res.second) { + /* Already seen this group */ + return; + } + + auto check_1A_range = [&]() -> bool { + if (this->adjusts.size() == 1 && this->adjusts[0].variable == 0x1A) { + /* Not clear why some GRFs do this, perhaps a way of commenting out a branch */ + uint32 value = 0; + switch (this->size) { + case DSG_SIZE_BYTE: value = EvalAdjustT (this->adjusts[0], nullptr, 0, UINT_MAX); break; + case DSG_SIZE_WORD: value = EvalAdjustT(this->adjusts[0], nullptr, 0, UINT_MAX); break; + case DSG_SIZE_DWORD: value = EvalAdjustT(this->adjusts[0], nullptr, 0, UINT_MAX); break; + default: NOT_REACHED(); + } + for (const auto &range : this->ranges) { + if (range.low <= value && value <= range.high) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return true; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return true; + } + return false; + }; + + if (op.mode == ACOM_FIND_CB_RESULT) { + if (this->calculated_result) { + op.cb_result_found = true; + return; + } else if (!op.cb_result_found) { + if (check_1A_range()) return; + if (this->adjusts.size() == 1 && this->adjusts[0].variable == 0xC) { + const auto &adjust = this->adjusts[0]; + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high && range.low == 0xC) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + } + return; + } + + if (check_1A_range()) return; + + auto find_cb_result = [&]() -> bool { + if (this->calculated_result) return true; + AnalyseCallbackOperation cbr_op; + cbr_op.mode = ACOM_FIND_CB_RESULT; + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(cbr_op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(cbr_op); + return cbr_op.cb_result_found; + }; + + if (this->adjusts.size() == 1 && !this->calculated_result) { + const auto &adjust = this->adjusts[0]; + if (op.mode == ACOM_CB_VAR && adjust.variable == 0xC) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high) { + switch (range.low) { + case CBID_VEHICLE_32DAY_CALLBACK: + op.callbacks_used |= SGCU_VEHICLE_32DAY_CALLBACK; + break; + + case CBID_VEHICLE_REFIT_COST: + op.callbacks_used |= SGCU_VEHICLE_REFIT_COST; + break; + + case CBID_VEHICLE_MODIFY_PROPERTY: + if (range.group != nullptr) { + AnalyseCallbackOperation cb36_op; + cb36_op.mode = ACOM_CB36_PROP; + range.group->AnalyseCallbacks(cb36_op); + if (cb36_op.properties_used == UINT64_MAX) DumpSpriteGroup(range.group, 0); + op.properties_used |= cb36_op.properties_used; + } + break; + } + } else { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0x10) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high) { + if (range.low < 64) { + if (find_cb_result()) SetBit(op.properties_used, range.low); + } + } else { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0xC) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low <= CBID_VEHICLE_MODIFY_PROPERTY && CBID_VEHICLE_MODIFY_PROPERTY <= range.high) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + } + for (const auto &adjust : this->adjusts) { + if (op.mode == ACOM_CB_VAR && adjust.variable == 0xC) { + op.callbacks_used |= SGCU_ALL; + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0x10) { + if (find_cb_result()) { + op.properties_used |= UINT64_MAX; + } + } + if (adjust.variable == 0x7E && adjust.subroutine != nullptr) { + adjust.subroutine->AnalyseCallbacks(op); + } + } + if (!this->calculated_result) { + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + } +} + +void CallbackResultSpriteGroup::AnalyseCallbacks(AnalyseCallbackOperation &op) const +{ + if (op.mode == ACOM_FIND_CB_RESULT) op.cb_result_found = true; +} const SpriteGroup *RandomizedSpriteGroup::Resolve(ResolverObject &object) const { diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index e49b263ff5..edd3df0f0e 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -20,6 +20,8 @@ #include "newgrf_storage.h" #include "newgrf_commons.h" +#include "3rdparty/cpp-btree/btree_set.h" + /** * Gets the value of a so-called newgrf "register". * @param i index of the register @@ -47,6 +49,20 @@ struct SpriteGroup; typedef uint32 SpriteGroupID; struct ResolverObject; +enum AnalyseCallbackOperationMode { + ACOM_CB_VAR, + ACOM_CB36_PROP, + ACOM_FIND_CB_RESULT, +}; + +struct AnalyseCallbackOperation { + btree::btree_set seen; + AnalyseCallbackOperationMode mode = ACOM_CB_VAR; + SpriteGroupCallbacksUsed callbacks_used = SGCU_NONE; + uint64 properties_used = 0; + bool cb_result_found = false; +}; + /* SPRITE_WIDTH is 24. ECS has roughly 30 sprite groups per real sprite. * Adding an 'extra' margin would be assuming 64 sprite groups per real * sprite. 64 = 2^6, so 2^30 should be enough (for now) */ @@ -69,6 +85,7 @@ public: virtual SpriteID GetResult() const { return 0; } virtual byte GetNumResults() const { return 0; } virtual uint16 GetCallbackResult() const { return CALLBACK_FAILED; } + virtual void AnalyseCallbacks(AnalyseCallbackOperation &op) const {}; static const SpriteGroup *Resolve(const SpriteGroup *group, ResolverObject &object, bool top_level = true); }; @@ -178,6 +195,8 @@ struct DeterministicSpriteGroup : SpriteGroup { const SpriteGroup *error_group; // was first range, before sorting ranges + void AnalyseCallbacks(AnalyseCallbackOperation &op) const override; + protected: const SpriteGroup *Resolve(ResolverObject &object) const; }; @@ -228,6 +247,7 @@ struct CallbackResultSpriteGroup : SpriteGroup { uint16 result; uint16 GetCallbackResult() const { return this->result; } + void AnalyseCallbacks(AnalyseCallbackOperation &op) const override; }; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 7a1d8e29b1..89cc76bed3 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -938,6 +938,8 @@ bool AfterLoadGame() _settings_game.vehicle.train_braking_model = TBM_ORIGINAL; } + AfterLoadEngines(); + /* Update all vehicles */ AfterLoadVehicles(true); @@ -4012,6 +4014,7 @@ void ReloadNewGRFData() RecomputePrices(); /* reload vehicles */ ResetVehicleHash(); + AfterLoadEngines(); AfterLoadVehicles(false); StartupEngines(); GroupStatistics::UpdateAfterLoad(); diff --git a/src/saveload/engine_sl.cpp b/src/saveload/engine_sl.cpp index 78f67f9e20..77635f6a1c 100644 --- a/src/saveload/engine_sl.cpp +++ b/src/saveload/engine_sl.cpp @@ -10,6 +10,7 @@ #include "../stdafx.h" #include "saveload_internal.h" #include "../engine_base.h" +#include "../engine_func.h" #include "../string_func.h" #include @@ -196,6 +197,11 @@ static void Load_EIDS() } } +void AfterLoadEngines() +{ + AnalyseEngineCallbacks(); +} + extern const ChunkHandler _engine_chunk_handlers[] = { { 'EIDS', Save_EIDS, Load_EIDS, nullptr, nullptr, CH_ARRAY }, { 'ENGN', Save_ENGN, Load_ENGN, nullptr, nullptr, CH_ARRAY }, diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h index 1f7b2e7511..11bfd00169 100644 --- a/src/saveload/saveload_internal.h +++ b/src/saveload/saveload_internal.h @@ -26,6 +26,7 @@ void MoveWaypointsToBaseStations(); const SaveLoad *GetBaseStationDescription(); void AfterLoadVehicles(bool part_of_load); +void AfterLoadEngines(); void FixupTrainLengths(); void AfterLoadTemplateVehicles(); void AfterLoadStations(); diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 6a07c1123e..d352c3abe9 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -10,6 +10,7 @@ #include "../newgrf_house.h" #include "../newgrf_engine.h" #include "../newgrf_roadtype.h" +#include "../newgrf_cargo.h" #include "../date_func.h" #include "../timetable.h" #include "../ship.h" @@ -272,6 +273,39 @@ class NIHVehicle : public NIHelper { print(buffer); const Engine *e = Engine::GetIfValid(v->engine_type); if (e != nullptr) { + seprintf(buffer, lastof(buffer), " Callbacks: 0x%X, CB36 Properties: 0x" OTTD_PRINTFHEX64, + e->callbacks_used, e->cb36_properties_used); + print(buffer); + uint64 cb36_properties = e->cb36_properties_used; + if (!e->sprite_group_cb36_properties_used.empty()) { + const SpriteGroup *root_spritegroup = nullptr; + if (v->IsGroundVehicle()) root_spritegroup = GetWagonOverrideSpriteSet(v->engine_type, v->cargo_type, v->GetGroundVehicleCache()->first_engine); + if (root_spritegroup == nullptr) { + CargoID cargo = v->cargo_type; + assert(cargo < lengthof(e->grf_prop.spritegroup)); + root_spritegroup = e->grf_prop.spritegroup[cargo] != nullptr ? e->grf_prop.spritegroup[cargo] : e->grf_prop.spritegroup[CT_DEFAULT]; + } + auto iter = e->sprite_group_cb36_properties_used.find(root_spritegroup); + if (iter != e->sprite_group_cb36_properties_used.end()) { + cb36_properties = iter->second; + seprintf(buffer, lastof(buffer), " Current sprite group: CB36 Properties: 0x" OTTD_PRINTFHEX64, iter->second); + print(buffer); + } + } + if (cb36_properties != UINT64_MAX) { + uint64 props = cb36_properties; + while (props) { + PropertyID prop = (PropertyID)FindFirstBit64(props); + props = KillFirstBit(props); + uint16 res = GetVehicleProperty(v, prop, CALLBACK_FAILED); + if (res == CALLBACK_FAILED) { + seprintf(buffer, lastof(buffer), " CB36: 0x%X --> FAILED", prop); + } else { + seprintf(buffer, lastof(buffer), " CB36: 0x%X --> 0x%X", prop, res); + } + print(buffer); + } + } YearMonthDay ymd; ConvertDateToYMD(e->intro_date, &ymd); seprintf(buffer, lastof(buffer), " Intro: %4i-%02i-%02i, Age: %u, Base life: %u, Durations: %u %u %u (sum: %u)", diff --git a/src/vehicle.cpp b/src/vehicle.cpp index c81ff2e61f..17af6f66d0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1203,7 +1203,7 @@ static void RunVehicleDayProc() if (v == nullptr) continue; /* Call the 32-day callback if needed */ - if ((v->day_counter & 0x1F) == 0 && v->HasEngineType()) { + if ((v->day_counter & 0x1F) == 0 && v->HasEngineType() && (Engine::Get(v->engine_type)->callbacks_used & SGCU_VEHICLE_32DAY_CALLBACK) != 0) { uint16 callback = GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK, 0, 0, v->engine_type, v); if (callback != CALLBACK_FAILED) { if (HasBit(callback, 0)) { diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index eadc547e29..f20a6c5bd9 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -272,7 +272,7 @@ static int GetRefitCostFactor(const Vehicle *v, EngineID engine_type, CargoID ne const Engine *e = Engine::Get(engine_type); /* Is this vehicle a NewGRF vehicle? */ - if (e->GetGRF() != nullptr) { + if (e->GetGRF() != nullptr && (e->callbacks_used & SGCU_VEHICLE_REFIT_COST) != 0) { const CargoSpec *cs = CargoSpec::Get(new_cid); uint32 param1 = (cs->classes << 16) | (new_subtype << 8) | e->GetGRF()->cargo_map[new_cid];