glyphs: replace open-addressed glyphname hashtable with sorted index

The open-addressed glyphname_hashtable stored each canonical
"G_xxx" name as a dupstr'd string in its bucket: ~9577 small heap
allocations and ~290 KB of resident name strings, on top of ~256 KB
of 32768-bucket scaffolding kept at <50% load for probe performance.
On classic Mac OS the populate cost (quadratic small-allocation in a
fragmented Memory Manager heap) dominated startup time -- many
seconds on an SE/30 -- and ~800 KB resident is meaningful on the
small machines the port targets.

Switch to a sorted (hash, glyph) index sized exactly to the number
of named glyphs:

  struct glyphname_hashtable_entry_t {
      uint32 hash;
      int glyphnum;
  };

populate_glyphname_hashtable() allocates one block of MAX_GLYPH
entries (no per-name strings), fills it via compose_glyph_name() +
glyph_hash(), and qsort()s ascending by hash.  Lookup binary-searches
the hash column, then walks any equal-hash neighbours verifying each
candidate by reconstructing its canonical name and strcmpi'ing it
back -- collisions are rare with 9577 uniformly-distributed 32-bit
hashes.

Other changes that fall out:

 * Extract compose_glyph_name() from parse_id's bulk-iteration switch
   so it is the single source of truth for "glyph number -> canonical
   name".  Called by find_glyph_in_hashtable for collision
   verification, by populate_glyphname_hashtable, by the
   --dumpglyphnames path, and by wizcustom_glyphnames.
 * empty_glyphname_hashtable() reduces to free(ptr); no per-entry
   strings to release.
 * Drop find_glyphname_in_hashtable_by_glyphnum (no longer used --
   wizcustom_glyphnames iterates compose_glyph_name directly).
 * Drop the res_fill_hashtable parse_id mode; populate iterates
   directly.

Memory drops from ~800 KB to ~75 KB.  One allocation instead of
~9578.  Lookup goes from O(1) to O(log N) but N ~ 9577 means ~14
comparisons per probe -- well under what the upstream cache ever
cost in practice.

Function names (populate_glyphname_hashtable, etc.) are kept for
extern.h compatibility; the data structure is now a sorted index,
"hashtable" in the names is historical.

--dumpglyphnames output is byte-identical.
This commit is contained in:
Ingo Paschke
2026-05-25 14:22:19 -04:00
committed by nhmall
parent b7c7f051b4
commit be9e430ebb
+333 -401
View File
@@ -11,7 +11,7 @@ extern struct enum_dump objdump[];
#define Fprintf (void) fprintf
enum reserved_activities { res_nothing, res_dump_glyphnames, res_fill_hashtable };
enum reserved_activities { res_nothing, res_dump_glyphnames };
enum things_to_find { find_nothing, find_pm, find_oc, find_cmap, find_glyph };
struct find_struct {
enum things_to_find findtype;
@@ -27,20 +27,18 @@ struct find_struct {
};
static const struct find_struct zero_find = { 0 };
struct glyphname_hashtable_entry_t {
int glyphnum;
char *id;
uint32 hash;
int glyphnum; /* NO_GLYPH (==MAX_GLYPH) marks an empty bucket */
};
static struct glyphname_hashtable_entry_t *glyphname_hashtable_ptr;
static unsigned glyphname_hashtable_lsize;
static size_t glyphname_hashtable_size;
static struct find_struct glyphname_hashtable_find, to_custom_symbol_find;
static size_t glyphname_hashtable_count;
static struct find_struct to_custom_symbol_find;
static const long nonzero_black = CLR_BLACK | NH_BASIC_COLOR;
staticfn void init_glyphname_hashtable(void);
staticfn void add_glyph_to_glyphname_hashtable(int glyphnum, const char *id);
staticfn int find_glyph_in_cache(const char *id);
staticfn char *find_glyphname_in_hashtable_by_glyphnum(int glyphnum);
staticfn int find_glyph_in_hashtable(const char *id);
staticfn int cmp_glyphname_entry(const void *a, const void *b);
staticfn uint32 glyph_hash(const char *id);
staticfn int compose_glyph_name(int glyph, char *buf, size_t bufsz);
staticfn void to_custom_symset_entry_callback(int glyph,
struct find_struct *findwhat);
staticfn int parse_id(const char *id, struct find_struct *findwhat);
@@ -196,6 +194,239 @@ fix_glyphname(char *str)
return str;
}
/* Build the canonical "G_xxx" identifier for the given glyph into buf.
* Returns 1 if a name was produced, 0 if this glyph has no canonical name
* (a few unused object indices); buf[0] is set to '\0' in that case.
* Used by parse_id's bulk-iteration paths, by find_glyph_in_hashtable to
* verify hash matches, by populate_glyphname_hashtable to fill the table,
* and by wizcustom_glyphnames. */
staticfn int
compose_glyph_name(int glyph, char *buf, size_t bufsz)
{
int i, j, mnum, cmap_offset = 0;
boolean skip_base = FALSE;
const char *buf2, *buf3, *buf4;
char tmpbuf[4][QBUFSZ];
if (bufsz < 2)
return 0;
buf[0] = '\0';
tmpbuf[0][0] = tmpbuf[1][0] = tmpbuf[2][0] = tmpbuf[3][0] = '\0';
/* compute cmap_offset (the start of SYM_PCHAR entries in loadsyms[]) */
i = 0;
while (loadsyms[i].range) {
if (!cmap_offset && loadsyms[i].range == SYM_PCHAR)
cmap_offset = i;
i++;
}
if (glyph_is_monster(glyph)) {
buf2 = "";
buf3 = monsdump[glyph_to_mon(glyph)].nm;
if (glyph_is_normal_male_monster(glyph)) {
buf2 = "male_";
} else if (glyph_is_normal_female_monster(glyph)) {
buf2 = "female_";
} else if (glyph_is_ridden_male_monster(glyph)) {
buf2 = "ridden_male_";
} else if (glyph_is_ridden_female_monster(glyph)) {
buf2 = "ridden_female_";
} else if (glyph_is_detected_male_monster(glyph)) {
buf2 = "detected_male_";
} else if (glyph_is_detected_female_monster(glyph)) {
buf2 = "detected_female_";
} else if (glyph_is_male_pet(glyph)) {
buf2 = "pet_male_";
} else if (glyph_is_female_pet(glyph)) {
buf2 = "pet_female_";
}
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_body(glyph)) {
buf2 = glyph_is_body_piletop(glyph) ? "piletop_body_" : "body_";
buf3 = monsdump[glyph_to_body_corpsenm(glyph)].nm;
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_statue(glyph)) {
buf2 = glyph_is_fem_statue_piletop(glyph)
? "piletop_statue_of_female_"
: glyph_is_fem_statue(glyph)
? "statue_of_female_"
: glyph_is_male_statue_piletop(glyph)
? "piletop_statue_of_male_"
: glyph_is_male_statue(glyph)
? "statue_of_male_"
: "";
buf3 = monsdump[glyph_to_statue_corpsenm(glyph)].nm;
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_object(glyph)) {
i = glyph_to_obj(glyph);
if (((i > SCR_STINKING_CLOUD) && (i < SCR_MAIL))
|| ((i > WAN_LIGHTNING) && (i < GOLD_PIECE))) {
return 0;
}
if ((i >= WAN_LIGHT) && (i <= WAN_LIGHTNING))
buf2 = "wand of ";
else if ((i >= SPE_DIG) && (i < SPE_BLANK_PAPER))
buf2 = "spellbook of ";
else if ((i >= SCR_ENCHANT_ARMOR) && (i <= SCR_STINKING_CLOUD))
buf2 = "scroll of ";
else if ((i >= POT_GAIN_ABILITY) && (i <= POT_WATER))
buf2 = (i == POT_WATER) ? "flask of n" : "potion of ";
else if ((i >= RIN_ADORNMENT) && (i <= RIN_PROTECTION_FROM_SHAPE_CHAN))
buf2 = "ring of ";
else if (i == LAND_MINE)
buf2 = "unset ";
else
buf2 = "";
buf3 = (i == SCR_BLANK_PAPER) ? "blank scroll"
: (i == SPE_BLANK_PAPER) ? "blank spellbook"
: (i == SLIME_MOLD) ? "slime mold"
: obj_descr[i].oc_name
? obj_descr[i].oc_name
: obj_descr[i].oc_descr;
Strcpy(buf, "G_");
if (glyph_is_normal_piletop_obj(glyph))
Strcat(buf, "piletop_");
Strcat(buf, buf2);
Strcat(buf, buf3);
} else if (glyph_is_cmap(glyph) || glyph_is_cmap_zap(glyph)
|| glyph_is_swallow(glyph) || glyph_is_explosion(glyph)) {
int cmap = -1;
buf2 = "";
buf3 = "";
buf4 = "";
if (glyph == GLYPH_CMAP_OFF) {
cmap = S_stone;
buf3 = "stone substrate";
skip_base = TRUE;
} else if (glyph_is_cmap_gehennom(glyph)) {
cmap = (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall;
buf4 = "_gehennom";
} else if (glyph_is_cmap_knox(glyph)) {
cmap = (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall;
buf4 = "_knox";
} else if (glyph_is_cmap_main(glyph)) {
cmap = (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall;
buf4 = "_main";
} else if (glyph_is_cmap_mines(glyph)) {
cmap = (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall;
buf4 = "_mines";
} else if (glyph_is_cmap_sokoban(glyph)) {
cmap = (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall;
buf4 = "_sokoban";
} else if (glyph_is_cmap_a(glyph)) {
cmap = (glyph - GLYPH_CMAP_A_OFF) + S_ndoor;
} else if (glyph_is_cmap_altar(glyph)) {
static const char *const altar_text[] = {
"unaligned", "chaotic", "neutral",
"lawful", "other",
};
j = (glyph - GLYPH_ALTAR_OFF);
cmap = S_altar;
if (j != altar_other) {
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s_", altar_text[j]);
buf2 = tmpbuf[2];
} else {
buf3 = "altar other";
skip_base = TRUE;
}
} else if (glyph_is_cmap_b(glyph)) {
cmap = (glyph - GLYPH_CMAP_B_OFF) + S_grave;
} else if (glyph_is_cmap_zap(glyph)) {
static const char *const zap_texts[] = {
"missile", "fire", "frost", "sleep",
"death", "lightning", "poison gas", "acid"
};
j = (glyph - GLYPH_ZAP_OFF);
cmap = (j % 4) + S_vbeam;
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s",
loadsyms[cmap + cmap_offset].name + 2);
Snprintf(tmpbuf[3], sizeof tmpbuf[3], "%s zap %s",
zap_texts[j / 4], fix_glyphname(tmpbuf[2]));
buf3 = tmpbuf[3];
buf2 = "";
skip_base = TRUE;
} else if (glyph_is_cmap_c(glyph)) {
cmap = (glyph - GLYPH_CMAP_C_OFF) + S_digbeam;
} else if (glyph_is_swallow(glyph)) {
static const char *const swallow_texts[] = {
"top left", "top center", "top right",
"middle left", "middle right", "bottom left",
"bottom center", "bottom right",
};
j = glyph - GLYPH_SWALLOW_OFF;
cmap = glyph_to_swallow(glyph);
mnum = j / ((S_sw_br - S_sw_tl) + 1);
Strcpy(tmpbuf[3], "swallow ");
Strcat(tmpbuf[3], monsdump[mnum].nm);
Strcat(tmpbuf[3], " ");
Strcat(tmpbuf[3], swallow_texts[cmap]);
buf3 = tmpbuf[3];
skip_base = TRUE;
} else if (glyph_is_explosion(glyph)) {
static const char *const expl_type_texts[] = {
"dark", "noxious", "muddy", "wet",
"magical", "fiery", "frosty",
};
static const char *const expl_texts[] = {
"tl", "tc", "tr", "ml", "mc",
"mr", "bl", "bc", "br",
};
int expl;
j = glyph - GLYPH_EXPLODE_OFF;
expl = j / ((S_expl_br - S_expl_tl) + 1);
cmap = glyph_to_explosion(glyph) + S_expl_tl;
i = cmap - S_expl_tl;
Snprintf(tmpbuf[2], sizeof tmpbuf[2], "%s ",
expl_type_texts[expl]);
buf2 = tmpbuf[2];
Snprintf(tmpbuf[3], sizeof tmpbuf[3], "%s%s", "expl_",
expl_texts[i]);
buf3 = tmpbuf[3];
skip_base = TRUE;
}
if (!skip_base) {
if (cmap >= 0 && cmap < MAXPCHARS)
buf3 = loadsyms[cmap + cmap_offset].name + 2;
}
Strcpy(buf, "G_");
Strcat(buf, buf2);
Strcat(buf, buf3);
Strcat(buf, buf4);
} else if (glyph_is_invisible(glyph)) {
Strcpy(buf, "G_invisible");
} else if (glyph_is_nothing(glyph)) {
Strcpy(buf, "G_nothing");
} else if (glyph_is_unexplored(glyph)) {
Strcpy(buf, "G_unexplored");
} else if (glyph_is_warning(glyph)) {
j = glyph - GLYPH_WARNING_OFF;
Snprintf(buf, bufsz, "G_%s%d", "warning", j);
}
if (buf[0] == '\0')
return 0;
if (memchr(buf, '\0', bufsz) == NULL) {
/* defensive: caller passed an undersized buffer */
buf[bufsz - 1] = '\0';
return 0;
}
fix_glyphname(buf + 2);
nhUse(mnum);
return 1;
}
int
glyph_to_cmap(int glyph)
{
@@ -283,152 +514,96 @@ glyph_find_core(
}
/*
When we start to process a config file or a symbol file,
that might have G_ entries, generating all 9000+ glyphid
for comparison repeatedly each time we encounter a G_
entry to decipher, then comparing against them, is obviously
extremely performance-poor.
* glyphname_hashtable is a sorted (hash, glyph) index of canonical
* "G_xxx" identifiers. populate_glyphname_hashtable() allocates one
* block of MAX_GLYPH * sizeof(entry) (some entries go unused for the
* scroll/gem appearance gaps), fills it via compose_glyph_name(), and
* sorts ascending by hash. Lookup is bsearch on the hash with
* compose_glyph_name()+strcmpi to disambiguate the rare collision.
*
* The name "hashtable" is historical; this is a sorted array, not an
* open-addressed hash table. The on-disk API in extern.h is kept so
* that callers (wizcmds.c, options.c) don't need to change.
*
* Memory: one alloc, ~75 KB for the 9577 named slots on a typical 5.0
* build (struct is 8 bytes, no per-name strings). The same data in
* upstream's open-addressed table cost ~256 KB for the buckets plus
* ~290 KB of dupstr'd names plus malloc overhead.
*/
Setting aside the "comparison" part for now (that has to be
done in some manner), we can likely do something about the
repeated "generation" of the names for parsing prior to the
actual comparison part by generating them once, ahead of the
bulk of the potential parsings. We can later free up
all the memory those names consumed once the bulk parsing is
over with.
*/
staticfn int
cmp_glyphname_entry(const void *a, const void *b)
{
uint32 ha = ((const struct glyphname_hashtable_entry_t *) a)->hash;
uint32 hb = ((const struct glyphname_hashtable_entry_t *) b)->hash;
if (ha < hb)
return -1;
if (ha > hb)
return 1;
return 0;
}
void
populate_glyphname_hashtable(void)
{
int reslt = 0;
int glyph;
size_t n = 0;
char buf[BUFSZ];
if (!glyphname_hashtable_ptr) {
init_glyphname_hashtable();
}
if (glyphname_hashtable_ptr) {
glyphname_hashtable_find = zero_find;
glyphname_hashtable_find.findtype = find_nothing;
glyphname_hashtable_find.reserved = (genericptr_t) glyphname_hashtable_ptr;
glyphname_hashtable_find.restype = res_fill_hashtable;
reslt = parse_id((char *) 0, &glyphname_hashtable_find);
if (!reslt) {
empty_glyphname_hashtable();
glyphname_hashtable_ptr = (struct glyphname_hashtable_entry_t *) 0;
if (glyphname_hashtable_ptr)
return;
glyphname_hashtable_ptr = (struct glyphname_hashtable_entry_t *) alloc(
MAX_GLYPH * sizeof (struct glyphname_hashtable_entry_t));
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
if (compose_glyph_name(glyph, buf, sizeof buf)) {
glyphname_hashtable_ptr[n].hash = glyph_hash(buf);
glyphname_hashtable_ptr[n].glyphnum = glyph;
++n;
}
}
}
/*
* The glyph ID cache is a simple double-hash table.
* The cache size is a power of two, and two hashes are derived from the
* cache ID. The first is a location in the table, and the second is an
* offset. On any collision, the second hash is added to the first until
* a match or an empty bucket is found.
* The second hash is an odd number, which is necessary and sufficient
* to traverse the entire table.
*/
staticfn void
init_glyphname_hashtable(void)
{
size_t glyph;
/* Cache size of power of 2 not less than 2*MAX_GLYPH */
glyphname_hashtable_lsize = 0;
glyphname_hashtable_size = 1;
while (glyphname_hashtable_size < 2*MAX_GLYPH) {
++glyphname_hashtable_lsize;
glyphname_hashtable_size <<= 1;
}
glyphname_hashtable_ptr = (struct glyphname_hashtable_entry_t *) alloc(
glyphname_hashtable_size * sizeof (struct glyphname_hashtable_entry_t));
for (glyph = 0; glyph < glyphname_hashtable_size; ++glyph) {
glyphname_hashtable_ptr[glyph].glyphnum = 0;
glyphname_hashtable_ptr[glyph].id = (char *) 0;
}
qsort(glyphname_hashtable_ptr, n,
sizeof glyphname_hashtable_ptr[0], cmp_glyphname_entry);
glyphname_hashtable_count = n;
}
void
empty_glyphname_hashtable(void)
{
size_t idx;
if (!glyphname_hashtable_ptr)
return;
for (idx = 0; idx < glyphname_hashtable_size; ++idx) {
if (glyphname_hashtable_ptr[idx].id) {
free(glyphname_hashtable_ptr[idx].id);
glyphname_hashtable_ptr[idx].id = (char *) 0;
}
}
free(glyphname_hashtable_ptr);
glyphname_hashtable_ptr = (struct glyphname_hashtable_entry_t *) 0;
}
staticfn void
add_glyph_to_glyphname_hashtable(int glyphnum, const char *id)
{
uint32 hash = glyph_hash(id);
size_t hash1 = (size_t) (hash & (glyphname_hashtable_size - 1));
size_t hash2 = (size_t)
(((hash >> glyphname_hashtable_lsize) & (glyphname_hashtable_size - 1)) | 1);
size_t i = hash1;
do {
if (glyphname_hashtable_ptr[i].id == NULL) {
/* Empty bucket found */
glyphname_hashtable_ptr[i].id = dupstr(id);
glyphname_hashtable_ptr[i].glyphnum = glyphnum;
return;
}
/* For speed, assume that no ID occurs twice */
i = (i + hash2) & (glyphname_hashtable_size - 1);
} while (i != hash1);
/* This should never happen */
panic("glyphname_hashtable_ptr full");
glyphname_hashtable_count = 0;
}
staticfn int
find_glyph_in_cache(const char *id)
find_glyph_in_hashtable(const char *id)
{
uint32 hash = glyph_hash(id);
size_t hash1 = (size_t) (hash & (glyphname_hashtable_size - 1));
size_t hash2 = (size_t)
(((hash >> glyphname_hashtable_lsize) & (glyphname_hashtable_size - 1)) | 1);
size_t i = hash1;
uint32 want = glyph_hash(id);
size_t lo = 0, hi = glyphname_hashtable_count, mid;
char buf[BUFSZ];
do {
if (glyphname_hashtable_ptr[i].id == NULL) {
/* Empty bucket found */
return -1;
}
if (strcmpi(id, glyphname_hashtable_ptr[i].id) == 0) {
/* Match found */
return glyphname_hashtable_ptr[i].glyphnum;
}
i = (i + hash2) & (glyphname_hashtable_size - 1);
} while (i != hash1);
return -1;
}
staticfn char *
find_glyphname_in_hashtable_by_glyphnum(int glyphnum)
{
size_t idx;
if (!glyphname_hashtable_ptr)
return (char *) 0;
for (idx = 0; idx < glyphname_hashtable_size; ++idx) {
if (glyphname_hashtable_ptr[idx].glyphnum == glyphnum
&& glyphname_hashtable_ptr[idx].id != 0) {
/* Match found */
return glyphname_hashtable_ptr[idx].id;
}
/* Binary-search the sorted array for the first entry whose hash >= want. */
while (lo < hi) {
mid = (lo + hi) >> 1;
if (glyphname_hashtable_ptr[mid].hash < want)
lo = mid + 1;
else
hi = mid;
}
return (char *) 0;
/* Walk forward across any equal-hash neighbours, verifying each by
reconstructing the canonical name and strcmpi'ing it back. */
while (lo < glyphname_hashtable_count
&& glyphname_hashtable_ptr[lo].hash == want) {
int g = glyphname_hashtable_ptr[lo].glyphnum;
if (compose_glyph_name(g, buf, sizeof buf) && !strcmpi(id, buf))
return g;
++lo;
}
return -1;
}
staticfn uint32
@@ -808,15 +983,11 @@ void
wizcustom_glyphnames(winid win)
{
int glyphnum;
char *id;
char buf[BUFSZ];
if (!glyphname_hashtable_ptr)
return;
for (glyphnum = 0; glyphnum < MAX_GLYPH; ++glyphnum) {
id = find_glyphname_in_hashtable_by_glyphnum(glyphnum);
if (id) {
wizcustom_callback(win, glyphnum, id);
}
if (compose_glyph_name(glyphnum, buf, sizeof buf))
wizcustom_callback(win, glyphnum, buf);
}
}
@@ -826,12 +997,11 @@ parse_id(
struct find_struct *findwhat)
{
FILE *fp = (FILE *) 0;
int i = 0, j, mnum, glyph,
int i = 0, glyph,
pm_offset = 0, oc_offset = 0, cmap_offset = 0,
pm_count = 0, oc_count = 0, cmap_count = 0;
boolean skip_base = FALSE, skip_this_one = FALSE, dump_ids = FALSE,
filling_cache = FALSE, is_S = FALSE, is_G = FALSE;
char buf[4][QBUFSZ];
boolean dump_ids = FALSE, is_S = FALSE, is_G = FALSE;
char buf[BUFSZ];
if (findwhat->findtype == find_nothing && findwhat->restype) {
if (findwhat->restype == res_dump_glyphnames) {
@@ -842,20 +1012,12 @@ parse_id(
return 0;
}
}
if (findwhat->restype == res_fill_hashtable) {
if (findwhat->reserved
&& findwhat->reserved == (genericptr_t) glyphname_hashtable_ptr) {
filling_cache = TRUE;
} else {
return 0;
}
}
}
is_G = (id && id[0] == 'G' && id[1] == '_');
is_S = (id && id[0] == 'S' && id[1] == '_');
if ((is_G && !glyphname_hashtable_ptr) || filling_cache || dump_ids || is_S) {
if (dump_ids || is_S) {
while (loadsyms[i].range) {
if (!pm_offset && loadsyms[i].range == SYM_MON)
pm_offset = i;
@@ -872,259 +1034,31 @@ parse_id(
i++;
}
}
if (is_G || filling_cache || dump_ids) {
if (!filling_cache && id && glyphname_hashtable_ptr) {
int val = find_glyph_in_cache(id);
if (is_G && id) {
/* Populate the hash table lazily, on first G_xxx lookup. */
if (!glyphname_hashtable_ptr)
populate_glyphname_hashtable();
if (glyphname_hashtable_ptr) {
int val = find_glyph_in_hashtable(id);
if (val >= 0) {
findwhat->findtype = find_glyph;
findwhat->val = val;
findwhat->loadsyms_offset = 0;
return 1;
} else {
return 0;
}
} else {
const char *buf2, *buf3, *buf4;
/* individual matching glyph entries */
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
skip_base = FALSE;
skip_this_one = FALSE;
buf[0][0] = buf[1][0] = buf[2][0] = buf[3][0] = '\0';
if (glyph_is_monster(glyph)) {
/* buf2 will hold the distinguishing prefix */
/* buf3 will hold the base name */
buf2 = "";
buf3 = monsdump[glyph_to_mon(glyph)].nm;
if (glyph_is_normal_male_monster(glyph)) {
buf2 = "male_";
} else if (glyph_is_normal_female_monster(glyph)) {
buf2 = "female_";
} else if (glyph_is_ridden_male_monster(glyph)) {
buf2 = "ridden_male_";
} else if (glyph_is_ridden_female_monster(glyph)) {
buf2 = "ridden_female_";
} else if (glyph_is_detected_male_monster(glyph)) {
buf2 = "detected_male_";
} else if (glyph_is_detected_female_monster(glyph)) {
buf2 = "detected_female_";
} else if (glyph_is_male_pet(glyph)) {
buf2 = "pet_male_";
} else if (glyph_is_female_pet(glyph)) {
buf2 = "pet_female_";
}
Strcpy(buf[0], "G_");
Strcat(buf[0], buf2);
Strcat(buf[0], buf3);
} else if (glyph_is_body(glyph)) {
/* buf2 will hold the distinguishing prefix */
/* buf3 will hold the base name */
buf2 = glyph_is_body_piletop(glyph)
? "piletop_body_"
: "body_";
buf3 = monsdump[glyph_to_body_corpsenm(glyph)].nm;
Strcpy(buf[0], "G_");
Strcat(buf[0], buf2);
Strcat(buf[0], buf3);
} else if (glyph_is_statue(glyph)) {
/* buf2 will hold the distinguishing prefix */
/* buf3 will hold the base name */
buf2 = glyph_is_fem_statue_piletop(glyph)
? "piletop_statue_of_female_"
: glyph_is_fem_statue(glyph)
? "statue_of_female_"
: glyph_is_male_statue_piletop(glyph)
? "piletop_statue_of_male_"
: glyph_is_male_statue(glyph)
? "statue_of_male_"
: ""; /* shouldn't happen */
buf3 = monsdump[glyph_to_statue_corpsenm(glyph)].nm;
Strcpy(buf[0], "G_");
Strcat(buf[0], buf2);
Strcat(buf[0], buf3);
} else if (glyph_is_object(glyph)) {
i = glyph_to_obj(glyph);
/* buf2 will hold the distinguishing prefix */
/* buf3 will hold the base name */
if (((i > SCR_STINKING_CLOUD) && (i < SCR_MAIL))
|| ((i > WAN_LIGHTNING) && (i < GOLD_PIECE)))
skip_this_one = TRUE;
if (!skip_this_one) {
if ((i >= WAN_LIGHT) && (i <= WAN_LIGHTNING))
buf2 = "wand of ";
else if ((i >= SPE_DIG) && (i < SPE_BLANK_PAPER))
buf2 = "spellbook of ";
else if ((i >= SCR_ENCHANT_ARMOR)
&& (i <= SCR_STINKING_CLOUD))
buf2 = "scroll of ";
else if ((i >= POT_GAIN_ABILITY) && (i <= POT_WATER))
buf2 = (i == POT_WATER) ? "flask of n"
: "potion of ";
else if ((i >= RIN_ADORNMENT)
&& (i <= RIN_PROTECTION_FROM_SHAPE_CHAN))
buf2 = "ring of ";
else if (i == LAND_MINE)
buf2 = "unset ";
else
buf2 = "";
buf3 = (i == SCR_BLANK_PAPER) ? "blank scroll"
: (i == SPE_BLANK_PAPER) ? "blank spellbook"
: (i == SLIME_MOLD) ? "slime mold"
: obj_descr[i].oc_name
? obj_descr[i].oc_name
: obj_descr[i].oc_descr;
Strcpy(buf[0], "G_");
if (glyph_is_normal_piletop_obj(glyph))
Strcat(buf[0], "piletop_");
Strcat(buf[0], buf2);
Strcat(buf[0], buf3);
}
} else if (glyph_is_cmap(glyph) || glyph_is_cmap_zap(glyph)
|| glyph_is_swallow(glyph)
|| glyph_is_explosion(glyph)) {
int cmap = -1;
/* buf2 will hold the distinguishing prefix */
/* buf3 will hold the base name */
/* buf4 will hold the distinguishing suffix */
buf2 = "";
buf3 = "";
buf4 = "";
if (glyph == GLYPH_CMAP_OFF) {
cmap = S_stone;
buf3 = "stone substrate";
skip_base = TRUE;
} else if (glyph_is_cmap_gehennom(glyph)) {
cmap = (glyph - GLYPH_CMAP_GEH_OFF) + S_vwall;
buf4 = "_gehennom";
} else if (glyph_is_cmap_knox(glyph)) {
cmap = (glyph - GLYPH_CMAP_KNOX_OFF) + S_vwall;
buf4 = "_knox";
} else if (glyph_is_cmap_main(glyph)) {
cmap = (glyph - GLYPH_CMAP_MAIN_OFF) + S_vwall;
buf4 = "_main";
} else if (glyph_is_cmap_mines(glyph)) {
cmap = (glyph - GLYPH_CMAP_MINES_OFF) + S_vwall;
buf4 = "_mines";
} else if (glyph_is_cmap_sokoban(glyph)) {
cmap = (glyph - GLYPH_CMAP_SOKO_OFF) + S_vwall;
buf4 = "_sokoban";
} else if (glyph_is_cmap_a(glyph)) {
cmap = (glyph - GLYPH_CMAP_A_OFF) + S_ndoor;
} else if (glyph_is_cmap_altar(glyph)) {
static const char *const altar_text[] = {
"unaligned", "chaotic", "neutral",
"lawful", "other",
};
j = (glyph - GLYPH_ALTAR_OFF);
cmap = S_altar;
if (j != altar_other) {
Snprintf(buf[2], sizeof buf[2], "%s_",
altar_text[j]);
buf2 = buf[2];
} else {
buf3 = "altar other";
skip_base = TRUE;
}
} else if (glyph_is_cmap_b(glyph)) {
cmap = (glyph - GLYPH_CMAP_B_OFF) + S_grave;
} else if (glyph_is_cmap_zap(glyph)) {
static const char *const zap_texts[] = {
"missile", "fire", "frost", "sleep",
"death", "lightning", "poison gas", "acid"
};
j = (glyph - GLYPH_ZAP_OFF);
cmap = (j % 4) + S_vbeam;
Snprintf(buf[2], sizeof buf[2], "%s",
loadsyms[cmap + cmap_offset].name + 2);
Snprintf(buf[3], sizeof buf[3], "%s zap %s",
zap_texts[j / 4], fix_glyphname(buf[2]));
buf3 = buf[3];
buf2 = "";
skip_base = TRUE;
} else if (glyph_is_cmap_c(glyph)) {
cmap = (glyph - GLYPH_CMAP_C_OFF) + S_digbeam;
} else if (glyph_is_swallow(glyph)) {
static const char *const swallow_texts[] = {
"top left", "top center", "top right",
"middle left", "middle right", "bottom left",
"bottom center", "bottom right",
};
j = glyph - GLYPH_SWALLOW_OFF;
cmap = glyph_to_swallow(glyph);
mnum = j / ((S_sw_br - S_sw_tl) + 1);
Strcpy(buf[3], "swallow ");
Strcat(buf[3], monsdump[mnum].nm);
Strcat(buf[3], " ");
Strcat(buf[3], swallow_texts[cmap]);
buf3 = buf[3];
skip_base = TRUE;
} else if (glyph_is_explosion(glyph)) {
static const char *const expl_type_texts[] = {
"dark", "noxious", "muddy", "wet",
"magical", "fiery", "frosty",
};
static const char *const expl_texts[] = {
"tl", "tc", "tr", "ml", "mc",
"mr", "bl", "bc", "br",
};
int expl;
j = glyph - GLYPH_EXPLODE_OFF;
expl = j / ((S_expl_br - S_expl_tl) + 1);
cmap = glyph_to_explosion(glyph) + S_expl_tl;
i = cmap - S_expl_tl;
Snprintf(buf[2], sizeof buf[2], "%s ",
expl_type_texts[expl]);
buf2 = buf[2];
Snprintf(buf[3], sizeof buf[3], "%s%s", "expl_",
expl_texts[i]);
buf3 = buf[3];
skip_base = TRUE;
}
if (!skip_base) {
if (cmap >= 0 && cmap < MAXPCHARS) {
buf3 = loadsyms[cmap + cmap_offset].name + 2;
}
}
Strcpy(buf[0], "G_");
Strcat(buf[0], buf2);
Strcat(buf[0], buf3);
Strcat(buf[0], buf4);
} else if (glyph_is_invisible(glyph)) {
Strcpy(buf[0], "G_invisible");
} else if (glyph_is_nothing(glyph)) {
Strcpy(buf[0], "G_nothing");
} else if (glyph_is_unexplored(glyph)) {
Strcpy(buf[0], "G_unexplored");
} else if (glyph_is_warning(glyph)) {
j = glyph - GLYPH_WARNING_OFF;
Snprintf(buf[0], sizeof buf[0], "G_%s%d", "warning", j);
}
if (memchr(buf[0], '\0', sizeof buf[0]) == NULL)
panic("parse_id: buf[0] overflowed");
if (!skip_this_one) {
fix_glyphname(buf[0]+2);
if (dump_ids) {
Fprintf(fp, "(%04d) %s\n", glyph, buf[0]);
} else if (filling_cache) {
add_glyph_to_glyphname_hashtable(glyph, buf[0]);
} else if (id) {
if (!strcmpi(id, buf[0])) {
findwhat->findtype = find_glyph;
findwhat->val = glyph;
findwhat->loadsyms_offset = 0;
return 1;
}
}
}
}
} /* not glyphname_hashtable_ptr */
} else if (is_S) {
}
return 0;
}
if (dump_ids) {
/* iterate and dump every named glyph */
for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
if (compose_glyph_name(glyph, buf, sizeof buf))
Fprintf(fp, "(%04d) %s\n", glyph, buf);
}
return 1;
}
if (is_S) {
/* cmap entries */
for (i = 0; i < cmap_count; ++i) {
if (!strcmpi(loadsyms[i + cmap_offset].name + 2, id + 2)) {
@@ -1153,8 +1087,6 @@ parse_id(
}
}
}
if (dump_ids || filling_cache)
return 1;
findwhat->findtype = find_nothing;
findwhat->val = 0;
findwhat->loadsyms_offset = 0;