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.
pull/261/head
Jonathan G Rennison 3 years ago
parent 7274432987
commit a15e26f369

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save