mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Completes the sweep the issue called for. mux_atol returns long, which is 32-bit on LLP64, so every caller silently truncated on Windows. Two of those were real defects (the truthiness family and cf_size, fixed in the preceding commits); the rest were latent, waiting for a value large enough to matter. Rather than audit 290 sites for whether each can reach 2^31 today, use the 64-bit parser everywhere and remove the class. A dbref cannot overflow now, but nothing stops a later caller passing that same site a timestamp or a byte count. Pure 1:1 substitution: 285 lines changed, and every removed line contained mux_atol while every added line contains mux_atoi64. No control flow, no types, no behaviour beyond the wider parse. This is a NO-OP on LP64 -- long is already 64-bit on Linux and macOS, so the generated code there is unchanged. It only widens the parse on Windows. Narrowing destinations are unaffected either way: `int x = mux_atoi64(s)` truncates exactly as `int x = mux_atol(s)` did, on both models. Left alone: mux_atol itself in mathutil, its declaration, and three comments that name it. Callers that genuinely want 32-bit semantics can still ask for them; none appear to. Verified on Windows: full solution builds clean with no new warnings, smoke is 1418 passed / 16 failed / 0 crashes / 306 of 306 dispatched -- identical to before the sweep, with the same 16 build-configuration failures (exp3 module not loaded, hmac/digest behind UNIX_DIGEST). Spot checks after the change: the boolean family returns 1 for multiples of 2^32, cf_size round-trips 3000000000 and still reads -1 as unlimited, and arithmetic, string and list functions are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
416 lines
10 KiB
C++
416 lines
10 KiB
C++
/*! \file player_c.cpp
|
|
* \brief Player cache routines.
|
|
*
|
|
* Frequently-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"
|
|
|
|
/*! \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 DbrefPtrMap 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 = nullptr;
|
|
}
|
|
|
|
/*! \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 UTF8 *cp = atr_get_raw(player, A_MONEY);
|
|
if (cp && *cp)
|
|
{
|
|
pp->money = mux_atoi64(cp);
|
|
}
|
|
else
|
|
{
|
|
pp->cflags |= PF_MONEY_CH;
|
|
pp->money = 0;
|
|
}
|
|
|
|
int m = -1;
|
|
cp = atr_get_raw(player, A_QUEUEMAX);
|
|
if (cp && *cp)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
m = mux_atoi64(atr_decode_flags_owner(cp, &aowner, &aflags));
|
|
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)
|
|
{
|
|
auto it = pcache_htab.find(player);
|
|
PCACHE *pp = (it != pcache_htab.end()) ? static_cast<PCACHE*>(it->second) : nullptr;
|
|
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;
|
|
pcache_htab.emplace(player, pp);
|
|
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)
|
|
{
|
|
UTF8 tbuf[I32BUF_SIZE];
|
|
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)
|
|
&& !mudstate.bStandAlone)
|
|
{
|
|
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 = nullptr;
|
|
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);
|
|
pcache_htab.erase(pp->player);
|
|
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 Drop a player cache entry when the object is destroyed.
|
|
*
|
|
* Entries are keyed only by dbref. Without an explicit delete, freelist
|
|
* recycle of a former player can inherit stale QueueMax / queue depth
|
|
* (#1180). Do not write dirty Money back — destroy_obj will free attrs.
|
|
*
|
|
* \param player object dbref being destroyed.
|
|
* \return None.
|
|
*/
|
|
|
|
void pcache_delete(dbref player)
|
|
{
|
|
if (mudstate.bStandAlone)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto it = pcache_htab.find(player);
|
|
if (it == pcache_htab.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PCACHE *pp = static_cast<PCACHE *>(it->second);
|
|
PCACHE *prev = nullptr;
|
|
for (PCACHE *cur = pcache_head; cur; prev = cur, cur = cur->next)
|
|
{
|
|
if (cur == pp)
|
|
{
|
|
if (prev)
|
|
{
|
|
prev->next = cur->next;
|
|
}
|
|
else
|
|
{
|
|
pcache_head = cur->next;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pcache_htab.erase(it);
|
|
free_pcache(pp);
|
|
}
|
|
|
|
/*! \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 or things's purse.
|
|
*
|
|
* \param obj dbref of player object.
|
|
* \return None.
|
|
*/
|
|
|
|
int Pennies(dbref obj)
|
|
{
|
|
if (Good_obj(obj))
|
|
{
|
|
if ( !mudstate.bStandAlone
|
|
&& OwnsOthers(obj))
|
|
{
|
|
PCACHE *pp = pcache_find(obj);
|
|
return pp->money;
|
|
}
|
|
else
|
|
{
|
|
const UTF8 *cp = atr_get_raw(obj, A_MONEY);
|
|
if (cp)
|
|
{
|
|
return mux_atoi64(cp);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*! \brief Sets the number of coins in a player's or thing'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 obj dbref of player object responsible for command.
|
|
* \param howfew Number of coins
|
|
* \return None.
|
|
*/
|
|
|
|
void s_Pennies(dbref obj, int howfew)
|
|
{
|
|
if (Good_obj(obj))
|
|
{
|
|
if ( !mudstate.bStandAlone
|
|
&& OwnsOthers(obj))
|
|
{
|
|
PCACHE *pp = pcache_find(obj);
|
|
pp->money = howfew;
|
|
pp->cflags |= PF_MONEY_CH;
|
|
}
|
|
else
|
|
{
|
|
UTF8 tbuf[I32BUF_SIZE];
|
|
mux_ltoa(howfew, tbuf);
|
|
atr_add_raw(obj, A_MONEY, tbuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! \brief A shortcut method of initializing the coins in a object's purse.
|
|
*
|
|
* This function should only be called from db_rw.cpp for loading the
|
|
* database. From there, objects are in an in-between state, the object type
|
|
* is not yet known, but on the other hand, the dbref has just been allocated,
|
|
* so the player cache will not need flushing or updated. In fact, using a
|
|
* the player cache during database load is ineffect because it causes a read
|
|
* request for A_MONEY which will most likely fail and return a value of zero
|
|
* coins. Any value it does return, we immediate change anyway. Furthermore,
|
|
* The cache consumes increasing memory during the load without any trims or
|
|
* saves.
|
|
*
|
|
* \param obj dbref of object.
|
|
* \param howfew Number of coins
|
|
* \return None.
|
|
*/
|
|
|
|
void s_PenniesDirect(dbref obj, int howfew)
|
|
{
|
|
UTF8 tbuf[I32BUF_SIZE];
|
|
mux_ltoa(howfew, tbuf);
|
|
atr_add_raw(obj, A_MONEY, tbuf);
|
|
}
|