Feature #2440 ยป 0002-path-finding-Share-reverse-map-searches-across-maps.patch
| common/aicore/path_finding.c | ||
|---|---|---|
|
#include <fc_config.h>
|
||
|
#endif
|
||
|
#include <stdlib.h> /* qsort() */
|
||
|
/* utility */
|
||
|
#include "bitvector.h"
|
||
|
#include "genhash.h"
|
||
|
#include "log.h"
|
||
|
#include "mem.h"
|
||
|
#include "support.h"
|
||
|
/* common */
|
||
|
#include "actions.h"
|
||
|
#include "game.h"
|
||
|
#include "map.h"
|
||
|
#include "movement.h"
|
||
| ... | ... | |
|
#define PF_DEBUG
|
||
|
#endif
|
||
|
/* Define to compute every reverse map position also with a search of its
|
||
|
* own, and log any difference from the shared search (see
|
||
|
* pf_reverse_map_pos()). This repeats all the work shared searches save,
|
||
|
* so it is not part of PF_DEBUG. */
|
||
|
/* #define PF_REVERSE_DEBUG */
|
||
|
/* ======================== Internal structures ========================== */
|
||
|
#ifdef PF_DEBUG
|
||
| ... | ... | |
|
}
|
||
|
/************************************************************************//**
|
||
|
Returns the map for the unit type. Creates it if needed. Returns nullptr if
|
||
|
'target_tile' is unreachable.
|
||
|
Compute the position of the unit described by 'param' at the target of
|
||
|
'pfrm' with a search of its own: a normal map from param->start_tile
|
||
|
whose get_action (pf_reverse_get_action()) attacks the target only,
|
||
|
iterated until the target is reached or the cost bound is.
|
||
|
This is the original reverse map computation. It is used when shared
|
||
|
searches cannot be used, and to check them with PF_REVERSE_DEBUG.
|
||
|
@param pfrm Reverse map whose target to reach
|
||
|
@param param Parameter of the unit
|
||
|
@param pos Filled with the position when the target is reached
|
||
|
@return Whether the target is reached
|
||
|
****************************************************************************/
|
||
|
static const struct pf_position *
|
||
|
pf_reverse_map_pos(struct pf_reverse_map *pfrm,
|
||
|
const struct pf_parameter *param)
|
||
|
static bool pf_reverse_map_pos_targeted(struct pf_reverse_map *pfrm,
|
||
|
const struct pf_parameter *param,
|
||
|
struct pf_position *pos)
|
||
|
{
|
||
|
struct pf_position *pos;
|
||
|
struct pf_map *pfm;
|
||
|
struct pf_parameter *copy;
|
||
|
struct tile *target_tile;
|
||
|
const struct pf_normal_node *lattice;
|
||
|
int max_cost;
|
||
|
/* Check if we already processed something similar. */
|
||
|
if (pf_pos_hash_lookup(pfrm->hash, param, &pos)) {
|
||
|
return pos;
|
||
|
}
|
||
|
/* We didn't. Build map and iterate. */
|
||
|
pfm = pf_normal_map_new(param);
|
||
|
lattice = PF_NORMAL_MAP(pfm)->lattice;
|
||
|
target_tile = pfrm->target_tile;
|
||
| ... | ... | |
|
if (lattice[tile_index(pfm->tile)].cost >= max_cost) {
|
||
|
break;
|
||
|
} else if (pfm->tile == target_tile) {
|
||
|
/* Found our position. Insert in hash, destroy map, and return. */
|
||
|
pos = fc_malloc(sizeof(*pos));
|
||
|
pf_normal_map_fill_position(PF_NORMAL_MAP(pfm), target_tile, pos);
|
||
|
copy = fc_malloc(sizeof(*copy));
|
||
|
*copy = *param;
|
||
|
pf_pos_hash_insert(pfrm->hash, copy, pos);
|
||
|
pf_map_destroy(pfm);
|
||
|
return pos;
|
||
|
return TRUE;
|
||
|
}
|
||
|
} while (pfm->iterate(pfm));
|
||
|
} else {
|
||
|
/* No limit for iteration. */
|
||
|
do {
|
||
|
if (pfm->tile == target_tile) {
|
||
|
/* Found our position. Insert in hash, destroy map, and return. */
|
||
|
pos = fc_malloc(sizeof(*pos));
|
||
|
pf_normal_map_fill_position(PF_NORMAL_MAP(pfm), target_tile, pos);
|
||
|
copy = fc_malloc(sizeof(*copy));
|
||
|
*copy = *param;
|
||
|
pf_pos_hash_insert(pfrm->hash, copy, pos);
|
||
|
pf_map_destroy(pfm);
|
||
|
return pos;
|
||
|
return TRUE;
|
||
|
}
|
||
|
} while (pfm->iterate(pfm));
|
||
|
}
|
||
|
pf_map_destroy(pfm);
|
||
|
/* Position not found. Let's insert nullptr as position to avoid to iterate
|
||
|
* the map again. */
|
||
|
copy = fc_malloc(sizeof(*copy));
|
||
|
*copy = *param;
|
||
|
pf_pos_hash_insert(pfrm->hash, copy, nullptr);
|
||
|
return FALSE;
|
||
|
}
|
||
|
/* ======================= Shared reverse searches ========================
|
||
|
Callers such as assess_danger() in ai/default build one reverse map
|
||
|
per (target, attacking player) and ask it about every unit of that
|
||
|
player, so the same search from the same unit is run once per target.
|
||
|
Instead, the searches below are run once per parameter key with no
|
||
|
target, and are shared by every reverse map until the world changes
|
||
|
(map_world_generation()) or, for a search that is not omniscient,
|
||
|
until the owner's tile knowledge changes (map_knowledge_generation()).
|
||
|
The position at each target is reconstructed from the shared search.
|
||
|
The shared search uses the reverse parameter with get_action attacking
|
||
|
nowhere (data = nullptr) and the same cost bound,
|
||
|
move_rate * (max_turns + 1). The targeted search's answer for target
|
||
|
T is then:
|
||
|
(a) T is the start tile: the start node, the same in both searches.
|
||
|
(b) T is unknown to the owner (not omniscient): pf_normal_node_init()
|
||
|
never calls get_action there, so the targeted graph is the shared
|
||
|
graph and the shared node at T is the answer.
|
||
|
(c) A non-civilian unit may not invade T: TB_IGNORE is decided before
|
||
|
get_action, so T is unreachable in both.
|
||
|
(d) Otherwise T is an ATTACK node (TB_DONT_LEAVE, enterable whatever
|
||
|
its move scope), entered from an expandable neighbour n at cost
|
||
|
cost(n) + MIN(attack_cost, pf_moves_left(cost(n))), where
|
||
|
attack_cost is move_rate for units whose attack takes all moves
|
||
|
or that attack suicidally, else SINGLE_MOVE (see
|
||
|
pf_normal_map_iterate()). The answer is the minimum over all n.
|
||
|
It is reachable only below the bound, because the targeted search
|
||
|
stops at the first popped cost that reaches it.
|
||
|
In (d) the shared graph lets paths run through T, where the targeted
|
||
|
graph makes T a sink. A neighbour whose shared shortest path avoids T
|
||
|
has the same cost in both graphs. If the shared path of neighbour n
|
||
|
passes through T, let m be the node it entered T from: m has the same
|
||
|
cost in both graphs, m is expandable (it expanded into T), and
|
||
|
cost(m) <= cost(n), because move costs are not negative. The
|
||
|
candidate cost(x) + MIN(a, pf_moves_left(cost(x))) equals
|
||
|
MIN(cost(x) + a, end of the turn of cost(x)) and does not decrease
|
||
|
with cost(x), so n never beats the candidate of m and the minimum is
|
||
|
unchanged.
|
||
|
Every node other than T must have the same properties in both
|
||
|
searches. They do, except that the PF_MS_CITY test of
|
||
|
pf_get_move_scope() reads the scope of whichever neighbour reached
|
||
|
the node first, which depends on the expansion order, and does so
|
||
|
only when two city tiles are adjacent. While any two cities on the
|
||
|
map are adjacent, shared searches are not used.
|
||
|
The key is (map, owner, omniscience, start tile, move rate, moves
|
||
|
left initially, unit type, max_turns), with the exact unit type
|
||
|
rather than the equivalence of pf_pos_hash_cmp(): each reverse map's
|
||
|
own hash still applies that equivalence and picks the representative
|
||
|
unit, so the representative is the one the targeted search would
|
||
|
have used. dir_to_here of a (d) position is that of the first
|
||
|
minimising neighbour, and may differ from the targeted search's
|
||
|
choice among equal costs. */
|
||
|
/* One popped node of a shared search, below the bound. */
|
||
|
struct pf_shared_node {
|
||
|
int tindex;
|
||
|
int cost;
|
||
|
unsigned dir_to_here : 4; /* As in pf_normal_node. */
|
||
|
bool expands; /* Whether the search expanded it. */
|
||
|
};
|
||
|
/* One shared search. */
|
||
|
struct pf_shared_entry {
|
||
|
/* Key. */
|
||
|
const struct civ_map *map;
|
||
|
const struct player *owner;
|
||
|
bool omniscience;
|
||
|
struct tile *start_tile;
|
||
|
int move_rate;
|
||
|
int moves_left_initially;
|
||
|
const struct unit_type *utype;
|
||
|
int max_turns;
|
||
|
/* map_knowledge_generation() of the owner when the search ran. Only
|
||
|
* checked when not omniscient: an omniscient search never reads tile
|
||
|
* knowledge. */
|
||
|
unsigned int knowledge_generation;
|
||
|
/* The popped nodes below the bound, sorted by tile index. */
|
||
|
int nnodes;
|
||
|
int cap;
|
||
|
struct pf_shared_node *nodes;
|
||
|
};
|
||
|
/* Shared searches, valid for world generation pf_shared_generation. */
|
||
|
static struct genhash *pf_shared_cache = nullptr;
|
||
|
static unsigned int pf_shared_generation = 0;
|
||
|
/* Whether any two cities are adjacent, if known in this generation. */
|
||
|
static bool pf_shared_adjacency_known = FALSE;
|
||
|
static bool pf_shared_adjacent_cities = FALSE;
|
||
|
#ifdef PF_REVERSE_DEBUG
|
||
|
static int pf_reverse_checks = 0;
|
||
|
static int pf_reverse_mismatches = 0;
|
||
|
static int pf_reverse_shared_searches = 0;
|
||
|
#endif /* PF_REVERSE_DEBUG */
|
||
|
/************************************************************************//**
|
||
|
Hash function of shared search keys.
|
||
|
****************************************************************************/
|
||
|
static genhash_val_t pf_shared_key_val(const void *vkey)
|
||
|
{
|
||
|
const struct pf_shared_entry *e = vkey;
|
||
|
return (genhash_val_t) (tile_index(e->start_tile)
|
||
|
+ ((genhash_val_t) e->move_rate << 16)
|
||
|
+ ((genhash_val_t) utype_index(e->utype) << 22)
|
||
|
+ ((genhash_val_t) player_index(e->owner) << 27)
|
||
|
+ ((genhash_val_t) e->max_turns << 9)
|
||
|
+ (e->omniscience ? 1u << 31 : 0));
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Comparison function of shared search keys.
|
||
|
****************************************************************************/
|
||
|
static bool pf_shared_key_comp(const void *va, const void *vb)
|
||
|
{
|
||
|
const struct pf_shared_entry *a = va, *b = vb;
|
||
|
return (a->map == b->map
|
||
|
&& a->owner == b->owner
|
||
|
&& a->omniscience == b->omniscience
|
||
|
&& a->start_tile == b->start_tile
|
||
|
&& a->move_rate == b->move_rate
|
||
|
&& a->moves_left_initially == b->moves_left_initially
|
||
|
&& a->utype == b->utype
|
||
|
&& a->max_turns == b->max_turns);
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Free a shared search.
|
||
|
****************************************************************************/
|
||
|
static void pf_shared_entry_free(void *ventry)
|
||
|
{
|
||
|
struct pf_shared_entry *e = ventry;
|
||
|
free(e->nodes);
|
||
|
free(e);
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Order shared search nodes by tile index, for qsort().
|
||
|
****************************************************************************/
|
||
|
static int pf_shared_node_cmp(const void *va, const void *vb)
|
||
|
{
|
||
|
const struct pf_shared_node *a = va, *b = vb;
|
||
|
return (a->tindex > b->tindex) - (a->tindex < b->tindex);
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Drop every shared search if the world has changed since they were run.
|
||
|
@return Whether shared searches may be used now
|
||
|
****************************************************************************/
|
||
|
static bool pf_shared_cache_sync(void)
|
||
|
{
|
||
|
if (pf_shared_cache == nullptr
|
||
|
|| pf_shared_generation != map_world_generation()) {
|
||
|
if (pf_shared_cache != nullptr) {
|
||
|
genhash_destroy(pf_shared_cache);
|
||
|
}
|
||
|
pf_shared_cache = genhash_new_full(pf_shared_key_val,
|
||
|
pf_shared_key_comp,
|
||
|
nullptr, nullptr,
|
||
|
nullptr, pf_shared_entry_free);
|
||
|
pf_shared_generation = map_world_generation();
|
||
|
pf_shared_adjacency_known = FALSE;
|
||
|
}
|
||
|
if (!pf_shared_adjacency_known) {
|
||
|
pf_shared_adjacency_known = TRUE;
|
||
|
pf_shared_adjacent_cities = FALSE;
|
||
|
players_iterate(pplayer) {
|
||
|
city_list_iterate(pplayer->cities, pcity) {
|
||
|
adjc_iterate(&(wld.map), city_tile(pcity), ntile) {
|
||
|
if (tile_city(ntile) != nullptr) {
|
||
|
pf_shared_adjacent_cities = TRUE;
|
||
|
}
|
||
|
} adjc_iterate_end;
|
||
|
} city_list_iterate_end;
|
||
|
} players_iterate_end;
|
||
|
}
|
||
|
return !pf_shared_adjacent_cities;
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Run the shared search for 'param' and 'max_turns' into 'e', replacing
|
||
|
what it held.
|
||
|
****************************************************************************/
|
||
|
static void pf_shared_entry_fill(struct pf_shared_entry *e,
|
||
|
const struct pf_parameter *param,
|
||
|
int max_turns)
|
||
|
{
|
||
|
struct pf_parameter shared;
|
||
|
struct pf_map *pfm;
|
||
|
const struct pf_normal_node *lattice;
|
||
|
int max_cost;
|
||
|
e->nnodes = 0;
|
||
|
if (!param->omniscience) {
|
||
|
e->knowledge_generation = map_knowledge_generation(param->owner);
|
||
|
}
|
||
|
shared = *param;
|
||
|
/* pf_reverse_get_action() attacks nowhere. */
|
||
|
shared.data = nullptr;
|
||
|
max_cost = param->move_rate * (max_turns + 1);
|
||
|
pfm = pf_normal_map_new(&shared);
|
||
|
lattice = PF_NORMAL_MAP(pfm)->lattice;
|
||
|
#ifdef PF_REVERSE_DEBUG
|
||
|
pf_reverse_shared_searches++;
|
||
|
#endif /* PF_REVERSE_DEBUG */
|
||
|
do {
|
||
|
int tindex = tile_index(pfm->tile);
|
||
|
const struct pf_normal_node *node = lattice + tindex;
|
||
|
struct pf_shared_node *sn;
|
||
|
if (node->cost >= max_cost) {
|
||
|
break;
|
||
|
}
|
||
|
if (e->nnodes == e->cap) {
|
||
|
e->cap *= 2;
|
||
|
e->nodes = fc_realloc(e->nodes, e->cap * sizeof(*e->nodes));
|
||
|
}
|
||
|
sn = e->nodes + e->nnodes++;
|
||
|
sn->tindex = tindex;
|
||
|
sn->cost = node->cost;
|
||
|
sn->dir_to_here = node->dir_to_here;
|
||
|
/* The test at the head of pf_normal_map_iterate(). There is no
|
||
|
* get_zoc callback, so every node is ZOC_MINE. */
|
||
|
sn->expands = (node->behavior != TB_DONT_LEAVE
|
||
|
&& node->move_scope != PF_MS_NONE
|
||
|
&& (shared.move_rate > 0 || node->cost < 0));
|
||
|
} while (pfm->iterate(pfm));
|
||
|
pf_map_destroy(pfm);
|
||
|
qsort(e->nodes, e->nnodes, sizeof(*e->nodes), pf_shared_node_cmp);
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Find or run the shared search for 'param' and 'max_turns'. A search
|
||
|
that read tile knowledge (not omniscient) is run again if the owner's
|
||
|
knowledge has changed since; everything else it read is covered by
|
||
|
the world generation (pf_shared_cache_sync()).
|
||
|
****************************************************************************/
|
||
|
static const struct pf_shared_entry *
|
||
|
pf_shared_entry_get(const struct pf_parameter *param, int max_turns)
|
||
|
{
|
||
|
struct pf_shared_entry key, *e;
|
||
|
key.map = param->map;
|
||
|
key.owner = param->owner;
|
||
|
key.omniscience = param->omniscience;
|
||
|
key.start_tile = param->start_tile;
|
||
|
key.move_rate = param->move_rate;
|
||
|
key.moves_left_initially = param->moves_left_initially;
|
||
|
key.utype = param->utype;
|
||
|
key.max_turns = max_turns;
|
||
|
if (genhash_lookup(pf_shared_cache, &key, (void **) &e)) {
|
||
|
if (!e->omniscience
|
||
|
&& e->knowledge_generation
|
||
|
!= map_knowledge_generation(e->owner)) {
|
||
|
pf_shared_entry_fill(e, param, max_turns);
|
||
|
}
|
||
|
return e;
|
||
|
}
|
||
|
e = fc_malloc(sizeof(*e));
|
||
|
*e = key;
|
||
|
e->knowledge_generation = 0;
|
||
|
e->cap = 64;
|
||
|
e->nodes = fc_malloc(e->cap * sizeof(*e->nodes));
|
||
|
pf_shared_entry_fill(e, param, max_turns);
|
||
|
genhash_insert(pf_shared_cache, e, e);
|
||
|
return e;
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Return the node of shared search 'e' at 'ptile', or nullptr if the
|
||
|
search did not pop it below the bound.
|
||
|
****************************************************************************/
|
||
|
static const struct pf_shared_node *
|
||
|
pf_shared_node_get(const struct pf_shared_entry *e,
|
||
|
const struct tile *ptile)
|
||
|
{
|
||
|
int tindex = tile_index(ptile);
|
||
|
int lo = 0, hi = e->nnodes - 1;
|
||
|
while (lo <= hi) {
|
||
|
int mid = (lo + hi) / 2;
|
||
|
if (e->nodes[mid].tindex == tindex) {
|
||
|
return e->nodes + mid;
|
||
|
} else if (e->nodes[mid].tindex < tindex) {
|
||
|
lo = mid + 1;
|
||
|
} else {
|
||
|
hi = mid - 1;
|
||
|
}
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Fill 'pos' as pf_normal_map_fill_position() does for a node at 'ptile'
|
||
|
of raw cost 'cost'.
|
||
|
****************************************************************************/
|
||
|
static void pf_shared_fill_position(const struct pf_parameter *param,
|
||
|
struct tile *ptile, int cost,
|
||
|
int dir_to_here,
|
||
|
struct pf_position *pos)
|
||
|
{
|
||
|
pos->tile = ptile;
|
||
|
pos->total_EC = 0;
|
||
|
pos->total_MC = (cost - pf_move_rate(param)
|
||
|
+ pf_moves_left_initially(param));
|
||
|
pos->turn = pf_turns(param, cost);
|
||
|
pos->moves_left = pf_moves_left(param, cost);
|
||
|
pos->fuel_left = 1;
|
||
|
pos->dir_to_here = dir_to_here;
|
||
|
pos->dir_to_next_pos = direction8_invalid();
|
||
|
if (cost > 0) {
|
||
|
pf_finalize_position(param, pos);
|
||
|
}
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Compute the position of the unit described by 'param' at the target of
|
||
|
'pfrm' from the shared search, as the targeted search would (cases (a)
|
||
|
to (d) above).
|
||
|
@param pfrm Reverse map whose target to reach
|
||
|
@param param Parameter of the unit
|
||
|
@param pos Filled with the position when the target is reached
|
||
|
@return Whether the target is reached
|
||
|
****************************************************************************/
|
||
|
static bool pf_reverse_map_pos_shared(struct pf_reverse_map *pfrm,
|
||
|
const struct pf_parameter *param,
|
||
|
struct pf_position *pos)
|
||
|
{
|
||
|
const struct pf_shared_entry *e = pf_shared_entry_get(param,
|
||
|
pfrm->max_turns);
|
||
|
struct tile *target = pfrm->target_tile;
|
||
|
int max_cost = param->move_rate * (pfrm->max_turns + 1);
|
||
|
const struct pf_shared_node *n;
|
||
|
int attack_cost;
|
||
|
int best = -1;
|
||
|
int best_dir = direction8_invalid();
|
||
|
if (target == param->start_tile
|
||
|
|| (!param->omniscience
|
||
|
&& TILE_UNKNOWN == tile_get_known(target, param->owner))) {
|
||
|
/* Cases (a) and (b). */
|
||
|
n = pf_shared_node_get(e, target);
|
||
|
if (n == nullptr) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
pf_shared_fill_position(param, target, n->cost, n->dir_to_here, pos);
|
||
|
return TRUE;
|
||
|
}
|
||
|
if (!utype_has_flag(param->utype, UTYF_CIVILIAN)
|
||
|
&& !player_can_invade_tile(param->owner, target)) {
|
||
|
/* Case (c). */
|
||
|
return FALSE;
|
||
|
}
|
||
|
/* Case (d). */
|
||
|
if (utype_action_takes_all_mp(param->utype,
|
||
|
action_by_number(ACTION_ATTACK))
|
||
|
|| utype_can_do_action(param->utype, ACTION_SUICIDE_ATTACK)) {
|
||
|
attack_cost = param->move_rate;
|
||
|
} else {
|
||
|
attack_cost = SINGLE_MOVE;
|
||
|
}
|
||
|
adjc_dir_iterate(param->map, target, ntile, dir) {
|
||
|
int cost;
|
||
|
n = pf_shared_node_get(e, ntile);
|
||
|
if (n == nullptr || !n->expands) {
|
||
|
continue;
|
||
|
}
|
||
|
cost = n->cost
|
||
|
+ pf_normal_map_adjust_cost(attack_cost,
|
||
|
pf_moves_left(param, n->cost));
|
||
|
if (best < 0 || cost < best) {
|
||
|
best = cost;
|
||
|
best_dir = opposite_direction(dir);
|
||
|
}
|
||
|
} adjc_dir_iterate_end;
|
||
|
if (best < 0 || best >= max_cost) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
pf_shared_fill_position(param, target, best, best_dir, pos);
|
||
|
return TRUE;
|
||
|
}
|
||
|
#ifdef PF_REVERSE_DEBUG
|
||
|
/************************************************************************//**
|
||
|
Compute the position with a search of its own, and log the difference
|
||
|
if it disagrees with the position from the shared search.
|
||
|
****************************************************************************/
|
||
|
static void pf_reverse_check_position(struct pf_reverse_map *pfrm,
|
||
|
const struct pf_parameter *param,
|
||
|
bool found,
|
||
|
const struct pf_position *pos)
|
||
|
{
|
||
|
struct pf_position opos;
|
||
|
bool ofound = pf_reverse_map_pos_targeted(pfrm, param, &opos);
|
||
|
pf_reverse_checks++;
|
||
|
if (ofound != found
|
||
|
|| (found && (opos.total_MC != pos->total_MC
|
||
|
|| opos.turn != pos->turn
|
||
|
|| opos.moves_left != pos->moves_left
|
||
|
|| opos.total_EC != pos->total_EC
|
||
|
|| opos.fuel_left != pos->fuel_left))) {
|
||
|
pf_reverse_mismatches++;
|
||
|
log_error("pf-reverse-check mismatch start=(%d,%d) target=(%d,%d)"
|
||
|
" utype=%s owner=%s omniscient=%d max_turns=%d"
|
||
|
" shared=%d/%d/%d/%d targeted=%d/%d/%d/%d",
|
||
|
TILE_XY(param->start_tile), TILE_XY(pfrm->target_tile),
|
||
|
utype_rule_name(param->utype), player_name(param->owner),
|
||
|
param->omniscience ? 1 : 0, pfrm->max_turns,
|
||
|
found ? 1 : 0, found ? pos->total_MC : -1,
|
||
|
found ? pos->turn : -1, found ? pos->moves_left : -1,
|
||
|
ofound ? 1 : 0, ofound ? opos.total_MC : -1,
|
||
|
ofound ? opos.turn : -1, ofound ? opos.moves_left : -1);
|
||
|
}
|
||
|
if (pf_reverse_checks % 100000 == 0) {
|
||
|
log_normal("pf-reverse-check checks=%d mismatches=%d"
|
||
|
" shared_searches=%d", pf_reverse_checks,
|
||
|
pf_reverse_mismatches, pf_reverse_shared_searches);
|
||
|
}
|
||
|
}
|
||
|
#endif /* PF_REVERSE_DEBUG */
|
||
|
/************************************************************************//**
|
||
|
Returns the position of the unit described by 'param' at the target of
|
||
|
'pfrm', computing it if needed. Returns nullptr if the target is
|
||
|
unreachable.
|
||
|
****************************************************************************/
|
||
|
static const struct pf_position *
|
||
|
pf_reverse_map_pos(struct pf_reverse_map *pfrm,
|
||
|
const struct pf_parameter *param)
|
||
|
{
|
||
|
struct pf_position *pos;
|
||
|
struct pf_position found_pos;
|
||
|
struct pf_parameter *copy;
|
||
|
bool found;
|
||
|
/* Check if we already processed something similar. */
|
||
|
if (pf_pos_hash_lookup(pfrm->hash, param, &pos)) {
|
||
|
return pos;
|
||
|
}
|
||
|
if (pfrm->max_turns >= 0 && pf_shared_cache_sync()) {
|
||
|
found = pf_reverse_map_pos_shared(pfrm, param, &found_pos);
|
||
|
#ifdef PF_REVERSE_DEBUG
|
||
|
pf_reverse_check_position(pfrm, param, found, &found_pos);
|
||
|
#endif /* PF_REVERSE_DEBUG */
|
||
|
} else {
|
||
|
found = pf_reverse_map_pos_targeted(pfrm, param, &found_pos);
|
||
|
}
|
||
|
copy = fc_malloc(sizeof(*copy));
|
||
|
*copy = *param;
|
||
|
if (found) {
|
||
|
pos = fc_malloc(sizeof(*pos));
|
||
|
*pos = found_pos;
|
||
|
} else {
|
||
|
/* Position not found. Let's insert nullptr as position to avoid to
|
||
|
* iterate the map again. */
|
||
|
pos = nullptr;
|
||
|
}
|
||
|
pf_pos_hash_insert(pfrm->hash, copy, pos);
|
||
|
return pos;
|
||
|
}
|
||
|
/************************************************************************//**
|
||
|
Returns the position for the unit. Creates it if needed. Returns nullptr if
|
||
|
'target_tile' is unreachable.
|
||
| common/map.c | ||
|---|---|---|
|
* a recomputation. */
|
||
|
static unsigned int world_generation = 0;
|
||
|
/* The last generation issued. A bump always issues a new value, so a
|
||
|
* value that world_generation returns to (see
|
||
|
* map_world_tile_owner_changed()) is never reissued for a different
|
||
|
* world. */
|
||
|
static unsigned int world_generation_issued = 0;
|
||
|
/* The last tile owner change, while it is the last bump. */
|
||
|
static struct {
|
||
|
const struct tile *ptile;
|
||
|
const struct player *old_owner;
|
||
|
unsigned int before; /* world_generation before the change. */
|
||
|
unsigned int after; /* world_generation the change issued. */
|
||
|
} world_owner_undo = { nullptr, nullptr, 0, 0 };
|
||
|
/* Per player slot: changes whenever the set of tiles the player knows
|
||
|
* changes. Pathfinding reads tile knowledge only when the parameter is
|
||
|
* not omniscient, so caches of such results compare this as well as
|
||
| ... | ... | |
|
***********************************************************************/
|
||
|
void map_world_changed(void)
|
||
|
{
|
||
|
world_generation++;
|
||
|
world_generation_issued++;
|
||
|
world_generation = world_generation_issued;
|
||
|
}
|
||
|
/*******************************************************************//**
|
||
| ... | ... | |
|
}
|
||
|
}
|
||
|
/*******************************************************************//**
|
||
|
The owner of ptile is changing from old_owner to new_owner.
|
||
|
A change that exactly undoes the previous bump (same tile, back to
|
||
|
the owner it had, nothing else bumped in between) returns the world
|
||
|
to the state it had before that bump, so the world generation returns
|
||
|
to that state's value and caches made in that state stay valid.
|
||
|
City site evaluation in ai/default (cityresult_fill()) sets the owner
|
||
|
of a candidate tile to the evaluating player and restores it around
|
||
|
every site outside the player's borders; without this, each such
|
||
|
evaluation would invalidate every cached search twice. Pathfinding
|
||
|
reads the owner, not the claimer.
|
||
|
@param ptile Tile whose owner changes
|
||
|
@param old_owner Owner before the change
|
||
|
@param new_owner Owner after the change
|
||
|
***********************************************************************/
|
||
|
void map_world_tile_owner_changed(const struct tile *ptile,
|
||
|
const struct player *old_owner,
|
||
|
const struct player *new_owner)
|
||
|
{
|
||
|
if (!map_world_tile_is_real(ptile)) {
|
||
|
return;
|
||
|
}
|
||
|
if (world_owner_undo.ptile == ptile
|
||
|
&& world_owner_undo.old_owner == new_owner
|
||
|
&& world_generation == world_owner_undo.after) {
|
||
|
world_generation = world_owner_undo.before;
|
||
|
world_owner_undo.ptile = nullptr;
|
||
|
return;
|
||
|
}
|
||
|
world_owner_undo.ptile = ptile;
|
||
|
world_owner_undo.old_owner = old_owner;
|
||
|
world_owner_undo.before = world_generation;
|
||
|
map_world_changed();
|
||
|
world_owner_undo.after = world_generation;
|
||
|
}
|
||
|
/*******************************************************************//**
|
||
|
The tile, orders or presence of punit is about to change.
|
||
| common/map.h | ||
|---|---|---|
|
void map_world_changed(void);
|
||
|
void map_world_tile_changed(const struct tile *ptile);
|
||
|
void map_world_tile_owner_changed(const struct tile *ptile,
|
||
|
const struct player *old_owner,
|
||
|
const struct player *new_owner);
|
||
|
void map_world_unit_changed(const struct unit *punit);
|
||
|
unsigned int map_world_generation(void);
|
||
|
void map_knowledge_changed(const struct player *pplayer);
|
||
| common/tile.c | ||
|---|---|---|
|
/* City tiles are always owned by the city owner. */
|
||
|
|| (tile_city(ptile) != nullptr || ptile->owner != nullptr)) {
|
||
|
if (ptile->owner != pplayer) {
|
||
|
map_world_tile_changed(ptile);
|
||
|
map_world_tile_owner_changed(ptile, ptile->owner, pplayer);
|
||
|
}
|
||
|
ptile->owner = pplayer;
|
||
|
ptile->claimer = claimer;
|
||