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.
This commit is contained in:
Jonathan G Rennison 2021-05-18 19:07:44 +01:00
parent 7274432987
commit a15e26f369
12 changed files with 285 additions and 3 deletions

View File

@ -16,6 +16,8 @@
#include "core/tinystring_type.hpp"
#include "newgrf_commons.h"
#include "3rdparty/cpp-btree/btree_map.h"
typedef Pool<Engine, EngineID, 64, 64000> 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<const SpriteGroup *, uint64> sprite_group_cb36_properties_used;
Engine();
Engine(VehicleType type, EngineID base);
~Engine();

View File

@ -17,6 +17,7 @@
void SetupEngines();
void StartupEngines();
void CheckEngines();
void AnalyseEngineCallbacks();
/* Original engine data counts and offsets */
extern const uint8 _engine_counts[4];

View File

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

View File

@ -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<const SpriteGroup *, uint64> 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;
}
}
}
}

View File

@ -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<uint8, int8> (this->adjusts[0], nullptr, 0, UINT_MAX); break;
case DSG_SIZE_WORD: value = EvalAdjustT<uint16, int16>(this->adjusts[0], nullptr, 0, UINT_MAX); break;
case DSG_SIZE_DWORD: value = EvalAdjustT<uint32, int32>(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
{

View File

@ -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<const SpriteGroup *> 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;
};

View File

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

View File

@ -10,6 +10,7 @@
#include "../stdafx.h"
#include "saveload_internal.h"
#include "../engine_base.h"
#include "../engine_func.h"
#include "../string_func.h"
#include <vector>
@ -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 },

View File

@ -26,6 +26,7 @@ void MoveWaypointsToBaseStations();
const SaveLoad *GetBaseStationDescription();
void AfterLoadVehicles(bool part_of_load);
void AfterLoadEngines();
void FixupTrainLengths();
void AfterLoadTemplateVehicles();
void AfterLoadStations();

View File

@ -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)",

View File

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

View File

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