Skip to content

Commit

Permalink
Back out "Performance: Optimize monster planning"
Browse files Browse the repository at this point in the history
Original commit changeset: f79a631
  • Loading branch information
akrieger committed Nov 15, 2023
1 parent 56c4952 commit 0235dfe
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 146 deletions.
7 changes: 1 addition & 6 deletions src/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,7 @@ Creature::Creature( Creature && ) noexcept( map_is_noexcept &&list_is_noexcept )
Creature &Creature::operator=( const Creature & ) = default;
Creature &Creature::operator=( Creature && ) noexcept = default;

Creature::~Creature()
{
if( g ) {
get_map().remove_creature_from_reachability( this );
}
}
Creature::~Creature() = default;

tripoint Creature::pos() const
{
Expand Down
64 changes: 0 additions & 64 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
#include "field.h"
#include "field_type.h"
#include "flag.h"
#include "flood_fill.h"
#include "fragment_cloud.h"
#include "fungal_effects.h"
#include "game.h"
Expand Down Expand Up @@ -10272,66 +10271,3 @@ tripoint drawsq_params::center() const
return view_center;
}
}

/** This is lazily evaluated on demand. Each creature in a zone is visited
* as it flood fills, then the zone number is incremented. At the end all creatures in
* the same zone will have the same zone number assigned, which can be used to have creatures in
* different zones ignore each other very cheaply.
*/
void map::flood_fill_zone( const Creature &origin )
{
creature_tracker &tracker = get_creature_tracker();

ff::flood_fill_visit_10_connected( origin.pos_bub(),
[this]( const tripoint_bub_ms & loc, int direction ) {
if( direction == 0 ) {
return inbounds( loc ) && ( is_transparent_wo_fields( loc.raw() ) ||
passable( loc ) );
}
if( direction == 1 ) {
const maptile &up = maptile_at( loc );
const ter_t &up_ter = up.get_ter_t();
if( up_ter.id.is_null() ) {
return false;
}
if( ( ( up_ter.movecost != 0 && up.get_furn_t().movecost >= 0 ) ||
is_transparent_wo_fields( loc.raw() ) ) &&
( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ||
up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) {
return true;
}
}
if( direction == -1 ) {
const maptile &up = maptile_at( loc + tripoint_above );
const ter_t &up_ter = up.get_ter_t();
if( up_ter.id.is_null() ) {
return false;
}
const maptile &down = maptile_at( loc );
const ter_t &down_ter = up.get_ter_t();
if( down_ter.id.is_null() ) {
return false;
}
if( ( ( down_ter.movecost != 0 && down.get_furn_t().movecost >= 0 ) ||
is_transparent_wo_fields( loc.raw() ) ) &&
( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ||
up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) {
return true;
}
}
return false;
},
[&tracker, this]( const tripoint_bub_ms & loc ) {
Creature *creature = tracker.creature_at<Creature>( loc );
if( creature ) {
const int n = zone_number * zone_tick;
creatures_by_zone[n].push_back( creature );
creature->set_reachable_zone( n );
}
} );
if( zone_number == std::numeric_limits<int>::max() ) {
zone_number = 1;
} else {
zone_number++;
}
}
49 changes: 2 additions & 47 deletions src/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -2236,59 +2236,14 @@ class map
bool _main_requires_cleanup = false;
std::optional<bool> _main_cleanup_override = std::nullopt;

// Tracks the dirtiness of the visitable zones cache. This must be flipped when
// Tracks the dirtiness of the visitable zones cache, but that cache does not live here,
// it is distributed among the active monsters. This must be flipped when
// persistent visibility from terrain or furniture changes
// (this excludes vehicles and fields) or when persistent traversability changes,
// which means walls and floors.
bool visitable_cache_dirty = false;
int zone_number = 1;
int zone_tick = 1;
std::unordered_map<int, std::vector<Creature *>> creatures_by_zone;
std::unordered_set<Creature *> to_remove;

void flood_fill_zone( const Creature &origin );

void flood_fill_if_needed( const Creature &origin ) {
if( get_visitable_zones_cache_dirty() ) {
creatures_by_zone.clear();
to_remove.clear();
zone_tick = zone_tick > 0 ? -1 : 1;
set_visitable_zones_cache_dirty( false );
zone_number = 1;
}
// This check insures we only flood fill when the target monster has an uninitialized zone,
// or if it has a zone from last turn. In other words it only triggers on
// the first monster in a zone each turn. We can detect this because the sign
// of the zone numbers changes on every invalidation.
int old_zone = origin.get_reachable_zone();
// Compare with zone_tick == old_zone && old_zone != 0
if( old_zone * zone_tick <= 0 ) {
flood_fill_zone( origin );
}
}

public:
// Only call from the Creature destructor.
void remove_creature_from_reachability( Creature *creature ) {
to_remove.insert( creature );
}

template <typename Functor>
void visit_reachable_creatures( const Creature &origin, Functor f ) {
flood_fill_if_needed( origin );
const auto map_iter = creatures_by_zone.find( origin.get_reachable_zone() );
if( map_iter != creatures_by_zone.end() ) {
auto vector_iter = map_iter->second.begin();
const auto vector_end = map_iter->second.end();
for( ; vector_iter != vector_end; ++vector_iter ) {
Creature *other = *vector_iter;
if( to_remove.count( other ) == 0 ) {
f( *other );
}
}
}
}

void queue_main_cleanup();
bool is_main_cleanup_queued() const;
void main_cleanup_override( bool over );
Expand Down
150 changes: 121 additions & 29 deletions src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,85 @@ bool monster::mating_angry() const
return mating_angry;
}

/** This is lazily evaluated in monster::plan(). Each monster in a zone is visited
* as it flood fills, then the zone number is incremented. At the end all monsters in
* the same zone will have the same zone number assigned, which can be used to have monsters in
* different zones ignore each other very cheaply.
*/
static void flood_fill_zone( Creature &origin )
{
static int zone_number = 1;
static int zone_tick = 1;
map &here = get_map();
if( here.get_visitable_zones_cache_dirty() ) {
zone_tick = zone_tick > 0 ? -1 : 1;
here.set_visitable_zones_cache_dirty( false );
zone_number = 1;
}
// This check insures we only flood fill when the target monster has an uninitialized zone,
// or if it has a zone from last turn. In other words it only triggers on
// the first monster in a zone each turn. We can detect this because the sign
// of the zone numbers changes on every invalidation.
int old_zone = origin.get_reachable_zone();
// Compare with zone_tick == old_zone && old_zone != 0
if( ( zone_tick > 0 && old_zone > 0 ) ||
( zone_tick < 0 && old_zone < 0 ) ) {
return;
}
creature_tracker &tracker = get_creature_tracker();

ff::flood_fill_visit_10_connected( origin.pos_bub(),
[&here]( const tripoint_bub_ms & loc, int direction ) {
if( direction == 0 ) {
return here.inbounds( loc ) && ( here.is_transparent_wo_fields( loc.raw() ) ||
here.passable( loc ) );
}
if( direction == 1 ) {
const maptile &up = here.maptile_at( loc );
const ter_t &up_ter = up.get_ter_t();
if( up_ter.id.is_null() ) {
return false;
}
if( ( ( up_ter.movecost != 0 && up.get_furn_t().movecost >= 0 ) ||
here.is_transparent_wo_fields( loc.raw() ) ) &&
( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ||
up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) {
return true;
}
}
if( direction == -1 ) {
const maptile &up = here.maptile_at( loc + tripoint_above );
const ter_t &up_ter = up.get_ter_t();
if( up_ter.id.is_null() ) {
return false;
}
const maptile &down = here.maptile_at( loc );
const ter_t &down_ter = up.get_ter_t();
if( down_ter.id.is_null() ) {
return false;
}
if( ( ( down_ter.movecost != 0 && down.get_furn_t().movecost >= 0 ) ||
here.is_transparent_wo_fields( loc.raw() ) ) &&
( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ||
up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) {
return true;
}
}
return false;
},
[&tracker]( const tripoint_bub_ms & loc ) {
Creature *creature = tracker.creature_at<Creature>( loc );
if( creature ) {
creature->set_reachable_zone( zone_number * zone_tick );
}
} );
if( zone_number == std::numeric_limits<int>::max() ) {
zone_number = 1;
} else {
zone_number++;
}
}

void monster::plan()
{
const auto &factions = g->critter_tracker->factions();
Expand All @@ -495,6 +574,7 @@ void monster::plan()
std::bitset<OVERMAP_LAYERS> seen_levels = here.get_inter_level_visibility( pos().z );
monster_attitude mood = attitude();
Character &player_character = get_player_character();
flood_fill_zone( *this );
// If we can see the player, move toward them or flee.
if( friendly == 0 && seen_levels.test( player_character.pos().z + OVERMAP_DEPTH ) &&
sees( player_character ) ) {
Expand Down Expand Up @@ -599,40 +679,52 @@ void monster::plan()
turns_since_target );
int turns_to_skip = max_turns_to_skip * rate_limiting_factor;
if( friendly == 0 && ( turns_to_skip == 0 || turns_since_target % turns_to_skip == 0 ) ) {
here.visit_reachable_creatures( *this, [this, &seen_levels, &mon_plan,
&valid_targets]( Creature & other ) {
mf_attitude faction_att = faction.obj().attitude( other.get_monster_faction() );
for( const auto &fac_list : factions ) {
mf_attitude faction_att = faction.obj().attitude( fac_list.first );
if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) {
return;
}
if( !seen_levels.test( other.posz() + OVERMAP_DEPTH ) ) {
return;
continue;
}
float rating = rate_target( other, mon_plan.dist, mon_plan.smart_planning );
if( rating == mon_plan.dist ) {
++valid_targets;
if( one_in( valid_targets ) ) {
mon_plan.target = &other;

for( const auto &fac : fac_list.second ) {
if( !seen_levels.test( fac.first + OVERMAP_DEPTH ) ) {
continue;
}
}
if( rating < mon_plan.dist ) {
mon_plan.target = &other;
mon_plan.dist = rating;
valid_targets = 1;
}
if( rating <= 5 ) {
if( anger <= 30 ) {
anger += mon_plan.angers_hostile_near;
for( const weak_ptr_fast<monster> &weak : fac.second ) {
const shared_ptr_fast<monster> shared = weak.lock();
if( !shared ) {
continue;
}
monster &mon = *shared;
if( get_reachable_zone() != mon.get_reachable_zone() ) {
continue;
}
float rating = rate_target( mon, mon_plan.dist, mon_plan.smart_planning );
if( rating == mon_plan.dist ) {
++valid_targets;
if( one_in( valid_targets ) ) {
mon_plan.target = &mon;
}
}
if( rating < mon_plan.dist ) {
mon_plan.target = &mon;
mon_plan.dist = rating;
valid_targets = 1;
}
if( rating <= 5 ) {
if( anger <= 30 ) {
anger += mon_plan.angers_hostile_near;
}
morale -= mon_plan.fears_hostile_near;
}
if( !mon_plan.fleeing && anger <= 20 && valid_targets != 0 ) {
anger += mon_plan.angers_hostile_seen;
}
if( !mon_plan.fleeing && valid_targets != 0 ) {
morale -= mon_plan.fears_hostile_seen;
}
}
morale -= mon_plan.fears_hostile_near;
}
if( !mon_plan.fleeing && anger <= 20 && valid_targets != 0 ) {
anger += mon_plan.angers_hostile_seen;
}
if( !mon_plan.fleeing && valid_targets != 0 ) {
morale -= mon_plan.fears_hostile_seen;
}
} );
}
}
if( mon_plan.target == nullptr ) {
// Just avoiding overflow.
Expand Down

0 comments on commit 0235dfe

Please sign in to comment.