empserver/src/lib/as/as_cache.c
Markus Armbruster 2797e58c20 A* path and neighbor cache performance statistics
as_clear_cachepath() now prints cache hits, misses, number of entries,
and memory use to stderr, when compiled with AS_STATS defined.
2011-04-12 21:40:08 +02:00

238 lines
6.6 KiB
C

/*
* Empire - A multi-player, client/server Internet based war game.
* Copyright (C) 1986-2010, Dave Pare, Jeff Bailey, Thomas Ruschak,
* Ken Stevens, Steve McClure
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---
*
* See files README, COPYING and CREDITS in the root of the source
* tree for related information and legal notices. It is expected
* that future projects/authors will amend these files as needed.
*
* ---
*
* as_cache.c: Routines used to create/delete caches of A* paths.
*
* Known contributors to this file:
* Steve McClure, 1998
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include "as.h"
#include "optlist.h"
/* The way this works is interesting. :) */
/* We keep a pointer to a list of pointers. The index into this list
* is the y coordinate of the from sector. This member points to a list
* of from sectors on that y coordinate. So, we march that list checking
* the x value to find the from x,y we want. */
/* Once we find the from x,y, that node has the same type of pointer to
* a list of pointers. The index into this list is the y coordinate of
* the to sector. This member points to a list of to sectors on that y
* coordinate. So, we march that list checking the x value to find the
* to x,y we want. */
/* These lists are dynamically created since the world is dynamically sized. */
/* See, I told you it was interesting. :) */
static struct as_frompath **fromhead = (struct as_frompath **)0;
/* Note that we only want to cache during updates. Other times, it
* probably doesn't make much sense, but can be done. */
static int as_cachepath_on = 0; /* Default to off */
#ifdef AS_STATS
unsigned as_cache_tries, as_cache_hits;
#define as_cache_try() ((void)as_cache_tries++)
#define as_cache_hit() ((void)as_cache_hits++)
#else
#define as_cache_try() ((void)0)
#define as_cache_hit() ((void)0)
#endif
void
as_enable_cachepath(void)
{
as_cachepath_on = 1;
}
void
as_disable_cachepath(void)
{
as_cachepath_on = 0;
}
/* Note we want these to be as fast as possible */
void
as_add_cachepath(struct as_data *adp)
{
struct as_frompath *from;
struct as_topath *to = (struct as_topath *)0;
struct as_node *np;
/* Don't do anything if we aren't cacheing these */
if (as_cachepath_on == 0)
return;
/* Note we will only allocate this once. Afterwards, we just keep
* zeroing it since it's rather small and we don't need to re-allocate
* each time. */
if (fromhead == NULL) {
fromhead = calloc(1, sizeof(struct as_frompath *) * WORLD_Y);
if (fromhead == NULL)
return;
}
np = adp->head->np;
for (from = fromhead[adp->from.y]; from; from = from->next)
if (from->x == adp->from.x)
break;
if (from) {
for (to = from->tolist[np->c.y]; to; to = to->next) {
if (to->x == np->c.x) {
/* It is already here! Don't bother adding it again */
return;
}
}
} else {
/* We must make a new one of these */
from = malloc(sizeof(struct as_frompath));
if (from == NULL)
return;
/* And set some stuff */
from->x = adp->from.x;
/* Here we malloc a whole bunch of tolist pointers. */
from->tolist = calloc(1, sizeof(struct as_topath *) * WORLD_Y);
/* Now, add from to the global list */
from->next = fromhead[adp->from.y];
fromhead[adp->from.y] = from;
}
if (!to) {
/* We must make a new one */
to = malloc(sizeof(struct as_topath));
/* We can't, sorry */
if (to == NULL)
return;
/* Now set some stuff */
to->x = np->c.x;
/* Now add it to the list we are in */
to->next = from->tolist[np->c.y];
from->tolist[np->c.y] = to;
}
/* Now, make the path */
as_makepath(adp);
/* Now, take the path */
to->path = adp->path;
/* And clear the path in the adp */
adp->path = NULL;
}
void
as_clear_cachepath(void)
{
struct as_frompath *from, *from2;
struct as_topath *to, *to2;
int i, j;
#ifdef AS_STATS
size_t index_sz = 0;
unsigned index_nb = 0, paths_nb = 0, paths = 0;
#define stats_index(sz) ((void)(index_sz += (sz), index_nb++))
#else
#define stats_index(sz) ((void)0)
#endif
/* Cache not used yet :) */
if (fromhead == NULL)
return;
for (j = 0; j < WORLD_Y; j++) {
for (from = fromhead[j]; from; from = from2) {
for (i = 0; i < WORLD_Y; i++) {
for (to = from->tolist[i]; to; to = to2) {
to2 = to->next;
/* Free this path */
#ifdef AS_STATS
{
struct as_path *pp;
for (pp = to->path; pp; pp = pp->next)
paths_nb++;
paths++;
}
#endif
as_free_path(to->path);
/* Free this node */
free(to);
stats_index(sizeof(*to));
}
}
/* Now, free the list of lists */
free(from->tolist);
stats_index(WORLD_Y * sizeof(*from->tolist));
/* Save the next pointer */
from2 = from->next;
/* now, free this from node */
free(from);
stats_index(sizeof(*from));
}
}
/* Note we don't free the fromhead here, we just zero it. That way,
we can use it next time without mallocing int */
memset(fromhead, 0, (sizeof(struct as_frompath *) * WORLD_Y));
stats_index(WORLD_Y * sizeof(*fromhead));
#ifdef AS_STATS
fprintf(stderr, "as_cache %u searches, %u hits, %u entries,"
" index %zu bytes %u blocks, paths %zu bytes %u blocks\n",
as_cache_tries, as_cache_hits,
paths,
index_sz, index_nb,
paths_nb * sizeof(struct as_path), paths_nb);
as_cache_hits = as_cache_tries = 0;
#endif
}
struct as_path *
as_find_cachepath(coord fx, coord fy, coord tx, coord ty)
{
struct as_frompath *from;
struct as_topath *to;
as_cache_try();
/* Is the cache on? if not, return NULL */
if (as_cachepath_on == 0)
return NULL;
/* Do we have any cached? */
if (fromhead == NULL)
return NULL;
/* Yes! */
for (from = fromhead[fy]; from; from = from->next) {
if (from->x == fx) {
for (to = from->tolist[ty]; to; to = to->next) {
if (to->x == tx) {
as_cache_hit();
return to->path;
}
}
}
}
return NULL;
}