Files
questshock/engine/src/GameSrc/objects.c
T
ml a5ec8db5f7
build / build (push) Failing after 5s
Vendor the Shockolate engine and build it via a Docker image with every
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont)
prebuilt, so compiling the engine needs no network access - just the
image and the engine source. Add res/assets/extract_assets.sh to pull
the game's data files out of a purchased GOG installer, and a Makefile
that assembles a runnable dist/ from the two.

Also add `make package`, which builds a redistributable tarball that
omits the proprietary game assets (shipping res/GET_ASSETS.txt instead)
plus license information for both the MIT tooling and the GPLv3 engine,
and a Gitea Actions workflow that builds and publishes it to
dl.ladkau.de on every push.
2026-07-19 15:43:38 +02:00

1524 lines
44 KiB
C

/*
Copyright (C) 2015-2018 Night Dive Studios, LLC.
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 3 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, see <http://www.gnu.org/licenses/>.
*/
/*
** $Header: r:/prj/cit/src/RCS/objects.c 1.13 1994/08/31 16:27:36 tjs Exp $
*
*/
////////////////////////////////////////////////////////////
//
// READ ME FIRST!
//
//////////////////////////////
//
// So then, let us determine some determinology for our terms, here
//
// There are really three places a thing can be:
// WORLD: in the world and being used
// FLOATING: existent but not connected to the real world in any way
// UNUSED: not used at all, ready to be reclaimed
//
// Any function modifying the world in some way will probably contain
// one of the following words:
//
// Grab: get an unused thing UNUSED -> FLOATING
// Free: return a thing to the unused pile FLOATING -> UNUSED
//
// Add: add to reality FLOATING -> WORLD
// Rem: remove from reality WORLD -> FLOATING
//
// Make: spring fully formed into the world UNUSED -> WORLD
// Del: terminate with extreme prejudice WORLD -> UNUSED
//
// For things for which there is no real FLOATING state, Del and Make are used.
// Basically, whenever you have Rem'ed something, assume that it is
// still floating and that you must do something more to it.
//
// Object system functions tend to have names beginning with "Obj."
// If one main argument is being passed in, and it would otherwise
// be unclear what type of argument it is, that type is then put in the name.
// Then follow any additional nouns and verbs describing what the function does.
// All Objs, ObjRefs, ObjSpecs, etc. are referred to by their ID's.
// Thus, ObjRefLinkDel deletes a link from an ObjRef to an Obj and is passed an ObjRefID.
//
//////////////////////////////
#include <string.h>
#include "objects.h"
#include "map.h"
/*
#define DBG_Check(x) DBG(DSRC_OBJECTS_Check,x)
#define SpewCheck(x) Spew(DSRC_OBJECTS_Check,x)
#define DBG_Anal(x) DBG(DSRC_OBJECTS_Anal,x)
#define SpewAnal(x) Spew(DSRC_OBJECTS_Anal,x)
#define DBG_Report(x) DBG(DSRC_OBJECTS_Report,x)
#define SpewReport(x) Spew(DSRC_OBJECTS_Report,x)
#define DBG_Hash(x) DBG(DSRC_OBJECTS_Hash,x)
#define SpewHash(x) Spew(DSRC_OBJECTS_Hash,x)
*/
// See objects.h and objapp.h for a description of global variables
#ifdef SUPPORT_VERSION_26_OBJS
old_Obj old_objs[NUM_OBJECTS];
#endif
#ifdef THESE_WERENT_IN_STATIC_S_BUT_THEY_ARE_SO_THESE_ARENT_REALLY_HERE
Obj objs[NUM_OBJECTS];
ObjRef objRefs[NUM_REF_OBJECTS];
uchar objsDealt[NUM_OBJECTS / 8];
#endif
ObjLocState objLocStates[MAX_OBJS_CHANGING];
uchar numObjLocStates;
#ifdef HASH_OBJECTS
ObjHashElem objHashTable[OBJ_HASH_ENTRIES];
uchar ObjDeleteHashElem(ObjRefStateBin bin);
#ifndef USE_FUNCTION_FOR_HASH_GET
ObjHashElemID HASHENTRY;
#endif
#endif // HASH_OBJECTS
static void ObjRefRem(ObjRefID ref);
static uchar ObjLinkMake(ObjRefID ref, ObjID obj);
static ObjID ObjRefLinkDel(ObjRefID ref);
static uchar ObjRefAdd(ObjRefID ref, ObjRefState refstate);
static uchar ObjDelRefs(ObjID ID);
static ObjID ObjGrab(void);
static uchar ObjFree(ObjID ref);
static ObjRefID ObjRefGrab(void);
static ObjID ObjRefFree(ObjRefID ref, uchar cleanup);
static ObjSpecID ObjSpecGrab(ObjClass obclass);
static uchar ObjSpecFree(ObjClass obclass, ObjSpecID id);
static uchar ObjAndSpecFree(ObjID obj);
////////////////////////////////////////////////////////////
//
// PUBLIC FUNCTIONS
//
////////////////////////////////////////////////////////////
//////////////////////////////
//
// Initializes all object stuff
//
// This entire function should be made faster, as it is currently quite stupid.
//
void ObjsInit(void) {
int i;
short c;
ObjSpecHeader *head;
ObjSpec *os;
// SpewReport (("ObjsInit ()\n"));
// clear out everything
LG_memset((void *)objs, 0, sizeof(Obj) * NUM_OBJECTS);
LG_memset((void *)objRefs, 0, sizeof(ObjRef) * NUM_REF_OBJECTS);
for (c = CLASS_FIRST; c < NUM_CLASSES; c++) {
head = &objSpecHeaders[c];
LG_memset((void *)head->data, 0, head->size * head->struct_size);
}
// set up the free chains for objects
for (i = 0; i < NUM_OBJECTS - 1; i++)
objs[i].next = i + 1;
objs[NUM_OBJECTS - 1].next = 0;
objs[0].headused = 0;
// set up the free chains for references
for (i = 0; i < NUM_REF_OBJECTS - 1; i++)
objRefs[i].next = i + 1;
objRefs[NUM_REF_OBJECTS - 1].next = 0;
// set up the free chains for class-specific info
for (c = CLASS_FIRST; c < NUM_CLASSES; c++) {
head = &objSpecHeaders[c];
((ObjSpec *)(head->data))->bits.id = 0; // really head of used chain
for (i = 0; i < head->size - 1; i++) {
os = (ObjSpec *)(head->data + i * head->struct_size);
os->next = i + 1;
}
((ObjSpec *)(head->data + (head->size - 1) * head->struct_size))->next = 0;
}
#ifdef HASH_OBJECTS
// set up the free chains for the hash table
LG_memset((void *)objHashTable, 0, sizeof(ObjHashElem) * OBJ_HASH_ENTRIES);
for (i = 0; i < OBJ_HASH_HEAD_ENTRIES_START - 1; i++)
objHashTable[i].next = i + 1;
objHashTable[OBJ_HASH_HEAD_ENTRIES_START - 1].next = 0;
#endif
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjsInit");
}
//////////////////////////////
//
// Finds a free Obj and a free ObjSpec of the appropriate class, and
// links them together. Fills up the given fields. Returns whether it
// succeeded.
//
uchar ObjAndSpecGrab(ObjClass obclass, ObjID *id, ObjSpecID *specid) {
ObjSpecHeader *head;
// SpewReport (("ObjAndSpecGrab (class %d)\n", obclass));
if ((*id = ObjGrab()) == OBJ_NULL)
return FALSE;
if ((*specid = ObjSpecGrab(obclass)) == OBJ_SPEC_NULL) {
ObjFree(*id);
return FALSE;
}
objs[*id].obclass = obclass;
objs[*id].specID = *specid;
head = &objSpecHeaders[obclass];
((ObjSpec *)(head->data + *specid * head->struct_size))->bits.id = *id;
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjAndSpecGrab");
return TRUE;
}
//////////////////////////////
//
// Sets the given fields of an object appropriately, and makes it active.
// Currently always returns TRUE.
//
uchar ObjPlace(ObjID id, ObjLoc *loc) {
// DBG_Report ({
// char str[80];
// ObjLocSprint (str, *loc);
// SpewReport (("ObjPlace (%s)\n", str));
//})
ObjLocCopy(*loc, objs[id].loc);
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjPlace");
return TRUE;
}
//////////////////////////////
//
// Removes the specified ObjRef from the map,
// destroys its reference to its Obj,
// and then destroys it.
//
// Quite a vicious little function really.
//
ObjID ObjRefDel(ObjRefID ref) {
uchar tmp;
// SpewReport (("ObjRefDel (ref %d)\n", ref));
ObjRefRem(ref);
tmp = ObjRefFree(ref, TRUE);
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjRefDel");
return tmp;
}
//////////////////////////////
//
// Gets a free ObjRef, assigns it to the given Obj,
// and puts it in the world in the given place.
//
// Returns the ObjRefID used, or OBJ_REF_NULL if it
// couldn't do it for any reason.
//
ObjRefID ObjRefMake(ObjID obj, ObjRefState refstate) {
ObjRefID ref;
uchar ok;
// DBG_Report ({
// char str[80];
// ObjRefStateSprint (str, refstate);
// SpewReport (("ObjRefMake (obj %d %s)\n", obj, str));
//})
if ((ref = ObjRefGrab()) == OBJ_REF_NULL)
return OBJ_REF_NULL;
ok = ObjLinkMake(ref, obj);
// DBG_Check({
// if (!ok)
// {
// Warning (("Could not make link from ObjRef %d to Obj %d\n", ref, obj));
// ObjRefFree (ref, TRUE);
// return OBJ_REF_NULL;
// }
//})
ok = ObjRefAdd(ref, refstate);
// DBG_Check({
// if (!ok)
// {
// char str[80];
// ObjRefStateSprint (str, refstate);
// Warning (("Could not add ObjRef %d to %s\n", ref, str));
// ObjRefFree (ref, TRUE);
// return OBJ_REF_NULL;
// }
//})
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjRefMake");
return ref;
}
//////////////////////////////
//
// Deletes the given obj and all its references.
// Returns whether success was achieved in this quest.
//
uchar ObjDel(ObjID obj) {
uchar ok;
// SpewReport (("ObjDel (obj %d)\n", obj));
ok = ObjDelRefs(obj);
// DBG_Check ({
// if (!ok)
// {
// Warning (("Could not delete refs to Obj %d\n", obj));
// return FALSE;
// }
//})
ok = ObjAndSpecFree(obj);
// DBG_Check ({
// if (!ok)
// {
// Warning (("Could not free obj & spec for Obj %d\n", obj));
// return FALSE;
// }
//})
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjDel");
return TRUE;
}
//////////////////////////////
//
// Updates the location of a moving object.
// Returns whether it was successful in doing so.
//
// The given ObjLocState contains information about the new location of the
// object. We know the old location of the object by following ref pointers
// around. This function updates all the ObjRefs's referring to that object
// correctly.
//
uchar ObjUpdateLocs(ObjLocState *olsp) {
ObjID obj; // this object
ObjRefID ref; // each reference of it
ObjRefID firstref; // first ref that refers to it
ObjRefState *newrefs; // squares the object will soon be in
ObjRefState in[MAX_REFS_PER_OBJ]; // places moved into
ObjRefID out[MAX_REFS_PER_OBJ]; // old references of the object
int incount = 0; // number of places moved into
int outcount = 0; // number of places moved out of
int newcount; // # of new square being moved into
ObjRefState *stCur; // current place being checked
int i; // loopy loopy
// SpewReport (("ObjUpdateLocs (obj %d)\n", olsp->obj));
// DBG_Report ({
// char str[80];
// newrefs = olsp->refs;
// while (!ObjRefStateBinCheckNull(newrefs->bin))
// {
// ObjRefStateSprint (str, *newrefs);
// SpewReport (("[%s] ", str));
// newrefs++;
// }
// SpewReport (("\n"));
// })
// Get some data first
obj = olsp->obj;
newrefs = olsp->refs;
ObjLocCopy(olsp->loc, objs[obj].loc);
// Figure out where we are now
outcount = 0;
firstref = ref = objs[obj].ref;
if (firstref != OBJ_REF_NULL) {
// We use a do-while so that we don't fail the loop condition
// at the very beginning of the loop.
do {
out[outcount++] = ref;
ref = objRefs[ref].nextref;
} while (ref != firstref);
}
// Now, for each square we will be in,
//
// - if the bin is in out[], then we are staying in it;
// update its info field and then delete it from out[]
//
// - if the bin is not in out[], then we are moving into it;
// add it to in[]
//
// At the end of the following loop, all the bins we are moving out of
// will be in out[], and all the bins we are moving into will be in in[].
incount = 0;
for (newcount = 0; !ObjRefStateBinCheckNull(newrefs[newcount].bin); newcount++) {
stCur = &newrefs[newcount];
// Check all bins in out[] for a match
for (i = 0; i < outcount; i++) {
if (ObjRefStateBinEqual(objRefs[out[i]].state.bin, stCur->bin)) {
#ifndef NO_OBJ_REF_STATE_INFO
objRefs[out[i]].state.info = stCur->info; // update info
#endif
out[i] = out[--outcount]; // delete this bin from out
goto found_bin_in_out; // go back to the outer loop
}
}
// this bin was not in out[], so we add it to in[].
in[incount++] = *stCur;
found_bin_in_out:;
}
// Now we must delete the references in out[] and add the ones in in[].
// Note that we might be able to speed up the following code if we had to
// by, instead of freeing RefID's and then grabbing them again, reusing
// them somehow. It is unclear how often both out[] and in[] will have
// stuff in them; if this case occurs often it would probably be worth
// speeding up.
for (i = 0; i < outcount; i++)
ObjRefDel(out[i]);
for (i = 0; i < incount; i++)
if (!ObjRefMake(obj, in[i]))
return FALSE;
// temp
// if (!ObjSysOkay())
// DebugString("Obj Sys bad after ObjUpdateLocs");
return TRUE;
}
#ifdef HASH_OBJECTS
//////////////////////////////
//
// Return a free hash table element, or 0 if none.
//
ObjHashElemID ObjGrabHashEntry(void) {
ObjHashElemID elem = objHashTable[0].next;
if (elem == 0)
return elem;
objHashTable[0].next = objHashTable[elem].next;
return elem;
}
//////////////////////////////
//
// Free up the given hash table element.
//
void ObjFreeHashEntry(ObjHashElemID elem) {
objHashTable[elem].ref = 0; // make sure nobody thinks
// there's something here
objHashTable[elem].next = objHashTable[0].next;
objHashTable[0].next = elem;
}
//////////////////////////////
//
// Returns the ID of a hash entry pointing to the contents of the given bin.
// If create is true, then return an entry even if once doesn't exist now.
// It will then have a ref field of NULL, signifying that it was just
// created. You must immediately set the ref's StateBin correctly.
//
#ifdef USE_FUNCTION_FOR_HASH_GET
ObjHashElemID ObjGetHashElem(ObjRefStateBin bin, uchar create) {
ObjHashElemID entry;
ObjHashElemID firstentry, nextentry;
entry = OBJ_HASH_FUNC(bin);
if (objHashTable[entry].ref == OBJ_REF_NULL) {
// Nothing at all at this hash location
if (!create)
return 0;
else
return entry;
}
// Check if the first element is correct
if (ObjRefStateBinEqual(objRefs[objHashTable[entry].ref].state.bin, bin))
return entry;
// Go through the list looking for the right object chain
firstentry = entry;
while (objHashTable[entry].next != 0) {
nextentry = objHashTable[entry].next;
if (ObjRefStateBinEqual(objRefs[objHashTable[nextentry].ref].state.bin, bin)) {
ObjRefID tmpref;
// Move nextentry to the top
tmpref = objHashTable[firstentry].ref;
objHashTable[firstentry].ref = objHashTable[nextentry].ref;
objHashTable[nextentry].ref = tmpref;
return firstentry;
}
entry = nextentry;
}
// Couldn't find it
if (!create)
return 0;
// Make a new one
if ((nextentry = ObjGrabHashEntry()) == 0)
return 0;
objHashTable[entry].next = nextentry;
objHashTable[nextentry].ref = objHashTable[firstentry].ref;
objHashTable[nextentry].next = 0;
objHashTable[firstentry].ref = OBJ_REF_NULL;
return firstentry;
}
#else
//////////////////////////////
//
// This is just the special case of the ObjGetHashElem() function, which is now
// called from the macro version of ObjGetHashElem() when it sees that it needs
// it. At this point, the entry is used, but not by us, so we go down the
// chain looking for the right entry.
//
ObjHashElemID ObjGetHashElemFromChain(ObjRefStateBin bin, uchar create, ObjHashElemID firstentry) {
ObjHashElemID entry = firstentry;
ObjHashElemID nextentry;
// Go through the list looking for the right object chain
while (objHashTable[entry].next != 0) {
nextentry = objHashTable[entry].next;
if (ObjRefStateBinEqual(objRefs[objHashTable[nextentry].ref].state.bin, bin)) {
ObjRefID tmpref;
// Move nextentry to the top
tmpref = objHashTable[firstentry].ref;
objHashTable[firstentry].ref = objHashTable[nextentry].ref;
objHashTable[nextentry].ref = tmpref;
return firstentry;
}
entry = nextentry;
}
// Couldn't find it
if (!create)
return 0;
// Make a new one
if ((nextentry = ObjGrabHashEntry()) == 0)
return 0;
objHashTable[entry].next = nextentry;
objHashTable[nextentry].ref = objHashTable[firstentry].ref;
objHashTable[nextentry].next = 0;
objHashTable[firstentry].ref = OBJ_REF_NULL;
return firstentry;
}
#endif
//////////////////////////////
//
// Deletes the entry in the hash table corresponding to the ref
// chain at the given bin. Returns FALSE if there was nothing to delete.
//
uchar ObjDeleteHashElem(ObjRefStateBin bin) {
ObjHashElemID firstentry = OBJ_HASH_FUNC(bin);
ObjHashElemID entry, nextentry;
if (objHashTable[firstentry].ref == OBJ_REF_NULL)
return FALSE;
// This should be true if we always move to the front of the list like above
if (ObjRefStateBinEqual(objRefs[objHashTable[firstentry].ref].state.bin, bin)) {
if ((nextentry = objHashTable[firstentry].next) == 0) {
// This was the only one
objHashTable[firstentry].ref = OBJ_REF_NULL;
return TRUE;
}
objHashTable[firstentry].ref = objHashTable[nextentry].ref;
objHashTable[firstentry].next = objHashTable[nextentry].next;
ObjFreeHashEntry(nextentry);
return TRUE;
}
entry = firstentry;
nextentry = objHashTable[entry].next;
while (nextentry != 0) {
if (ObjRefStateBinEqual(objRefs[objHashTable[nextentry].ref].state.bin, bin)) {
objHashTable[entry].next = objHashTable[nextentry].next;
ObjFreeHashEntry(nextentry);
return TRUE;
}
entry = nextentry;
nextentry = objHashTable[entry].next;
}
return FALSE;
}
static int hash_i;
void ObjHashIteratorInit(void) { hash_i = 0; }
uchar ObjHashIterator(ObjRefID *ref) {
while (hash_i < OBJ_HASH_ENTRIES && objHashTable[hash_i].ref == 0)
hash_i++;
if (hash_i == OBJ_HASH_ENTRIES)
return FALSE;
*ref = objHashTable[hash_i++].ref;
return TRUE;
}
#define MAX_CHAIN_LENGTH 10 // let's hope it gets no higher
int numlengths[MAX_CHAIN_LENGTH + 1];
//////////////////////////////
//
//
//
ObjHashStats(void) {
int i;
for (i = 0; i <= MAX_CHAIN_LENGTH; i++)
numlengths[i] = 0;
for (i = OBJ_HASH_HEAD_ENTRIES_START; i < OBJ_HASH_ENTRIES; i++) {
int j = i, length = 0;
if (objHashTable[i].ref != 0) {
while (j != 0)
length++, j = objHashTable[j].next;
}
if (length > MAX_CHAIN_LENGTH)
length = MAX_CHAIN_LENGTH;
numlengths[length]++;
}
for (i = 0; i <= MAX_CHAIN_LENGTH; i++) {
if (numlengths[i] > 0)
SpewHash(("Object hash chains of length %d: %d\n", i, numlengths[i]));
}
}
#endif // HASH_OBJECTS
////////////////////////////////////////////////////////////
//
// DEBUGGING FUNCTIONS
//
////////////////////////////////////////////////////////////
#define OBJ_NO_STATE 0
#define OBJ_FREE 1
#define OBJ_USED 2
#define OBJ_IN_MAP 4
//////////////////////////////
//
// Check whether the object system is consistent.
//
uchar ObjSysOkay(void) {
char usedObj[NUM_OBJECTS];
char usedRef[NUM_REF_OBJECTS];
ObjID cur;
ObjSpecHeader *head;
int i, j;
ObjRefStateBin refbin;
ObjRefID ref;
// 1. Every Obj is in either the free chain or the used chain,
// and does not appear twice in any chain.
LG_memset(usedObj, 0, NUM_OBJECTS);
// SpewAnal (("Free Objs: "));
// DBG_Anal ({RangeInit ();})
cur = objs[OBJ_NULL].next;
while (cur) {
// DBG_Anal ({RangeAdd (cur);})
if (cur < 0 || cur >= NUM_OBJECTS) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Invalid ID in free chain", __FUNCTION__); // cur
return FALSE;
}
if (objs[cur].active) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Active Obj in free chain", __FUNCTION__); // objs[cur]
return FALSE;
}
if (usedObj[cur] == OBJ_FREE) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Obj is free more than once", __FUNCTION__); // usedObj[cur]
return FALSE;
}
usedObj[cur] = OBJ_FREE;
cur = objs[cur].next;
}
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
// SpewAnal (("Used Objs: "));
// DBG_Anal ({RangeInit ();})
cur = objs[OBJ_NULL].ref;
while (cur) {
// DBG_Anal ({RangeAdd (cur);})
if (cur < 0 || cur >= NUM_OBJECTS) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Invalid ID in Obj used chain", __FUNCTION__); // cur
return FALSE;
}
if (usedObj[cur] == OBJ_FREE) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Obj is free and used chain", __FUNCTION__); // cur
return FALSE;
}
if (usedObj[cur] == OBJ_USED) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Obj is used twice", __FUNCTION__); // cur
return FALSE;
}
usedObj[cur] = OBJ_USED;
cur = objs[cur].next;
}
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
for (cur = 1; cur < NUM_OBJECTS; cur++) {
if (usedObj[cur] == 0) {
DEBUG("%s: Obj is neither free nor used", __FUNCTION__); // cur
return FALSE;
}
}
// 3. No ObjRef occurs twice in the free chain.
LG_memset(usedRef, 0, NUM_REF_OBJECTS);
cur = objRefs[OBJ_REF_NULL].next;
// SpewAnal (("Free ObjRefs: "));
// DBG_Anal ({RangeInit ();})
while (cur) {
// DBG_Anal ({RangeAdd (cur);})
if (cur < 0 || cur >= NUM_REF_OBJECTS) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: Invalid ID in ObjRef free chain", __FUNCTION__); // cur
return FALSE;
}
if (usedRef[cur] == OBJ_FREE) {
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
DEBUG("%s: ObjRef is free more than once", __FUNCTION__); // cur
return FALSE;
}
usedRef[cur] = OBJ_FREE;
cur = objRefs[cur].next;
}
// DBG_Anal ({RangeFlush ();}) SpewAnal (("%s\n", range_str));
// 2. Every ObjSpec is in either the free chain or the used chain, and does
// not appear twice in any chain.
for (i = CLASS_FIRST; i < NUM_CLASSES; i++) {
LG_memset(usedObj, 0, NUM_OBJECTS);
head = &objSpecHeaders[i];
cur = ((ObjSpec *)head->data)->next;
while (cur) {
if (cur < 0 || cur >= head->size) {
DEBUG("%s: Invalid ID (cur) in Class (i) free chain", __FUNCTION__);
return FALSE;
}
if (usedObj[cur] == OBJ_FREE) {
DEBUG("%s: Class (i) ObjSpec (cur) is free more than once", __FUNCTION__);
return FALSE;
}
usedObj[cur] = OBJ_FREE;
cur = ((ObjSpec *)(head->data + cur * head->struct_size))->next;
}
cur = ((ObjSpec *)head->data)->bits.id;
while (cur) {
if (cur < 0 || cur >= head->size) {
DEBUG("%s: Invalid ID (cur) in Class (i) used chain", __FUNCTION__);
return FALSE;
}
if (usedObj[cur] == OBJ_FREE) {
DEBUG("%s: Class (i) ObjSpec (cur) is free and used", __FUNCTION__);
return FALSE;
}
if (usedObj[cur] == OBJ_USED) {
DEBUG("%s: Class (i) ObjSpec (cur) is used twice", __FUNCTION__);
return FALSE;
}
usedObj[cur] = OBJ_USED;
cur = ((ObjSpec *)(head->data + cur * head->struct_size))->next;
}
for (cur = 1; cur < head->size; cur++) {
if (usedObj[cur] == 0) {
DEBUG("%s: Class (i) ObjSpec (cur) is neither free nor used", __FUNCTION__);
return FALSE;
}
}
}
// 4. All active ObjRefs occur exactly once in the map.
// 5. All active ObjRefs point to the map element in which they occur.
// 6. All active ObjRefs point to active Objs.
#ifdef HASH_OBJECTS
ObjHashIteratorInit();
#else
ObjRefStateBinIteratorInit();
#endif
#ifdef HASH_OBJECTS
while (ObjHashIterator(&ref))
#else
while (ObjRefStateBinIterator(&refbin))
#endif
{
#ifdef HASH_OBJECTS
refbin = objRefs[ref].state.bin;
#else
ref = ObjRefHead(refbin);
#endif
// DBG_Anal ({
// if (ref != OBJ_REF_NULL)
// {
// ObjRefStateBinSprint (str, refbin);
// SpewAnal (("Contents of [%s]: ", str));
// }
// })
while (ref != OBJ_REF_NULL) {
#ifndef NO_OBJ_REF_STATE_INFO
// DBG_Anal ({
// ObjRefStateInfoSprint (str, objRefs[ref].state.info);
// SpewAnal ((" ObjRef %3d [%s] -> Obj %3d\n", ref, str, objRefs[ref].obj));
// })
#else
// DBG_Anal ({
// SpewAnal ((" ObjRef %3d -> Obj %3d\n", ref, objRefs[ref].obj));
// })
#endif
if (usedRef[ref] & OBJ_FREE) {
ObjRefStateBinSprint(str, refbin);
DEBUG("%s: ObjRef (ref) is free and in map bin (str)", __FUNCTION__);
return FALSE;
}
if (usedRef[ref] & OBJ_IN_MAP) {
ObjRefStateBinSprint(str, refbin);
DEBUG("%s: ObjRef (ref) exists in two bins (one is str)", __FUNCTION__);
return FALSE;
}
if (!ObjRefStateBinEqual(objRefs[ref].state.bin, refbin)) {
ObjRefStateBinSprint(str, refbin);
ObjRefStateBinSprint(str2, objRefs[ref].state.bin);
DEBUG("%s: ObjRef (ref) thinks it is in (str2) but is in (str)", __FUNCTION__);
return FALSE;
}
if (objRefs[ref].obj == OBJ_NULL) {
ObjRefStateBinSprint(str, refbin);
DEBUG("%s: ObjRef (ref) in (str) points to null Obj", __FUNCTION__);
return FALSE;
}
usedRef[ref] |= OBJ_IN_MAP;
ref = objRefs[ref].next;
}
}
// 7. All ObjRefs referring to a single Obj reside in distinct map elements.
// 8. The links between an Obj and its ObjSpec are valid.
// 9. The links between an Obj and its ObjRefs are valid.
cur = objs[OBJ_NULL].headused;
while (cur != OBJ_NULL) {
ObjRefStateBin refbins[MAX_REFS_PER_OBJ];
ObjRefID firstref;
ObjRefID ref;
int i = 0;
if (!objs[cur].active)
goto done_checking_obj;
ref = firstref = objs[cur].ref;
if (ref == OBJ_REF_NULL)
goto done_checking_refs;
do {
if (i == MAX_REFS_PER_OBJ) {
DEBUG("%s: Too many ObjRefs refer to Obj (cur)", __FUNCTION__);
return FALSE;
}
if (objRefs[ref].obj != cur) {
DEBUG("%s: Obj (cur) points to ObjRef (ref) but not vice versa", __FUNCTION__);
return FALSE;
}
for (j = 0; j < i; j++)
if (ObjRefStateBinEqual(refbins[j], objRefs[ref].state.bin))
return FALSE;
refbins[i++] = objRefs[ref].state.bin;
ref = objRefs[ref].nextref;
} while (ref != firstref);
done_checking_refs:
if (objs[cur].specID != OBJ_SPEC_NULL) {
char *data;
ObjSpecHeader *head;
if (objs[cur].obclass < 0 || objs[cur].obclass >= NUM_CLASSES) {
DEBUG("%s: Obj (cur) has invalid obclass (objs[cur].obclass)", __FUNCTION__);
return FALSE;
}
head = &objSpecHeaders[objs[cur].obclass];
data = head->data;
if (((ObjSpec *)(data + head->struct_size * objs[cur].specID))->bits.id != cur ||
((ObjSpec *)(data + head->struct_size * objs[cur].specID))->bits.tile == TRUE) {
DEBUG("%s: Obj (cur) obclass-specific data does not point back to it", __FUNCTION__);
return FALSE;
}
}
done_checking_obj:
cur = objs[cur].next;
}
#ifdef HASH_OBJECTS
DBG_Hash({ ObjHashStats(); })
#endif
return TRUE;
}
////////////////////////////////////////////////////////////
//
// STATIC FUNCTIONS
//
// Some of these may want to become public at some point.
// However, before making one of these functions public, first
// make sure that there isn't already a public interface that you
// can use instead.
//
////////////////////////////////////////////////////////////
//////////////////////////////
//
// Returns a ObjID that is not currently being used
// or OBJ_NULL If there are none
//
// The caller is responsible for setting the fieds of the Obj, including active.
// This function does clear the ref field to ensure we don't follow bad pointers around.
//
static ObjID ObjGrab(void) {
ObjID obj; // the next free ObjID
// SpewReport (("ObjGrab ()\n"));
if (objs[OBJ_NULL].next == OBJ_NULL) // all gone
{
// Warning (("No room in ObjGrab\n"));
return OBJ_NULL;
}
// remove the head of the free chain and return it
obj = objs[OBJ_NULL].next;
objs[OBJ_NULL].next = objs[obj].next;
// and put obj at the head of the used chain
objs[obj].next = objs[OBJ_NULL].headused;
objs[objs[OBJ_NULL].headused].prev = obj;
objs[obj].prev = OBJ_NULL;
objs[OBJ_NULL].headused = obj;
objs[obj].ref = OBJ_REF_NULL;
ObjInfoInit(&objs[obj].info);
return obj;
}
//////////////////////////////
//
// Frees up the given object
// Returns FALSE (and refuses to free the object)
// if there are objRefs referring to the object
//
static uchar ObjFree(ObjID obj) {
// SpewReport (("ObjFree (obj %d)\n", obj));
// DBG_Check ({
// // Check if something still depends on this to be valid
// if (objs[obj].active && objs[obj].ref != OBJ_REF_NULL)
// {
// Warning (("Tried to free obj %d which was depended on\n", obj));
// return FALSE;
// }
//})
objs[obj].active = FALSE;
// take obj out of the used chain
if (objs[obj].prev == OBJ_NULL)
objs[OBJ_NULL].headused = objs[obj].next;
else
objs[objs[obj].prev].next = objs[obj].next;
objs[objs[obj].next].prev = objs[obj].prev;
// don't need to clear objs[obj].next since we are resetting it immediately
// and put it back at the head of the free chain
objs[obj].next = objs[OBJ_NULL].next;
objs[OBJ_NULL].next = obj;
return TRUE;
}
//////////////////////////////
//
// Returns an ObjRefID that is not currently being used
// or OBJ_REF_NULL if there are none
//
static ObjRefID ObjRefGrab(void) {
ObjRefID thisobj; // the next free ObjRefID
// SpewReport (("ObjRefGrab ()\n"));
if (objRefs[OBJ_REF_NULL].next == OBJ_REF_NULL) {
// Warning (("No room in ObjRefGrab\n"));
return OBJ_REF_NULL;
}
// remove the head of the free chain and return it
thisobj = objRefs[OBJ_REF_NULL].next;
objRefs[OBJ_REF_NULL].next = objRefs[thisobj].next;
objRefs[thisobj].obj = OBJ_NULL;
objRefs[thisobj].next = OBJ_REF_NULL;
objRefs[thisobj].nextref = OBJ_REF_NULL;
return thisobj;
}
//////////////////////////////
//
// Frees up the space used by the ObjRef referred to by ref.
//
// If cleanup is TRUE, also deletes the reference of the ObjRef to the Obj.
// If this is the last reference to an Obj, returns that Obj's ID.
//
static ObjID ObjRefFree(ObjRefID ref, uchar cleanup) {
ObjID obj; // the object ref refers to
// SpewReport (("ObjRefFree (ref %d cleanup %d)\n", ref, cleanup));
if (cleanup)
obj = ObjRefLinkDel(ref); // remove the link
else
obj = OBJ_REF_NULL;
objRefs[ref].next = objRefs[OBJ_NULL].next;
objRefs[OBJ_REF_NULL].next = ref;
return obj;
}
//////////////////////////////
//
// Returns an ObjSpecID of the specified obclass that is not currently being
// used, or OBJ_SPEC_NULL if there are none available
//
static ObjSpecID ObjSpecGrab(ObjClass obclass) {
char *data;
ObjSpecHeader *head;
ObjSpecID thisid;
ObjSpec *spec0, *thisspec;
// SpewReport (("ObjSpecGrab (obclass %d)\n", obclass));
// DBG_Check ({
// if (obclass >= NUM_CLASSES)
// {
// Warning (("Invalid obclass %d in ObjSpecGrab\n", obclass));
// return OBJ_SPEC_NULL;
// }
//})
head = &objSpecHeaders[obclass];
data = head->data;
spec0 = (ObjSpec *)data;
if (spec0->next == OBJ_SPEC_NULL)
return OBJ_SPEC_NULL;
// remove the head of the free chain and return it
thisid = spec0->next;
thisspec = (ObjSpec *)(data + head->struct_size * thisid);
spec0->next = thisspec->next;
// and put this at the head of the used chain
thisspec->next = spec0->headused;
((ObjSpec *)(data + head->struct_size * spec0->headused))->prev = thisid;
thisspec->prev = OBJ_SPEC_NULL;
spec0->headused = thisid;
return thisid;
}
#ifdef COMPRESS_OBJSPECS
ObjSpecID HeaderObjSpecGrab(ObjClass obclass, ObjSpecHeader *head) {
char *data;
ObjSpecID thisid;
ObjSpec *spec0, *thisspec;
SpewReport(("ObjSpecGrab (obclass %d)\n", obclass));
DBG_Check({
if (obclass >= NUM_CLASSES) {
Warning(("Invalid obclass %d in ObjSpecGrab\n", obclass));
return OBJ_SPEC_NULL;
}
})
data = head->data;
spec0 = (ObjSpec *)data;
if (spec0->next == OBJ_SPEC_NULL)
return OBJ_SPEC_NULL;
// remove the head of the free chain and return it
thisid = spec0->next;
thisspec = (ObjSpec *)(data + head->struct_size * thisid);
spec0->next = thisspec->next;
// and put this at the head of the used chain
thisspec->next = spec0->headused;
((ObjSpec *)(data + head->struct_size * spec0->headused))->prev = thisid;
thisspec->prev = OBJ_SPEC_NULL;
spec0->headused = thisid;
return thisid;
}
#endif
//////////////////////////////
//
// Frees up the space used by the ObjSpec in the specified class
// referred to by id.
//
static uchar ObjSpecFree(ObjClass obclass, ObjSpecID id) {
char *data;
ObjSpecHeader *head;
ObjSpec *spec0, *thisspec;
// SpewReport (("ObjSpecFree (obclass %d, specid %d)\n", obclass, id));
// DBG_Check ({
// if (obclass >= NUM_CLASSES)
// {
// Warning (("Invalid obclass %d in ObjSpecFree\n", obclass));
// return FALSE;
// }
//})
head = &objSpecHeaders[obclass];
data = head->data;
spec0 = (ObjSpec *)&data[0];
thisspec = (ObjSpec *)(data + head->struct_size * id);
// take this out of the used chain
if (thisspec->prev == OBJ_SPEC_NULL)
spec0->headused = thisspec->next;
else
((ObjSpec *)(data + head->struct_size * thisspec->prev))->next = thisspec->next;
((ObjSpec *)(data + head->struct_size * thisspec->next))->prev = thisspec->prev;
// don't need to clear thisspec->next since we are resetting it immediately
// and put it back at the head of the free chain
thisspec->next = spec0->headfree;
spec0->headfree = id;
return TRUE;
}
#ifdef COMPRESS_OBJSPECS
uchar HeaderObjSpecFree(ObjClass obclass, ObjSpecID id, ObjSpecHeader *head) {
char *data;
ObjSpec *spec0, *thisspec;
SpewReport(("ObjSpecFree (obclass %d, specid %d)\n", obclass, id));
DBG_Check({
if (obclass >= NUM_CLASSES) {
Warning(("Invalid obclass %d in ObjSpecFree\n", obclass));
return FALSE;
}
})
data = head->data;
spec0 = (ObjSpec *)&data[0];
thisspec = (ObjSpec *)(data + head->struct_size * id);
// take this out of the used chain
if (thisspec->prev == OBJ_SPEC_NULL)
spec0->headused = thisspec->next;
else
((ObjSpec *)(data + head->struct_size * thisspec->prev))->next = thisspec->next;
((ObjSpec *)(data + head->struct_size * thisspec->next))->prev = thisspec->prev;
// don't need to clear thisspec->next since we are resetting it immediately
// and put it back at the head of the free chain
thisspec->next = spec0->headfree;
spec0->headfree = id;
return TRUE;
}
#endif
#ifdef COMPRESS_OBJSPECS
uchar HeaderObjSpecCopy(ObjClass cls, ObjSpecID old, ObjSpecID new, ObjSpecHeader *head) {
char *data;
ObjSpec *spec0;
int size;
SpewReport(("ObjSpecCopy (obclass %d)\n", cls));
DBG_Check({
if (cls >= NUM_CLASSES) {
Warning(("Invalid obclass %d in ObjSpecCopy\n", cls));
return FALSE;
}
})
data = head->data;
spec0 = (ObjSpec *)data;
// the ObjSpec (generic) part of the new spec is already set; we
// need to copy the rest
if ((size = head->struct_size - sizeof(ObjSpec)) > 0) {
LG_memcpy(data + head->struct_size * new + sizeof(ObjSpec), data + head->struct_size * old + sizeof(ObjSpec),
size);
}
return TRUE;
}
#endif
//////////////////////////////
//
// Removes the given ObjRef from the object list in a map bin.
//
static void ObjRefRem(ObjRefID ref) {
ObjRefID *ptr; // what we must change to splice ref out
#ifdef HASH_OBJECTS
ObjHashElemID hash_entry;
#endif
// SpewReport (("ObjRefRem (ref %d)\n", ref));
#ifdef HASH_OBJECTS
if ((hash_entry = ObjGetHashElem(objRefs[ref].state.bin, FALSE)) == 0) {
Warning(("Tried to remove ref %d not in hash table in ObjRefRem\n", ref));
return;
}
if (objHashTable[hash_entry].ref == ref) {
if (objRefs[ref].next == OBJ_REF_NULL) {
// This was the only one
if (!ObjDeleteHashElem(objRefs[ref].state.bin)) {
Warning(("Couldn't delete refchain %d from hash table in ObjRefRem\n", ref));
return;
}
} else {
objHashTable[hash_entry].ref = objRefs[ref].next;
}
objRefs[ref].next = OBJ_REF_NULL; // we are no longer in a chain
ObjRefStateBinSetNull(objRefs[ref].state.bin); // we are no longer in the world
return;
}
ptr = &objRefs[objHashTable[hash_entry].ref].next; // next field of head of ref chain
#else
ptr = &ObjRefHead(objRefs[ref].state.bin);
#endif
while (*ptr != ref)
ptr = &(objRefs[*ptr].next);
// ptr is now the address of an ObjRefID equal to ref.
// It could be the address of the obj field of a MapPoint
// or the address of the next field of an ObjRef
// Got it?
*ptr = objRefs[ref].next; // we have spliced ref out
objRefs[ref].next = OBJ_REF_NULL; // we are no longer in a chain
ObjRefStateBinSetNull(objRefs[ref].state.bin); // we are no longer in the world
}
//////////////////////////////
//
// Makes ref be a reference to obj
// Returns whether we could do it
//
static uchar ObjLinkMake(ObjRefID ref, ObjID obj) {
// SpewReport (("ObjLinkMake (ref %d, obj %d)\n", ref, obj));
// check that obj is a real object
// DBG_Check ({
// if (objs[obj].active == FALSE)
// {
// Warning (("Obj %d is inactive in ObjLinkMake\n", obj));
// return FALSE;
// }
//})
if (objs[obj].ref == OBJ_REF_NULL) {
// we are the first ObjRef to refer to obj
objs[obj].ref = ref;
objRefs[ref].nextref = ref;
} else {
// there are other references already
objRefs[ref].nextref = objRefs[objs[obj].ref].nextref;
objRefs[objs[obj].ref].nextref = ref;
}
// add the reference
objRefs[ref].obj = obj;
return TRUE;
}
//////////////////////////////
//
// Deletes the references of ref to its obj
// If this was the last reference to that obj,
// returns its ID, otherwise returns OBJ_NULL
//
static ObjID ObjRefLinkDel(ObjRefID ref) {
ObjID obj;
ObjRefID curref;
// SpewReport (("ObjRefLinkDel (ref %d)\n", ref));
obj = objRefs[ref].obj;
// DBG_Check ({
// // make sure we're actually referring to something
// if (obj == OBJ_NULL)
// {
// Warning (("ref %d refers to nothing in ObjRefLinkDel\n", ref));
// return OBJ_NULL;
// }
//})
if (objRefs[ref].nextref == ref) // last one
{
objs[obj].ref = OBJ_REF_NULL;
return obj;
} else {
// run around the circular list until we reach ourselves
// and splice ourselves out
curref = ref;
while (objRefs[curref].nextref != ref)
curref = objRefs[curref].nextref;
objRefs[curref].nextref = objRefs[ref].nextref;
objs[obj].ref = curref; // easier than checking objs[obj].ref
}
// delete the reference
objRefs[ref].obj = OBJ_NULL;
return OBJ_NULL;
}
//////////////////////////////
//
// Takes an ObjRef and adds it to the real world at the given place.
// This ObjRef must already refer to a valid Obj.
// Returns whether everything worked okay.
//
static uchar ObjRefAdd(ObjRefID ref, ObjRefState refstate) {
ObjID obj;
ObjRefID *refhead;
#ifdef HASH_OBJECTS
ObjHashElemID hash_entry;
#endif
// DBG_Report ({
// char str[80];
// ObjRefStateSprint (str, refstate);
// SpewReport (("ObjRefAdd (ref %d, %s)\n", ref, str));
// })
obj = objRefs[ref].obj;
// DBG_Check ({
// // make sure we point to a valid object
// if (obj == OBJ_NULL || objs[obj].active == FALSE)
// {
// Warning (("Ref %d points to invalid Obj %d in ObjRefAdd\n", ref, obj));
// return FALSE;
// }
//})
#ifdef HASH_OBJECTS
if ((hash_entry = ObjGetHashElem(refstate.bin, TRUE)) == 0) {
char str[80];
ObjRefStateBinSprint(str, refstate.bin);
Warning(("Could not create hash entry at %s in ObjRefAdd\n", str));
return FALSE;
}
refhead = &objHashTable[hash_entry].ref;
#else
refhead = &ObjRefHead(refstate.bin);
#endif
objRefs[ref].next = *refhead;
*refhead = ref;
ObjRefStateBinCopy(refstate.bin, objRefs[ref].state.bin);
#ifndef NO_OBJ_REF_STATE_INFO
ObjRefStateInfoCopy(refstate.info, objRefs[ref].state.info);
#endif
return TRUE;
}
//////////////////////////////
//
// Deletes all references to a given object.
// Returns whether the object really exists or not.
//
// NOTE: This is currently much slower than it could be because
// we delete references one at a time and clean up each time.
//
static uchar ObjDelRefs(ObjID obj) {
ObjRefID ref;
ObjRefID nextref = OBJ_REF_NULL;
// SpewReport (("ObjDelRefs (obj %d)\n", obj));
// DBG_Check ({
// if (objs[obj].active == FALSE)
// {
// Warning (("Obj %d inactive in ObjDelRefs\n", obj));
// return FALSE;
// }
//})
ref = objs[obj].ref;
if (ref == OBJ_REF_NULL)
return TRUE;
while (TRUE) {
nextref = objRefs[ref].nextref;
ObjRefRem(ref);
// objRefs[ref].obj = OBJ_NULL;
ObjRefFree(ref, TRUE);
if (ref == nextref)
break;
ref = nextref;
}
objs[obj].ref = OBJ_REF_NULL;
return TRUE;
}
//////////////////////////////
//
// Frees up the given object and its class-specific data.
// Returns FALSE (and refuses to free the object)
// if there are ObjRefs referring to the object.
//
static uchar ObjAndSpecFree(ObjID obj) {
ObjClass obclass;
ObjSpecID specID;
uchar ok;
// SpewReport (("ObjAndSpecFree (obj %d)\n", obj));
// The only place that things should be able to go wrong is in ObjFree,
// so we do that first
obclass = objs[obj].obclass;
specID = objs[obj].specID;
ok = ObjFree(obj);
// DBG_Check ({
// if (!ok)
// {
// Warning (("ObjFree (%d) failed in ObjAndSpecFree\n",obj));
// return FALSE;
// }
//})
ok = ObjSpecFree(obclass, specID);
// DBG_Check ({
// if (!ok)
// {
// Warning (("ObjSpecFree (%d, %d) failed in ObjAndSpecFree\n", obclass, specID));
// return FALSE;
// }
//})
return TRUE;
}