Files
scummvm-cursorfix/engines/glk/tads/tads2/object.cpp
2026-02-02 04:50:13 +01:00

1076 lines
30 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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/>.
*
*/
#include "glk/tads/tads2/object.h"
#include "glk/tads/tads2/error.h"
#include "glk/tads/tads2/memory_cache_heap.h"
#include "glk/tads/os_glk.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
/*
* Get a property WITHOUT INHERITANCE. The offset of the property's
* prpdef is returned. An offset of zero means the property wasn't
* found.
*/
uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
{
objdef *objptr;
prpdef *p;
int cnt;
uint retval; /* property offset, if we find it */
uint ignprop; /* ignored property - use if real property isn't found */
uchar pbuf[2]; /* property number in portable format */
uchar *indp = nullptr;
uchar *indbase;
int last;
int first;
int cur = 0;
oswp2(pbuf, prop); /* get property number in portable foramt */
objptr = (objdef *)mcmlck(mctx, objn); /* get a lock on the object */
ignprop = 0; /* assume we won't find ignored property */
cnt = objnprop(objptr); /* get number of properties defined */
retval = 0; /* presume failure */
if (objflg(objptr) & OBJFINDEX)
{
/* there's an index -> do a binary search through the index */
indbase = (uchar *)objpfre(objptr); /* find index */
first = 0;
last = cnt - 1;
for (;;)
{
if (first > last) break; /* crossed over -> not found */
cur = first + (last - first)/2; /* split the difference */
indp = indbase + cur*4; /* get pointer to this entry */
if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
{
retval = osrp2(indp + 2);
break;
}
else if (indp[0] < pbuf[0]
|| (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
first = (cur == first ? first + 1 : cur);
else
last = (cur == last ? last - 1 : cur);
}
/* ignore ignored and deleted properties if possible */
while (retval
&& ((prpflg(objptr + retval) & PRPFIGN) != 0
|| ((prpflg(objptr + retval) & PRPFDEL) != 0
&& (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
&& cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
{
indp += 4;
retval = osrp2(indp + 2);
}
if (retval && osrp2(objptr + retval) != prop)
assert(FALSE);
}
else
{
/* there's no index -> do sequential search through properties */
for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
{
/* if this is the property, and it's not being ignored, use it */
if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
{
if (prpflg(p) & PRPFIGN) /* this is ignored */
ignprop = objpofs(objptr, p); /* ... make a note of it */
else if ((prpflg(p) & PRPFDEL) != 0 /* it's deleted */
&& (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
/* simply skip it */ ;
else
{
retval = objpofs(objptr, p); /* this is the one */
break; /* we're done */
}
}
}
}
if (!retval) retval = ignprop; /* use ignored value if nothing else */
if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
mcmunlck(mctx, objn); /* done with object, so unlock it */
return(retval);
}
/* get the offset of the end of a property in an object */
uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
{
objdef *objp;
prpdef *propptr;
uint ofs;
uint valsiz;
/* get the start of the object */
ofs = objgetp(ctx, objn, prop, nullptr);
if (ofs == 0)
return 0;
/* get the object */
objp = mcmlck(ctx, (mcmon)objn);
/* get the property */
propptr = objofsp(objp, ofs);
/* get the data size */
valsiz = prpsize(propptr);
/* done with the object */
mcmunlck(ctx, (mcmon)objn);
/*
* return the ending offset - it's the starting offset plus the
* property header size plus the size of the property data
*/
return ofs + PRPHDRSIZ + valsiz;
}
/* determine whether an object is a descendant of another object */
static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
{
uchar *sc;
int cnt;
for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
sc += 2, --cnt)
{
int cursc = osrp2(sc);
int ret;
objdef *curptr;
if (cursc == parentnum) return(TRUE);
curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
ret = objisd(ctx, curptr, parentnum);
mcmunlck(ctx, (mcmon)cursc);
if (ret) return(TRUE);
}
return(FALSE);
}
/*
* Get a property of an object, either from the object or from a
* superclass (inherited). If the inh flag is TRUE, we do not look at
* all in the object itself, but restrict our search to inherited
* properties only. We return the byte offset of the prpdef within the
* object in which the prpdef is found; the superclass object itself is
* NOT locked upon return, but we will NOT unlock the object passed in
* (in other words, all object locking status is the same as it was on
* entry). If the offset is zero, the property was not found.
*
* This is an internal helper routine - it's not meant to be called
* except by objgetap().
*/
static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
objnum *orn, int inh, dattyp *ortyp)
{
uchar *sc;
ushort sccnt;
ushort psav = 0;
dattyp typsav = DAT_NIL;
objnum osavn = MCMONINV;
uchar *o1;
objnum o1n;
ushort poff;
int found;
uint retval;
dattyp typ;
uchar sclist[100]; /* up to 50 superclasses */
objdef *objptr;
NOREG((&obj))
/* see if the property is in the current object first */
if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
{
/*
* tell the caller which object this came from, if the caller
* wants to know
*/
if (orn != nullptr)
*orn = obj;
/* if the caller wants to know the type, return it */
if (ortyp != nullptr)
*ortyp = typ;
/* return the property offset */
return retval;
}
/* lock the object, cache its superclass list, and unlock it */
objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
sccnt = objnsc(objptr);
memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
sc = sclist;
mcmunlck(ctx, (mcmon)obj);
/* try to inherit the property */
for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
{
/* recursively look up the property in this superclass */
poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);
/* if we found the property, remember it */
if (poff != 0)
{
int isdesc = 0;
/* if we have a previous object, determine lineage */
if (found)
{
o1 = mcmlck(ctx, o1n);
isdesc = objisd(ctx, o1, osavn);
mcmunlck(ctx, o1n);
}
/*
* if we don't already have a property, or the new object
* is a descendant of the previously found object (meaning
* that the new object's property should override the
* previously found object's property), use this new
* property
*/
if (!found || isdesc)
{
psav = poff;
osavn = o1n;
typsav = typ;
found = TRUE;
}
}
}
/* set return pointer and return the offset of what we found */
if (orn != nullptr)
*orn = osavn;
/* return the object type if the caller wanted it */
if (ortyp != nullptr)
*ortyp = typsav;
/* return the offset of the property if we found one, or zero if not */
return (found ? psav : 0);
}
/*
* Get a property of an object, either from the object or from a
* superclass (inherited). If the inh flag is TRUE, we do not look at
* all in the object itself, but restrict our search to inherited
* properties only. We return the byte offset of the prpdef within the
* object in which the prpdef is found; the superclass object itself is
* NOT locked upon return, but we will NOT unlock the object passed in
* (in other words, all object locking status is the same as it was on
* entry). If the offset is zero, the property was not found.
*/
uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
objnum *ornp, int inh)
{
uint retval;
dattyp typ;
objnum orn;
/*
* even if the caller doesn't care about the original object number,
* we do, so provide our own location to store it if necessary
*/
if (ornp == nullptr)
ornp = &orn;
/* keep going until we've finished translating synonyms */
for (;;)
{
/* look up the property */
retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
/*
* If we found something (i.e., retval != 0), check to see if we
* have a synonym; if so, synonym translation is required
*/
if (retval != 0 && typ == DAT_SYN)
{
prpnum prvprop;
objdef *objptr;
prpdef *p;
/*
* Translation is required - get new property and try again.
* First, remember the original property, so we can make
* sure we're not going to loop (at least, not in this one
* synonym definition).
*/
prvprop = prop;
objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
p = objofsp(objptr, retval);
prop = osrp2(prpvalp(p));
mcmunlck(ctx, (mcmon)*ornp);
/* check for direct circularity */
if (prop == prvprop)
errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);
/* go back for another try with the new property */
continue;
}
/* we don't have to perform a translation; return the result */
return retval;
}
}
/*
* Expand an object by a requested size, and return a pointer to the
* object's location. The object will be unlocked and relocked by this
* call. The new size is written to the *siz argument.
*/
objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
{
ushort oldsiz;
uchar *p;
oldsiz = mcmobjsiz(ctx, (mcmon)obj);
p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
*siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
return((objdef *)p);
}
/*
* Delete a property in an object. Note that we never actually remove
* anything marked as an original property, but just mark it 'ignore'.
* This way, it's easy to restore the entire original state of the
* objects, simply by deleting everything not marked original and
* clearing the 'ignore' flag on the remaining properties. If
* 'mark_only' is true, we'll only mark the property as deleted without
* actually reclaiming the space; this is necessary when deleting a
* method when other methods may follow, since p-code is not entirely
* self-relative and thus can't always be relocated within an object.
*/
void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
{
objdef *objptr;
uint pofs;
prpdef *p;
prpdef *nxt;
size_t movsiz;
pofs = objgetp(mctx, objn, prop, (dattyp *)nullptr); /* try to find property */
if (!pofs) return; /* not defined - nothing to delete */
objptr = (objdef *)mcmlck(mctx, objn); /* get lock on object */
p = objofsp(objptr, pofs); /* get actual prpdef pointer */
nxt = objpnxt(p); /* find next prpdef after this one */
/* if this is original, just mark 'ignore' */
if (prpflg(p) & PRPFORG)
{
prpflg(p) |= PRPFIGN; /* mark this as overridden */
}
else if (mark_only)
{
prpflg(p) |= PRPFDEL; /* mark as deleted without removing space */
}
else
{
/* move prpdef's after current one down over current one */
movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
memmove(p, nxt, movsiz);
objsnp(objptr, objnprop(objptr)-1);
objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
}
/* tell cache manager this object has been changed, and unlock it */
mcmtch(mctx, objn);
mcmunlck(mctx, objn);
}
/*
* Set a property of an object to a new value, overwriting the original
* value (if any); the object must be unlocked coming in. If an undo
* context is provided, an undo record is written; if the undo context
* pointer is null, no undo information is kept.
*/
void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
const void *val, objucxdef *undoctx)
{
objdef *objptr;
prpdef *p;
uint pofs;
uint siz;
ushort newsiz;
int indexed;
int prop_was_set;
/* get a lock on the object */
objptr = (objdef *)mcmlck(ctx, objn);
indexed = objflg(objptr) & OBJFINDEX;
/* catch any errors so we can unlock the object */
ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
{
/* get the previous value of the property, if any */
pofs = objgetp(ctx, objn, prop, (dattyp *)nullptr);
p = objofsp(objptr, pofs);
prop_was_set = (p != nullptr);
/* start the undo record if we are keeping undo information */
if (undoctx && objuok(undoctx))
{
uchar *up;
uchar cmd;
if (p)
{
if (prpflg(p) & PRPFORG)
{
cmd = OBJUOVR; /* override original */
p = (prpdef *)nullptr; /* pretend it doesn't even exist */
}
else cmd = OBJUCHG; /* change property */
}
else cmd = OBJUADD; /* prop didn't exist - adding it */
/* write header, reserve space, and get a pointer to the space */
up = objures(undoctx, cmd,
(ushort)(sizeof(mcmon) + sizeof(prpnum)
+ (p ? PRPHDRSIZ + prpsize(p) : 0)));
/* write the object and property numbers */
memcpy(up, &objn, (size_t)sizeof(objn));
up += sizeof(mcmon);
memcpy(up, &prop, (size_t)sizeof(prop));
up += sizeof(prop);
/* if there's existing data, write it */
if (p)
{
memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
up += PRPHDRSIZ + prpsize(p);
}
/* update the undo context's head offset for the new value */
undoctx->objucxhead = up - undoctx->objucxbuf;
}
/* get the size of the data */
siz = datsiz(typ, val);
/*
* If the property is already set, and the new data fits, use the
* existing slot. However, do not use existing slot if it's
* in the non-mutable portion of the object.
*/
if (!p || (uint)prpsize(p) < siz || pofs < (uint)objrst(objptr))
{
uint avail;
/* delete any existing value */
if (prop_was_set)
objdelp(ctx, objn, prop, FALSE);
/* get the top of the property area */
p = objpfre(objptr);
/* make sure there's room at the top */
avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
if (avail < siz + PRPHDRSIZ)
{
newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
mcmobjsiz(ctx, (mcmon)objn));
objptr = objexp(ctx, objn, &newsiz);
p = objpfre(objptr); /* reset pointer if object moved */
/* NOTE! Index (if present) is now invalid! */
}
prpsetsize(p, siz); /* set the new property size */
prpsetprop(p, prop); /* ... and property id */
prpflg(p) = 0; /* no property flags yet */
objsnp(objptr, objnprop(objptr) + 1); /* one more prop */
objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
}
/* copy the new data to top of object's free space */
prptype(p) = typ;
if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
}
ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
{
mcmunlck(ctx, objn); /* unlock the object */
}
ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
/* dirty the object, and release lock on object before return */
mcmtch(ctx, objn); /* mark the object as changed */
mcmunlck(ctx, objn); /* unlock it */
/* if necessary, rebuild the property index */
if (indexed) objindx(ctx, objn);
}
/* set an undo savepoint */
void objusav(objucxdef *undoctx)
{
/* the only thing in this record is the OBJUSAV header */
objures(undoctx, OBJUSAV, (ushort)0);
}
/* reserve space in an undo buffer, and write header */
uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
{
ushort prv;
uchar *p;
/* adjust size to include header information */
siz += 1 + sizeof(ushort);
/* make sure there's enough room overall for the record */
if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
/* if there's no information, reset buffers */
if (undoctx->objucxhead == undoctx->objucxprv)
{
undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
undoctx->objucxtop = 0;
goto done;
}
/* if tail is below head, we can use to top of entire buffer */
if (undoctx->objucxtail < undoctx->objucxhead)
{
/* if there's enough space left after head, we're done */
if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
goto done;
/* insufficient space: wrap head down to bottom of buffer */
undoctx->objucxtop = undoctx->objucxprv; /* last was top */
undoctx->objucxhead = 0;
}
/* head is below tail: delete records until we have enough room */
while (undoctx->objucxtail - undoctx->objucxhead < siz)
{
objutadv(undoctx);
/* if the tail wrapped, advancing won't do any more good */
if (undoctx->objucxtail <= undoctx->objucxhead)
{
/* if there's enough room at the top, we're done */
if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
goto done;
/* still not enough room; wrap the head this time */
undoctx->objucxtop = undoctx->objucxprv; /* last was top */
undoctx->objucxhead = 0;
}
}
done:
/* save back-link, and set objucxprv pointer to the new record */
prv = undoctx->objucxprv;
undoctx->objucxprv = undoctx->objucxhead;
/* write the header: command byte, back-link to previous record */
p = &undoctx->objucxbuf[undoctx->objucxhead];
*p++ = cmd;
memcpy(p, &prv, sizeof(prv));
/* advance the head pointer past the header */
undoctx->objucxhead += 1 + sizeof(prv);
/* set the high-water mark if we've exceeded the old one */
if (undoctx->objucxprv > undoctx->objucxtop)
undoctx->objucxtop = undoctx->objucxprv;
/* return the reserved space */
return &undoctx->objucxbuf[undoctx->objucxhead];
}
/* advance the undo tail pointer over the record it points to */
void objutadv(objucxdef *undoctx)
{
uchar *p;
ushort siz;
uchar pr[PRPHDRSIZ]; /* space for a property header */
uchar cmd;
/* if we're at the most recently written record, flush buffer */
if (undoctx->objucxtail == undoctx->objucxprv)
{
undoctx->objucxtail = 0;
undoctx->objucxprv = 0;
undoctx->objucxhead = 0;
undoctx->objucxtop = 0;
}
/* if we've reached high water mark, wrap back to bottom */
if (undoctx->objucxtail == undoctx->objucxtop)
{
undoctx->objucxtail = 0;
return;
}
/* determine size by inspecting current record */
p = undoctx->objucxbuf + undoctx->objucxtail;
siz = 1 + sizeof(ushort); /* basic header size */
cmd = *p++;
p += sizeof(ushort); /* skip the previous pointer */
switch(cmd)
{
case OBJUCHG:
/* change: property header (added below) plus data value */
memcpy(pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
siz += PRPHDRSIZ + prpsize(pr);
/* FALLTHROUGH */
case OBJUADD:
case OBJUOVR:
/* add/override: property header only */
siz += sizeof(mcmon) + sizeof(prpnum);
break;
case OBJUCLI:
siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
break;
case OBJUSAV:
break;
}
undoctx->objucxtail += siz;
}
/* undo one undo record, and remove it from the undo list */
void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
{
uchar *p;
prpnum prop = 0;
objnum objn = 0;
uchar cmd;
uchar pr[PRPHDRSIZ]; /* space for property header */
ushort prv;
ushort pofs;
objdef *objptr;
int indexed = 0;
/* if there's no more undo, signal an error */
if (undoctx->objucxprv == undoctx->objucxhead)
errsig(undoctx->objucxerr, ERR_NOUNDO);
/* move back to previous record */
undoctx->objucxhead = undoctx->objucxprv;
p = &undoctx->objucxbuf[undoctx->objucxprv];
/* get command, and set undocxprv to previous record */
cmd = *p++;
memcpy(&prv, p, sizeof(prv));
p += sizeof(prv);
/* if we're at the tail, no more undo; otherwise, use back link */
if (undoctx->objucxprv == undoctx->objucxtail)
undoctx->objucxprv = undoctx->objucxhead;
else
undoctx->objucxprv = prv;
if (cmd == OBJUSAV) return; /* savepointer marker - nothing to do */
/* get object/property information for property-changing undo */
if (cmd != OBJUCLI)
{
memcpy(&objn, p, (size_t)sizeof(objn));
p += sizeof(objn);
memcpy(&prop, p, (size_t)sizeof(prop));
p += sizeof(prop);
objptr = mcmlck(mctx, objn);
indexed = (objflg(objptr) & OBJFINDEX);
mcmunlck(mctx, objn);
}
switch(cmd)
{
case OBJUADD:
objdelp(mctx, objn, prop, FALSE);
if (indexed) objindx(mctx, objn);
break;
case OBJUOVR:
objdelp(mctx, objn, prop, FALSE); /* delete the non-original value */
pofs = objgetp(mctx, objn, prop, (dattyp *)nullptr); /* get ignored prop */
objptr = (objdef *)mcmlck(mctx, objn); /* lock the object */
prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN; /* no longer ignored */
mcmunlck(mctx, objn); /* unlock the object */
break;
case OBJUCHG:
memcpy(pr, p, (size_t)PRPHDRSIZ);
p += PRPHDRSIZ;
objsetp(mctx, objn, prop, prptype(pr), (void *)p, (objucxdef *)nullptr);
break;
case OBJUCLI:
(*undoctx->objucxcun)(undoctx->objucxccx, p);
break;
}
}
/*
* Determine if it's ok to add undo records - returns TRUE if a
* savepoint has been stored in the undo log, FALSE if not.
*/
int objuok(objucxdef *undoctx)
{
ushort prv;
/* see if there's any more undo information */
if (undoctx->objucxprv == undoctx->objucxhead)
return(FALSE);
/* look for most recent savepoint marker */
for (prv = undoctx->objucxprv ;; )
{
if (undoctx->objucxbuf[prv] == OBJUSAV)
return(TRUE); /* found a savepoint - can add undo */
/* if we've reached the tail, there are no more undo records */
if (prv == undoctx->objucxtail)
return(FALSE); /* no savepoints - can't add undo */
/* get previous record */
memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
}
}
/*
* Undo back to the most recent savepoint. If there is no savepoint in
* the undo list, NOTHING will be undone. This prevents reaching an
* inconsistent state in which some, but not all, of the operations
* between two savepoints are undone: either all operations between two
* savepoints will be undone, or none will.
*/
void objundo(mcmcxdef *mctx, objucxdef *undoctx)
{
ushort prv;
ushort sav;
/* see if there's any more undo information */
if (undoctx->objucxprv == undoctx->objucxhead)
errsig(undoctx->objucxerr, ERR_NOUNDO);
/* look for most recent savepoint marker */
for (prv = undoctx->objucxprv ;; )
{
if (undoctx->objucxbuf[prv] == OBJUSAV)
{
sav = prv;
break;
}
/* if we've reached the tail, there are no more undo records */
if (prv == undoctx->objucxtail)
errsig(undoctx->objucxerr, ERR_ICUNDO);
/* get previous record */
memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
}
/* now undo everything until we get to the savepoint */
do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
}
/* initialize undo context */
objucxdef *objuini(mcmcxdef *ctx, ushort siz,
void (*undocb)(void *, uchar *),
ushort (*sizecb)(void *, uchar *),
void *callctx)
{
objucxdef *ret;
long totsiz;
/* force size into valid range */
totsiz = (long)siz + sizeof(objucxdef) - 1;
if (totsiz > 0xff00)
siz = 0xff00 - sizeof(objucxdef) + 1;
ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
(sizeof(objucxdef) + siz - 1),
"objuini");
ret->objucxmem = ctx;
ret->objucxerr = ctx->mcmcxgl->mcmcxerr;
ret->objucxsiz = siz;
ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
/* set client callback functions */
ret->objucxcun = undocb; /* callback to apply client undo */
ret->objucxcsz = sizecb; /* callback to get size of client undo */
ret->objucxccx = callctx; /* context for client callback functions */
return(ret);
}
/* discard all undo records */
void objulose(objucxdef *ctx)
{
if (ctx)
ctx->objucxhead =
ctx->objucxprv =
ctx->objucxtail =
ctx->objucxtop = 0;
}
/* uninitialize the undo context - release allocated memory */
void objuterm(objucxdef *uctx)
{
/* free the undo memory block */
mchfre(uctx);
}
/* revert object to original (post-compilation) values */
void objrevert(void *ctx0, mcmon objn)
{
mcmcxdef *mctx = (mcmcxdef *)ctx0;
uchar *p;
prpdef *pr;
int cnt;
int indexed;
p = mcmlck(mctx, objn);
pr = objprp(p);
indexed = objflg(p) & OBJFINDEX;
/* restore original settings */
objsfree(p, objrst(p));
objsnp(p, objstat(p));
/* go through original properties and remove 'ignore' flag if set */
for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
prpflg(pr) &= ~PRPFIGN;
/* touch object and unlock it */
mcmtch(mctx, objn);
mcmunlck(mctx, objn);
/* if it's indexed, rebuild the index */
if (indexed) objindx(mctx, objn);
}
/* set 'ignore' flag for original properties set in mutable part */
void objsetign(mcmcxdef *mctx, objnum objn)
{
objdef *objptr;
prpdef *mut;
prpdef *p;
int statcnt;
int cnt;
int indexed;
prpdef *p1;
objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
p1 = objprp(objptr);
indexed = objflg(objptr) & OBJFINDEX;
/* go through mutables, and set ignore on duplicates in non-mutables */
for (mut = (prpdef *)(objptr + objrst(objptr)),
cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
mut = objpnxt(mut), --cnt)
{
for (p = p1, statcnt = objstat(objptr) ; statcnt ;
p = objpnxt(p), --statcnt)
{
/* if this static prop matches a mutable prop, ignore it */
if (prpprop(p) == prpprop(mut))
{
prpflg(p) |= PRPFIGN;
break;
}
}
}
mcmtch(mctx, (mcmon)objn);
mcmunlck(mctx, (mcmon)objn);
if (indexed) objindx(mctx, objn);
}
/*
* Build or rebuild a property index for an object.
*/
void objindx(mcmcxdef *mctx, objnum objn)
{
uint newsiz;
uint avail;
objdef *objptr;
uint cnt;
prpdef *p;
uchar *indp = nullptr;
uchar *indbase;
uint icnt;
uint first;
uint last;
uint cur = 0;
objptr = (objdef *)mcmlck(mctx, objn); /* get object pointer */
cnt = objnprop(objptr); /* get number of properties */
p = objprp(objptr); /* get pointer to properties (or old index) */
newsiz = 2 + 4*cnt; /* figure size needed for the index */
avail = mcmobjsiz(mctx, objn) - objfree(objptr);
/* insert space for the index; expand the object if necessary */
if (avail < newsiz)
{
ushort need;
newsiz += 10*4; /* add some extra space for later */
need = newsiz - avail; /* compute amount of space needed */
objptr = objexp(mctx, objn, &need);
p = objprp(objptr);
}
/* now build the index */
indbase = objpfre(objptr);
for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
{
uint ofs = (uchar *)p - (uchar *)objptr;
if (icnt)
{
/* figure out where to insert this property */
first = 0;
last = icnt - 1;
for (;;)
{
if (first > last) break;
cur = first + (last - first)/2;
indp = indbase + cur*4;
if (indp[0] == p[0] && indp[1] == p[1])
break;
else if (indp[0] < p[0]
|| (indp[0] == p[0] && indp[1] < p[1]))
first = (cur == first ? first + 1 : cur);
else
last = (cur == last ? last - 1 : cur);
}
/* make sure we're positioned just before insertion point */
while (cur < icnt
&& (indp[0] <= p[0]
|| (indp[0] == p[0] && indp[1] <= p[1])))
{
indp += 4;
++cur;
}
/* move elements above if any */
if (cur < icnt)
memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
}
else
indp = indbase;
/* insert property into index */
indp[0] = p[0];
indp[1] = p[1];
oswp2(indp+2, ofs);
}
/* set the index flag, and dirty and free the object */
objsflg(objptr, objflg(objptr) | OBJFINDEX);
mcmtch(mctx, (mcmon)objn);
mcmunlck(mctx, (mcmon)objn);
}
/* allocate and initialize an object */
objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
objnum *objnptr, int classflg)
{
objdef *o;
mcmon objn;
/* allocate cache object */
o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
&objn);
/* set up object descriptor for the new object */
objini(mctx, sccnt, (objnum)objn, classflg);
*objnptr = (objnum)objn;
return(o);
}
/* initialize an already allocated object */
void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
{
objdef *o;
uint flags = 0;
/* get a lock on the object */
o = (objdef *)mcmlck(mctx, objn);
memset(o, 0, (size_t)10);
objsnsc(o, sccnt);
objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
/* set up flags */
if (classflg) flags |= OBJFCLASS;
objsflg(o, flags);
/* tell cache manager that this object has been modified */
mcmtch(mctx, objn);
mcmunlck(mctx, objn);
}
/*
* Get the first superclass of an object. If it doesn't have any
* superclasses, return invalid.
*/
objnum objget1sc(mcmcxdef *ctx, objnum objn)
{
objdef *p;
objnum retval;
/* lock the object */
p = mcmlck(ctx, (mcmon)objn);
/* get the first superclass if it has any */
if (objnsc(p) == 0)
retval = MCMONINV;
else
retval = osrp2(objsc(p));
/* unlock the object and return the superclass value */
mcmunlck(ctx, (mcmon)objn);
return retval;
}
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk