a5ec8db5f7
build / build (push) Failing after 5s
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.
1524 lines
44 KiB
C
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;
|
|
}
|