/* * static char *rcsid_swap_c = * "$Id: swap.c 11578 2009-02-23 22:02:27Z lalo $"; */ /* CrossFire, A Multiplayer game for X-windows Copyright (C) 2002 Mark Wedel & Crossfire Development Team Copyright (C) 1992 Frank Tore Johansen 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., 675 Mass Ave, Cambridge, MA 02139, USA. The author can be reached via e-mail to crossfire-devel@real-time.com */ /** * @file * All those functions deal with swapping maps to disk to claim memory space. */ #include #ifndef __CEXTRACT__ #include #endif #include /** * Writes out information on all the temporary maps. It is called by * swap_map(). * * Will LOG() in case of error. */ static void write_map_log(void) { FILE *fp; mapstruct *map; char buf[MAX_BUF]; long current_time = time(NULL); snprintf(buf, sizeof(buf), "%s/temp.maps", settings.localdir); if (!(fp = fopen(buf, "w"))) { LOG(llevError, "Could not open %s for writing\n", buf); return; } for (map = first_map; map != NULL; map = map->next) { /* If tmpname is null, it is probably a unique player map, * so don't save information on it. */ if (map->in_memory != MAP_IN_MEMORY && (map->tmpname != NULL) && (strncmp(map->path, "/random", 7))) { /* the 0 written out is a leftover from the lock number for * unique items and second one is from encounter maps. * Keep using it so that old temp files continue * to work. */ fprintf(fp, "%s:%s:%ld:0:0:%d:0:%d\n", map->path, map->tmpname, (map->reset_time == -1 ? -1 : map->reset_time-current_time), map->difficulty, map->darkness); } } fclose(fp); } /** * Reads temporary maps information from disk. * * Will LOG() in case of error. */ void read_map_log(void) { FILE *fp; mapstruct *map; char buf[MAX_BUF], *cp, *cp1; int do_los, darkness, lock; long sec = seconds(); snprintf(buf, sizeof(buf), "%s/temp.maps", settings.localdir); if (!(fp = fopen(buf, "r"))) { LOG(llevDebug, "Could not open %s for reading\n", buf); return; } while (fgets(buf, MAX_BUF, fp) != NULL) { map = get_linked_map(); /* scanf doesn't work all that great on strings, so we break * out that manually. strdup is used for tmpname, since other * routines will try to free that pointer. */ cp = strchr(buf, ':'); *cp++ = '\0'; strcpy(map->path, buf); cp1 = strchr(cp, ':'); *cp1++ = '\0'; map->tmpname = strdup_local(cp); /* Lock is left over from the lock items - we just toss it now. * We use it twice - second one is from encounter, but as we * don't care about the value, this works fine */ sscanf(cp1, "%u:%d:%d:%hu:%d:%d\n", &map->reset_time, &lock, &lock, &map->difficulty, &do_los, &darkness); map->in_memory = MAP_SWAPPED; map->darkness = darkness; map->timeout = 0; /* When the reset time is saved out, it is adjusted so that * the current time is subtracted (thus, it is saved as number * of seconds from current time that it should reset). We need * to add in the current seconds for this to work right. * On metalforge, strange behavior was observed with really high * reset times - I don't know how they got to that state, * but easy enough to do some sanity checking here. */ map->reset_time += sec; if (map->reset_time > (sec+MAP_MAXRESET)) map->reset_time = 0; } fclose(fp); } /** * Swaps a map to disk. * * After calling this function, the map will be: * - swapped to disk * - kept in memory in case of save error * * @param map * map to swap. * @return * one of @ref SAVE_ERROR_xxx "SAVE_ERROR_xxx". */ int swap_map(mapstruct *map) { player *pl; int res; if (map->in_memory != MAP_IN_MEMORY) { LOG(llevError, "Tried to swap out map which was not in memory.\n"); return SAVE_ERROR_NOT_IN_MEMORY; } for (pl = first_player; pl != NULL; pl = pl->next) if (pl->ob == NULL || (!(QUERY_FLAG(pl->ob, FLAG_REMOVED)) && pl->ob->map == map)) break; if (pl != NULL) { LOG(llevDebug, "Wanted to swap out map with player.\n"); return SAVE_ERROR_PLAYER; } remove_all_pets(); /* Give them a chance to follow */ /* Update the reset time. Only do this is STAND_STILL is not set */ if (!map->fixed_resettime) set_map_reset_time(map); /* If it is immediate reset time, don't bother saving it - just get * rid of it right away. */ if (map->reset_time <= seconds()) { mapstruct *oldmap = map; LOG(llevDebug, "Resetting map %s.\n", map->path); /* Lauwenmark : Here we handle the MAPRESET global event */ execute_global_event(EVENT_MAPRESET, map); map = map->next; delete_map(oldmap); return SAVE_ERROR_OK; } if ((res = save_map(map, SAVE_MODE_NORMAL)) < 0) { LOG(llevError, "Failed to swap map %s.\n", map->path); /* This is sufficiently critical to mandate to warn all DMs. */ draw_ext_info_format(NDI_ALL_DMS|NDI_UNIQUE|NDI_RED, -1, NULL, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_LOADSAVE, "Failed to swap map %s!", NULL, map->path); /* Map is *not *swapped. */ map->in_memory = MAP_IN_MEMORY; return res; } free_map(map); if (settings.recycle_tmp_maps == TRUE) write_map_log(); return SAVE_ERROR_OK; } /** * Finds maps in memory to reset. * * @todo * The check for MAX_OBJECTS_LWM is wrongly placed, and should be moved elsewhere. */ void check_active_maps(void) { mapstruct *map, *next; for (map = first_map; map != NULL; map = next) { next = map->next; if (map->in_memory != MAP_IN_MEMORY) continue; if (!map->timeout) continue; if (--(map->timeout) > 0) continue; /* If LWM is set, we only swap maps out when we run out of objects */ #ifndef MAX_OBJECTS_LWM swap_map(map); #endif } } /** * Returns the map with the lowest timeout variable (not 0). * * @param except_level * path of map to ignore for reset. Musn't be NULL. * @return * map, or NULL if no map is ready for reset. */ static mapstruct *map_least_timeout(const char *except_level) { mapstruct *map, *chosen = NULL; int timeout = MAP_MAXTIMEOUT+1; for (map = first_map; map != NULL; map = map->next) if (map->in_memory == MAP_IN_MEMORY && strcmp(map->path, except_level) && map->timeout && map->timeout < timeout) chosen = map, timeout = map->timeout; return chosen; } /** * Tries to swap out maps which are still in memory, because * of MAP_TIMEOUT until used objects is below MAX_OBJECTS or there are * no more maps to swap. * * @param except_level * path of map to ignore for reset. Musn't be NULL. */ void swap_below_max(const char *except_level) { mapstruct *map; if (nrofallocobjects-nroffreeobjects < MAX_OBJECTS) return; for (;;) { #ifdef MAX_OBJECTS_LWM if (nrofallocobjects-nroffreeobjects < MAX_OBJECTS_LWM) return; #else if (nrofallocobjects-nroffreeobjects < MAX_OBJECTS) return; #endif if ((map = map_least_timeout(except_level)) == NULL) return; LOG(llevDebug, "Trying to swap out %s before its time.\n", map->path); map->timeout = 0; swap_map(map); } } /** * Returns the count of players on a map, calculated from player list. * * @param m * map we want the count of players on. * @param show_all * if true, show everyone. If not, don't show hidden players (dms) * @return * player count. * * @todo * this doesn't take into account transports. Should be removed when mapstruct::players is valid. */ int players_on_map(mapstruct *m, int show_all) { player *pl; int nr = 0; for (pl = first_player; pl != NULL; pl = pl->next) if (pl->ob != NULL && !QUERY_FLAG(pl->ob, FLAG_REMOVED) && pl->ob->map == m && (show_all || !pl->hidden)) nr++; return nr; } /** * Removes tmp-files of maps which are going to be reset next time * they are visited. * This is very useful if the tmp-disk is very full. */ void flush_old_maps(void) { mapstruct *m, *oldmap; long sec; sec = seconds(); m = first_map; while (m) { /* There can be cases (ie death) where a player leaves a map and the timeout * is not set so it isn't swapped out. */ if ((m->in_memory == MAP_IN_MEMORY) && (m->timeout == 0) && !players_on_map(m, TRUE)) { set_map_timeout(m); } /* per player unique maps are never really reset. However, we do want * to perdiocially remove the entries in the list of active maps - this * generates a cleaner listing if a player issues the map commands, and * keeping all those swapped out per player unique maps also has some * memory and cpu consumption. * We do the cleanup here because there are lots of places that call * swap map, and doing it within swap map may cause problems as * the functions calling it may not expect the map list to change * underneath them. */ if ((m->unique || m->is_template) && m->in_memory == MAP_SWAPPED) { LOG(llevDebug, "Resetting map %s.\n", m->path); oldmap = m; m = m->next; delete_map(oldmap); } else if (m->in_memory != MAP_SWAPPED || m->tmpname == NULL || sec < m->reset_time) { m = m->next; } else { LOG(llevDebug, "Resetting map %s.\n", m->path); /* Lauwenmark : Here we handle the MAPRESET global event */ execute_global_event(EVENT_MAPRESET, m); clean_tmp_map(m); oldmap = m; m = m->next; delete_map(oldmap); } } }