/*! \file player_c.cpp * Player cache routines. * * $Id: player_c.cpp,v 1.17 2006/01/07 09:07:15 sdennis Exp $ * * Frequenty-used items which appear on every object generally find a home in * the db[] structure managed in db.cpp. However, there are a few items * related only to players which are still accessed frequently enough that * they should be cached. These items are money, current number of queued * commands, and the limit on the number of queued commands. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "attrs.h" /*! \brief structure to hold cached data for player-type objects. */ typedef struct player_cache { dbref player; int money; int queue; int qmax; int cflags; struct player_cache *next; } PCACHE; /*! \brief Hash Table which maps player dbref to PCACHE entry. */ static CHashTable pcache_htab; /*! \brief The head of a singly-linked list of all PCACHE entries. */ static PCACHE *pcache_head; #define PF_REF 0x0002 #define PF_MONEY_CH 0x0004 /*! \brief Initializes the player cache. * * This is called once to initialize the player cache and supporting * data structures: Player cache structures are pooled, the Hash Table * initializes itself, and the singly-linked list is started. * * \return None. */ void pcache_init(void) { pool_init(POOL_PCACHE, sizeof(PCACHE)); pcache_head = NULL; } /*! \brief Updates player cache items from the database. * * The Money and QueueMax attributes are used to initialize the corresponding * items in the player cache. If a Money attribute does not exist for some * strange reason, it it initialized to zero and marked as dirty. If a * QueueMax attribute doesn't exist or is negative, then the game will * choose a reasonable limit later in QueueMax(). * * \param player player object to begin caching. * \param pp pointer to PCACHE structure. * \return None. */ static void pcache_reload1(dbref player, PCACHE *pp) { const char *cp = atr_get_raw(player, A_MONEY); if (cp && *cp) { pp->money = mux_atol(cp); } else { pp->cflags |= PF_MONEY_CH; pp->money = 0; } int m = -1; cp = atr_get_raw(player, A_QUEUEMAX); if (cp && *cp) { m = mux_atol(cp); if (m < 0) { m = -1; } } pp->qmax = m; } /*! \brief Returns a player's cache record. * * Whether created from scratch or found in the cache, pcache_find() always * returns a valid player cache record for the requested player object dbref. * This function uses Hash Table access primarily, but it maintains the * singly-linked list as well. * * \param player player object dbref. * \return Pointer to new or existing player cache record. */ static PCACHE *pcache_find(dbref player) { PCACHE *pp = (PCACHE *)hashfindLEN(&player, sizeof(player), &pcache_htab); if (pp) { pp->cflags |= PF_REF; return pp; } pp = alloc_pcache("pcache_find"); pp->queue = 0; pp->cflags = PF_REF; pp->player = player; pcache_reload1(player, pp); pp->next = pcache_head; pcache_head = pp; hashaddLEN(&player, sizeof(player), pp, &pcache_htab); return pp; } /*! \brief Saves any dirty player data items to the database. * * \param pp pointer to potentially dirty PCACHE structure. * \return None. */ static void pcache_save(PCACHE *pp) { if (pp->cflags & PF_MONEY_CH) { IBUF tbuf; mux_ltoa(pp->money, tbuf); atr_add_raw(pp->player, A_MONEY, tbuf); pp->cflags &= ~PF_MONEY_CH; } } /*! \brief Re-initializes Money and QueueMax items from the database. * * \param player player object dbref. * \return None. */ void pcache_reload(dbref player) { if ( Good_obj(player) && OwnsOthers(player)) { PCACHE *pp = pcache_find(player); pcache_save(pp); pcache_reload1(player, pp); } } /*! \brief Ages and trims the player cache of stale entries. * * pcache_trim() relies primarily on the singly-linked list, but it also * maintains the Hash Table. To be trimmed, a player cache record must * not have outstanding commands in the command queue. * * The one level of aging is accomplished with PR_REF. On the first pass * through the linked list, the PR_REF bit is removed. On the second pass * through the list, the record is trimmed. * * \return None. */ void pcache_trim(void) { PCACHE *pp = pcache_head; PCACHE *pplast = NULL; while (pp) { PCACHE *ppnext = pp->next; if ( pp->queue || (pp->cflags & PF_REF)) { // This entry either has outstanding commands in the queue or we // need to let it age. // pp->cflags &= ~PF_REF; pplast = pp; } else { // Unlink and destroy this entry. // if (pplast) { pplast->next = ppnext; } else { pcache_head = ppnext; } pcache_save(pp); hashdeleteLEN(&(pp->player), sizeof(pp->player), &pcache_htab); free_pcache(pp); } pp = ppnext; } } /*! \brief Flushes any dirty player items to the database. * * The primary access is via the singly-linked list. Upon return, all the * player cache records are marked as clean. * * \return None. */ void pcache_sync(void) { PCACHE *pp = pcache_head; while (pp) { pcache_save(pp); pp = pp->next; } } /*! \brief Adjusts the count of queued commands up or down. * * cque.cpp uses this as it schedules and performs queued commands. * * \param player dbref of player object responsible for command. * \param adj new (+) or completed (-) commands being queued. * \return None. */ int a_Queue(dbref player, int adj) { if ( Good_obj(player) && OwnsOthers(player)) { PCACHE *pp = pcache_find(player); pp->queue += adj; return pp->queue; } return 0; } /*! \brief Returns the player's upper limit of queued commands. * * If a QueueMax is set on the player, we use that. Otherwise, there is * a configurable game-wide limit (given by player_queue_limit) unless the * player is a Wizard in which case, we reason that well behaved Wizard code * should be able to schedule as much work as there are objects in the * database -- larger game, more work to be expected in the queue. * * \param player dbref of player object. * \return None. */ int QueueMax(dbref player) { int m = 0; if ( Good_obj(player) && OwnsOthers(player)) { PCACHE *pp = pcache_find(player); if (pp->qmax >= 0) { m = pp->qmax; } else { // @queuemax was not valid so we use the game-wide limit. // m = mudconf.queuemax; if ( Wizard(player) && m < mudstate.db_top + 1) { m = mudstate.db_top + 1; } } } return m; } /*! \brief Returns how many coins are in a player's purse. * * \param player dbref of player object. * \return None. */ int Pennies(dbref obj) { if (mudstate.bStandAlone) { const char *cp = atr_get_raw(obj, A_MONEY); if (cp) { return mux_atol(cp); } } else if ( Good_obj(obj) && OwnsOthers(obj)) { PCACHE *pp = pcache_find(obj); return pp->money; } return 0; } /*! \brief Sets the number of coins in a player's purse. * * This changes the number of coins a player holds and sets this attribute * as dirty so that it will be updated in the attribute database later. * * \param player dbref of player object responsible for command. * \param howfew Number of coins * \return None. */ void s_Pennies(dbref obj, int howfew) { if (mudstate.bStandAlone) { IBUF tbuf; mux_ltoa(howfew, tbuf); atr_add_raw(obj, A_MONEY, tbuf); } else if ( Good_obj(obj) && OwnsOthers(obj)) { PCACHE *pp = pcache_find(obj); pp->money = howfew; pp->cflags |= PF_MONEY_CH; } }