1076 lines
30 KiB
C++
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
|