Skip to content

Commit

Permalink
Cache waste levels when running governors
Browse files Browse the repository at this point in the history
Evaluating waste-related effects was found to represent half of the
governors' runtime. Cache them to speed things up.

This optimization breaks if waste depends on the celebration status, but
governors are generally broken in this regard (they also don't
understand how tile outputs might change).
  • Loading branch information
lmoureaux authored and jwrober committed Jul 15, 2024
1 parent 966a9f5 commit ecabf5d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 29 deletions.
22 changes: 20 additions & 2 deletions common/aicore/cm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ struct cm_state {
// cached government centers to avoid looping through all cities
std::vector<city *> gov_centers;

// cached waste levels to avoid recomputations
std::array<cached_waste, O_LAST> waste;

// the best known solution, and its fitness
struct partial_solution best;
struct cm_fitness best_value;
Expand Down Expand Up @@ -694,7 +697,8 @@ static void apply_solution(struct cm_state *state,
}

// Finally we must refresh the city to reset all the precomputed fields.
city_refresh_from_main_map(pcity, state->workers_map, state->gov_centers);
city_refresh_from_main_map(pcity, state->workers_map, state->gov_centers,
&state->waste);
fc_assert_ret(citizen_count == city_size_get(pcity));
}

Expand Down Expand Up @@ -1608,7 +1612,7 @@ static void compute_max_stats_heuristic(const struct cm_state *state,
}
output_type_iterate_end;

set_city_production(pcity, state->gov_centers);
set_city_production(pcity, state->gov_centers, &state->waste);
memcpy(production, pcity->prod, sizeof(pcity->prod));
}

Expand Down Expand Up @@ -1854,6 +1858,20 @@ static struct cm_state *cm_state_init(struct city *pcity,
// cache government centers
state->gov_centers = player_gov_centers(pplayer);

// cache waste levels
output_type_iterate(o)
{
state->waste[o].level =
get_city_output_bonus(pcity, get_output_type(o), EFT_OUTPUT_WASTE);
state->waste[o].relative = get_city_output_bonus(
pcity, get_output_type(o), EFT_OUTPUT_WASTE_PCT);
state->waste[o].by_distance = get_city_output_bonus(
pcity, get_output_type(o), EFT_OUTPUT_WASTE_BY_DISTANCE);
state->waste[o].by_rel_distance = get_city_output_bonus(
pcity, get_output_type(o), EFT_OUTPUT_WASTE_BY_REL_DISTANCE);
}
output_type_iterate_end;

// For the heuristic, make sorted copies of the lattice
output_type_iterate(stat_index)
{
Expand Down
56 changes: 35 additions & 21 deletions common/city.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2806,7 +2806,8 @@ int city_airlift_max(const struct city *pcity)
the bonus[] and citizen_base[] arrays are alread built.
*/
void set_city_production(struct city *pcity,
const std::vector<city *> &gov_centers)
const std::vector<city *> &gov_centers,
const std::array<cached_waste, O_LAST> *pcwaste)
{
/* Calculate city production!
*
Expand Down Expand Up @@ -2868,7 +2869,7 @@ void set_city_production(struct city *pcity,
{
pcity->waste[o] =
city_waste(pcity, o, pcity->prod[o] * pcity->bonus[o] / 100, nullptr,
gov_centers);
gov_centers, pcwaste ? &pcwaste->at(o) : nullptr);
}
output_type_iterate_end;

Expand Down Expand Up @@ -3023,8 +3024,10 @@ static inline void city_support(struct city *pcity)
If 'workers_map' is set, only basic updates are needed.
*/
void city_refresh_from_main_map(struct city *pcity, bool *workers_map,
const std::vector<city *> &gov_centers)
void city_refresh_from_main_map(
struct city *pcity, bool *workers_map,
const std::vector<city *> &gov_centers,
const std::array<cached_waste, O_LAST> *pcwaste)
{
if (workers_map == nullptr) {
// do a full refresh
Expand All @@ -3041,7 +3044,7 @@ void city_refresh_from_main_map(struct city *pcity, bool *workers_map,
get_worked_tile_output(pcity, pcity->citizen_base, workers_map);
add_specialist_output(pcity, pcity->citizen_base);

set_city_production(pcity, gov_centers);
set_city_production(pcity, gov_centers, pcwaste);
citizen_base_mood(pcity);
/* Note that pollution is calculated before unhappy_city_check() makes
* deductions for disorder; so a city in disorder still causes pollution */
Expand Down Expand Up @@ -3076,15 +3079,22 @@ void city_refresh_from_main_map(struct city *pcity, bool *workers_map,
(not cumulative).
*/
int city_waste(const struct city *pcity, Output_type_id otype, int total,
int *breakdown, const std::vector<city *> &gov_centers)
int *breakdown, const std::vector<city *> &gov_centers,
const cached_waste *pcwaste)
{
int penalty_waste = 0;
int penalty_size = 0; /* separate notradesize/fulltradesize from normal
* corruption */
int total_eft = total; /* normal corruption calculated on total reduced by
* possible size penalty */
int waste_level =
get_city_output_bonus(pcity, get_output_type(otype), EFT_OUTPUT_WASTE);

cached_waste waste;
if (pcwaste) {
waste = *pcwaste;
} else {
waste.level = get_city_output_bonus(pcity, get_output_type(otype),
EFT_OUTPUT_WASTE);
}
bool waste_all = false;

if (otype == O_TRADE) {
Expand Down Expand Up @@ -3112,11 +3122,13 @@ int city_waste(const struct city *pcity, Output_type_id otype, int total,
/* Distance-based waste.
* Don't bother calculating if there's nothing left to lose. */
if (total_eft > 0) {
int waste_by_dist = get_city_output_bonus(pcity, get_output_type(otype),
EFT_OUTPUT_WASTE_BY_DISTANCE);
int waste_by_rel_dist = get_city_output_bonus(
pcity, get_output_type(otype), EFT_OUTPUT_WASTE_BY_REL_DISTANCE);
if (waste_by_dist > 0 || waste_by_rel_dist > 0) {
if (!pcwaste) {
waste.by_distance = get_city_output_bonus(
pcity, get_output_type(otype), EFT_OUTPUT_WASTE_BY_DISTANCE);
waste.by_rel_distance = get_city_output_bonus(
pcity, get_output_type(otype), EFT_OUTPUT_WASTE_BY_REL_DISTANCE);
}
if (waste.by_distance > 0 || waste.by_rel_distance > 0) {
const struct city *gov_center = nullptr;
int min_dist = FC_INFINITY;

Expand All @@ -3142,12 +3154,12 @@ int city_waste(const struct city *pcity, Output_type_id otype, int total,
if (gov_center == nullptr) {
waste_all = true; // no gov center - no income
} else {
waste_level += waste_by_dist * min_dist / 100;
if (waste_by_rel_dist > 0) {
waste.level += waste.by_distance * min_dist / 100;
if (waste.by_rel_distance > 0) {
/* Multiply by 50 as an "standard size" for which
* EFT_OUTPUT_WASTE_BY_DISTANCE and
* EFT_OUTPUT_WASTE_BY_REL_DISTANCE would give same result. */
waste_level += waste_by_rel_dist * 50 * min_dist / 100
waste.level += waste.by_rel_distance * 50 * min_dist / 100
/ MAX(wld.map.xsize, wld.map.ysize);
}
}
Expand All @@ -3157,16 +3169,18 @@ int city_waste(const struct city *pcity, Output_type_id otype, int total,
if (waste_all) {
penalty_waste = total_eft;
} else {
int waste_pct = get_city_output_bonus(pcity, get_output_type(otype),
EFT_OUTPUT_WASTE_PCT);
if (!pcwaste) {
waste.relative = get_city_output_bonus(pcity, get_output_type(otype),
EFT_OUTPUT_WASTE_PCT);
}

/* corruption/waste calculated only for the actually produced amount */
if (waste_level > 0) {
penalty_waste = total_eft * waste_level / 100;
if (waste.level > 0) {
penalty_waste = total_eft * waste.level / 100;
}

// bonus calculated only for the actually produced amount
penalty_waste -= penalty_waste * waste_pct / 100;
penalty_waste -= penalty_waste * waste.relative / 100;

// Clip
penalty_waste = MIN(MAX(penalty_waste, 0), total_eft);
Expand Down
23 changes: 17 additions & 6 deletions common/city.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ enum city_updates {
CU_POPUP_DIALOG = 1 << 2
};

/// Used to cache the value of waste effects to speed up governors
struct cached_waste {
int level = 0; // EFT_OUTPUT_WASTE
int relative = 0; // EFT_OUTPUT_WASTE_PCT
int by_distance = 0; // EFT_OUTPUT_WASTE_BY_DISTANCE
int by_rel_distance = 0; // EFT_OUTPUT_WASTE_BY_REL_DISTANCE
};

struct tile_cache; // defined and only used within city.c

struct adv_city; /* defined in ./server/advisors/infracache.h */
Expand Down Expand Up @@ -523,8 +531,9 @@ const char *get_output_name(Output_type_id output);
struct output_type *get_output_type(Output_type_id output);
Output_type_id output_type_by_identifier(const char *id);
void add_specialist_output(const struct city *pcity, int *output);
void set_city_production(struct city *pcity,
const std::vector<city *> &gov_centers);
void set_city_production(
struct city *pcity, const std::vector<city *> &gov_centers,
const std::array<cached_waste, O_LAST> *pcwaste = nullptr);

// properties

Expand Down Expand Up @@ -694,11 +703,13 @@ void city_remove_improvement(struct city *pcity,
const struct impr_type *pimprove);

// city update functions
void city_refresh_from_main_map(struct city *pcity, bool *workers_map,
const std::vector<city *> &gov_centers);

void city_refresh_from_main_map(
struct city *pcity, bool *workers_map,
const std::vector<city *> &gov_centers,
const std::array<cached_waste, O_LAST> *pcwaste = nullptr);
int city_waste(const struct city *pcity, Output_type_id otype, int total,
int *breakdown, const std::vector<city *> &gov_centers);
int *breakdown, const std::vector<city *> &gov_centers,
const cached_waste *pcwaste = nullptr);
Specialist_type_id best_specialist(Output_type_id otype,
const struct city *pcity);
int get_final_city_output_bonus(const struct city *pcity,
Expand Down

0 comments on commit ecabf5d

Please sign in to comment.