1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "glk/tads/tads2/object.h"
24 #include "glk/tads/tads2/error.h"
25 #include "glk/tads/tads2/memory_cache_heap.h"
26 #include "glk/tads/os_glk.h"
27
28 namespace Glk {
29 namespace TADS {
30 namespace TADS2 {
31
32 /*
33 * Get a property WITHOUT INHERITANCE. The offset of the property's
34 * prpdef is returned. An offset of zero means the property wasn't
35 * found.
36 */
objgetp(mcmcxdef * mctx,objnum objn,prpnum prop,dattyp * typptr)37 uint objgetp(mcmcxdef *mctx, objnum objn, prpnum prop, dattyp *typptr)
38 {
39 objdef *objptr;
40 prpdef *p;
41 int cnt;
42 uint retval; /* property offset, if we find it */
43 uint ignprop; /* ignored property - use if real property isn't found */
44 uchar pbuf[2]; /* property number in portable format */
45 uchar *indp = nullptr;
46 uchar *indbase;
47 int last;
48 int first;
49 int cur = 0;
50
51 oswp2(pbuf, prop); /* get property number in portable foramt */
52 objptr = (objdef *)mcmlck(mctx, objn); /* get a lock on the object */
53 ignprop = 0; /* assume we won't find ignored property */
54 cnt = objnprop(objptr); /* get number of properties defined */
55 retval = 0; /* presume failure */
56
57 if (objflg(objptr) & OBJFINDEX)
58 {
59 /* there's an index -> do a binary search through the index */
60 indbase = (uchar *)objpfre(objptr); /* find index */
61 first = 0;
62 last = cnt - 1;
63 for (;;)
64 {
65 if (first > last) break; /* crossed over -> not found */
66 cur = first + (last - first)/2; /* split the difference */
67 indp = indbase + cur*4; /* get pointer to this entry */
68 if (indp[0] == pbuf[0] && indp[1] == pbuf[1])
69 {
70 retval = osrp2(indp + 2);
71 break;
72 }
73 else if (indp[0] < pbuf[0]
74 || (indp[0] == pbuf[0] && indp[1] < pbuf[1]))
75 first = (cur == first ? first + 1 : cur);
76 else
77 last = (cur == last ? last - 1 : cur);
78 }
79
80 /* ignore ignored and deleted properties if possible */
81 while (retval
82 && ((prpflg(objptr + retval) & PRPFIGN) != 0
83 || ((prpflg(objptr + retval) & PRPFDEL) != 0
84 && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0))
85 && cur < cnt && indp[0] == indp[4] && indp[1] == indp[5])
86 {
87 indp += 4;
88 retval = osrp2(indp + 2);
89 }
90 if (retval && osrp2(objptr + retval) != prop)
91 assert(FALSE);
92 }
93 else
94 {
95 /* there's no index -> do sequential search through properties */
96 for (p = objprp(objptr) ; cnt ; p = objpnxt(p), --cnt)
97 {
98 /* if this is the property, and it's not being ignored, use it */
99 if (*(uchar *)p == pbuf[0] && *(((uchar *)p) + 1) == pbuf[1])
100 {
101 if (prpflg(p) & PRPFIGN) /* this is ignored */
102 ignprop = objpofs(objptr, p); /* ... make a note of it */
103 else if ((prpflg(p) & PRPFDEL) != 0 /* it's deleted */
104 && (mctx->mcmcxflg & MCMCXF_NO_PRP_DEL) == 0)
105 /* simply skip it */ ;
106 else
107 {
108 retval = objpofs(objptr, p); /* this is the one */
109 break; /* we're done */
110 }
111 }
112 }
113 }
114
115 if (!retval) retval = ignprop; /* use ignored value if nothing else */
116 if (retval && typptr) *typptr = prptype(objofsp(objptr, retval));
117
118 mcmunlck(mctx, objn); /* done with object, so unlock it */
119 return(retval);
120 }
121
122 /* get the offset of the end of a property in an object */
objgetp_end(mcmcxdef * ctx,objnum objn,prpnum prop)123 uint objgetp_end(mcmcxdef *ctx, objnum objn, prpnum prop)
124 {
125 objdef *objp;
126 prpdef *propptr;
127 uint ofs;
128 uint valsiz;
129
130 /* get the start of the object */
131 ofs = objgetp(ctx, objn, prop, 0);
132 if (ofs == 0)
133 return 0;
134
135 /* get the object */
136 objp = mcmlck(ctx, (mcmon)objn);
137
138 /* get the property */
139 propptr = objofsp(objp, ofs);
140
141 /* get the data size */
142 valsiz = prpsize(propptr);
143
144 /* done with the object */
145 mcmunlck(ctx, (mcmon)objn);
146
147 /*
148 * return the ending offset - it's the starting offset plus the
149 * property header size plus the size of the property data
150 */
151 return ofs + PRPHDRSIZ + valsiz;
152 }
153
154 /* determine whether an object is a descendant of another object */
objisd(mcmcxdef * ctx,objdef * objptr,objnum parentnum)155 static int objisd(mcmcxdef *ctx, objdef *objptr, objnum parentnum)
156 {
157 uchar *sc;
158 int cnt;
159
160 for (sc = objsc(objptr), cnt = objnsc(objptr) ; cnt ;
161 sc += 2, --cnt)
162 {
163 int cursc = osrp2(sc);
164 int ret;
165 objdef *curptr;
166
167 if (cursc == parentnum) return(TRUE);
168
169 curptr = (objdef *)mcmlck(ctx, (mcmon)cursc);
170 ret = objisd(ctx, curptr, parentnum);
171 mcmunlck(ctx, (mcmon)cursc);
172 if (ret) return(TRUE);
173 }
174 return(FALSE);
175 }
176
177 /*
178 * Get a property of an object, either from the object or from a
179 * superclass (inherited). If the inh flag is TRUE, we do not look at
180 * all in the object itself, but restrict our search to inherited
181 * properties only. We return the byte offset of the prpdef within the
182 * object in which the prpdef is found; the superclass object itself is
183 * NOT locked upon return, but we will NOT unlock the object passed in
184 * (in other words, all object locking status is the same as it was on
185 * entry). If the offset is zero, the property was not found.
186 *
187 * This is an internal helper routine - it's not meant to be called
188 * except by objgetap().
189 */
objgetap0(mcmcxdef * ctx,noreg objnum obj,prpnum prop,objnum * orn,int inh,dattyp * ortyp)190 static uint objgetap0(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
191 objnum *orn, int inh, dattyp *ortyp)
192 {
193 uchar *sc;
194 ushort sccnt;
195 ushort psav = 0;
196 dattyp typsav = DAT_NIL;
197 objnum osavn = MCMONINV;
198 uchar *o1;
199 objnum o1n;
200 ushort poff;
201 int found;
202 uint retval;
203 dattyp typ;
204 uchar sclist[100]; /* up to 50 superclasses */
205 objdef *objptr;
206
207 NOREG((&obj))
208
209 /* see if the property is in the current object first */
210 if (!inh && (retval = objgetp(ctx, obj, prop, &typ)) != 0)
211 {
212 /*
213 * tell the caller which object this came from, if the caller
214 * wants to know
215 */
216 if (orn != 0)
217 *orn = obj;
218
219 /* if the caller wants to know the type, return it */
220 if (ortyp != 0)
221 *ortyp = typ;
222
223 /* return the property offset */
224 return retval;
225 }
226
227 /* lock the object, cache its superclass list, and unlock it */
228 objptr = (objdef *)mcmlck(ctx, (mcmon)obj);
229 sccnt = objnsc(objptr);
230 memcpy(sclist, objsc(objptr), (size_t)(sccnt << 1));
231 sc = sclist;
232 mcmunlck(ctx, (mcmon)obj);
233
234 /* try to inherit the property */
235 for (found = FALSE ; sccnt != 0 ; sc += 2, --sccnt)
236 {
237 /* recursively look up the property in this superclass */
238 poff = objgetap0(ctx, (objnum)osrp2(sc), prop, &o1n, FALSE, &typ);
239
240 /* if we found the property, remember it */
241 if (poff != 0)
242 {
243 int isdesc = 0;
244
245 /* if we have a previous object, determine lineage */
246 if (found)
247 {
248 o1 = mcmlck(ctx, o1n);
249 isdesc = objisd(ctx, o1, osavn);
250 mcmunlck(ctx, o1n);
251 }
252
253 /*
254 * if we don't already have a property, or the new object
255 * is a descendant of the previously found object (meaning
256 * that the new object's property should override the
257 * previously found object's property), use this new
258 * property
259 */
260 if (!found || isdesc)
261 {
262 psav = poff;
263 osavn = o1n;
264 typsav = typ;
265 found = TRUE;
266 }
267 }
268 }
269
270 /* set return pointer and return the offset of what we found */
271 if (orn != 0)
272 *orn = osavn;
273
274 /* return the object type if the caller wanted it */
275 if (ortyp != 0)
276 *ortyp = typsav;
277
278 /* return the offset of the property if we found one, or zero if not */
279 return (found ? psav : 0);
280 }
281
282 /*
283 * Get a property of an object, either from the object or from a
284 * superclass (inherited). If the inh flag is TRUE, we do not look at
285 * all in the object itself, but restrict our search to inherited
286 * properties only. We return the byte offset of the prpdef within the
287 * object in which the prpdef is found; the superclass object itself is
288 * NOT locked upon return, but we will NOT unlock the object passed in
289 * (in other words, all object locking status is the same as it was on
290 * entry). If the offset is zero, the property was not found.
291 */
objgetap(mcmcxdef * ctx,noreg objnum obj,prpnum prop,objnum * ornp,int inh)292 uint objgetap(mcmcxdef *ctx, noreg objnum obj, prpnum prop,
293 objnum *ornp, int inh)
294 {
295 uint retval;
296 dattyp typ;
297 objnum orn;
298
299 /*
300 * even if the caller doesn't care about the original object number,
301 * we do, so provide our own location to store it if necessary
302 */
303 if (ornp == 0)
304 ornp = &orn;
305
306 /* keep going until we've finished translating synonyms */
307 for (;;)
308 {
309 /* look up the property */
310 retval = objgetap0(ctx, obj, prop, ornp, inh, &typ);
311
312 /*
313 * If we found something (i.e., retval != 0), check to see if we
314 * have a synonym; if so, synonym translation is required
315 */
316 if (retval != 0 && typ == DAT_SYN)
317 {
318 prpnum prvprop;
319 objdef *objptr;
320 prpdef *p;
321
322 /*
323 * Translation is required - get new property and try again.
324 * First, remember the original property, so we can make
325 * sure we're not going to loop (at least, not in this one
326 * synonym definition).
327 */
328 prvprop = prop;
329
330 objptr = (objdef *)mcmlck(ctx, (mcmon)*ornp);
331 p = objofsp(objptr, retval);
332 prop = osrp2(prpvalp(p));
333 mcmunlck(ctx, (mcmon)*ornp);
334
335 /* check for direct circularity */
336 if (prop == prvprop)
337 errsig(ctx->mcmcxgl->mcmcxerr, ERR_CIRCSYN);
338
339 /* go back for another try with the new property */
340 continue;
341 }
342
343 /* we don't have to perform a translation; return the result */
344 return retval;
345 }
346 }
347
348
349 /*
350 * Expand an object by a requested size, and return a pointer to the
351 * object's location. The object will be unlocked and relocked by this
352 * call. The new size is written to the *siz argument.
353 */
objexp(mcmcxdef * ctx,objnum obj,ushort * siz)354 objdef *objexp(mcmcxdef *ctx, objnum obj, ushort *siz)
355 {
356 ushort oldsiz;
357 uchar *p;
358
359 oldsiz = mcmobjsiz(ctx, (mcmon)obj);
360 p = mcmrealo(ctx, (mcmon)obj, (ushort)(oldsiz + *siz));
361 *siz = mcmobjsiz(ctx, (mcmon)obj) - oldsiz;
362 return((objdef *)p);
363 }
364
365 /*
366 * Delete a property in an object. Note that we never actually remove
367 * anything marked as an original property, but just mark it 'ignore'.
368 * This way, it's easy to restore the entire original state of the
369 * objects, simply by deleting everything not marked original and
370 * clearing the 'ignore' flag on the remaining properties. If
371 * 'mark_only' is true, we'll only mark the property as deleted without
372 * actually reclaiming the space; this is necessary when deleting a
373 * method when other methods may follow, since p-code is not entirely
374 * self-relative and thus can't always be relocated within an object.
375 */
objdelp(mcmcxdef * mctx,objnum objn,prpnum prop,int mark_only)376 void objdelp(mcmcxdef *mctx, objnum objn, prpnum prop, int mark_only)
377 {
378 objdef *objptr;
379 uint pofs;
380 prpdef *p;
381 prpdef *nxt;
382 size_t movsiz;
383
384 pofs = objgetp(mctx, objn, prop, (dattyp *)0); /* try to find property */
385 if (!pofs) return; /* not defined - nothing to delete */
386
387 objptr = (objdef *)mcmlck(mctx, objn); /* get lock on object */
388 p = objofsp(objptr, pofs); /* get actual prpdef pointer */
389 nxt = objpnxt(p); /* find next prpdef after this one */
390
391 /* if this is original, just mark 'ignore' */
392 if (prpflg(p) & PRPFORG)
393 {
394 prpflg(p) |= PRPFIGN; /* mark this as overridden */
395 }
396 else if (mark_only)
397 {
398 prpflg(p) |= PRPFDEL; /* mark as deleted without removing space */
399 }
400 else
401 {
402 /* move prpdef's after current one down over current one */
403 movsiz = (uchar *)objptr + objfree(objptr) - (uchar *)nxt;
404 memmove(p, nxt, movsiz);
405
406 objsnp(objptr, objnprop(objptr)-1);
407 objsfree(objptr, objfree(objptr) - (((uchar *)nxt) - ((uchar *)p)));
408 }
409
410 /* tell cache manager this object has been changed, and unlock it */
411 mcmtch(mctx, objn);
412 mcmunlck(mctx, objn);
413 }
414
415 /*
416 * Set a property of an object to a new value, overwriting the original
417 * value (if any); the object must be unlocked coming in. If an undo
418 * context is provided, an undo record is written; if the undo context
419 * pointer is null, no undo information is kept.
420 */
objsetp(mcmcxdef * ctx,objnum objn,prpnum prop,dattyp typ,const void * val,objucxdef * undoctx)421 void objsetp(mcmcxdef *ctx, objnum objn, prpnum prop, dattyp typ,
422 const void *val, objucxdef *undoctx)
423 {
424 objdef *objptr;
425 prpdef *p;
426 uint pofs;
427 uint siz;
428 ushort newsiz;
429 int indexed;
430 int prop_was_set;
431
432 /* get a lock on the object */
433 objptr = (objdef *)mcmlck(ctx, objn);
434 indexed = objflg(objptr) & OBJFINDEX;
435
436 /* catch any errors so we can unlock the object */
437 ERRBEGIN(ctx->mcmcxgl->mcmcxerr)
438 {
439 /* get the previous value of the property, if any */
440 pofs = objgetp(ctx, objn, prop, (dattyp *)0);
441 p = objofsp(objptr, pofs);
442 prop_was_set = (p != 0);
443
444 /* start the undo record if we are keeping undo information */
445 if (undoctx && objuok(undoctx))
446 {
447 uchar *up;
448 uchar cmd;
449
450 if (p)
451 {
452 if (prpflg(p) & PRPFORG)
453 {
454 cmd = OBJUOVR; /* override original */
455 p = (prpdef *)0; /* pretend it doesn't even exist */
456 }
457 else cmd = OBJUCHG; /* change property */
458 }
459 else cmd = OBJUADD; /* prop didn't exist - adding it */
460
461 /* write header, reserve space, and get a pointer to the space */
462 up = objures(undoctx, cmd,
463 (ushort)(sizeof(mcmon) + sizeof(prpnum)
464 + (p ? PRPHDRSIZ + prpsize(p) : 0)));
465
466 /* write the object and property numbers */
467 memcpy(up, &objn, (size_t)sizeof(objn));
468 up += sizeof(mcmon);
469 memcpy(up, &prop, (size_t)sizeof(prop));
470 up += sizeof(prop);
471
472 /* if there's existing data, write it */
473 if (p)
474 {
475 memcpy(up, p, (size_t)(PRPHDRSIZ + prpsize(p)));
476 up += PRPHDRSIZ + prpsize(p);
477 }
478
479 /* update the undo context's head offset for the new value */
480 undoctx->objucxhead = up - undoctx->objucxbuf;
481 }
482
483 /* get the size of the data */
484 siz = datsiz(typ, val);
485
486 /*
487 * If the property is already set, and the new data fits, use the
488 * existing slot. However, do not use existing slot if it's
489 * in the non-mutable portion of the object.
490 */
491 if (!p || (uint)prpsize(p) < siz || pofs < (uint)objrst(objptr))
492 {
493 uint avail;
494
495 /* delete any existing value */
496 if (prop_was_set)
497 objdelp(ctx, objn, prop, FALSE);
498
499 /* get the top of the property area */
500 p = objpfre(objptr);
501
502 /* make sure there's room at the top */
503 avail = mcmobjsiz(ctx, (mcmon)objn) - objfree(objptr);
504 if (avail < siz + PRPHDRSIZ)
505 {
506 newsiz = 64 + ((objfree(objptr) + siz + PRPHDRSIZ) -
507 mcmobjsiz(ctx, (mcmon)objn));
508 objptr = objexp(ctx, objn, &newsiz);
509 p = objpfre(objptr); /* reset pointer if object moved */
510 /* NOTE! Index (if present) is now invalid! */
511 }
512
513 prpsetsize(p, siz); /* set the new property size */
514 prpsetprop(p, prop); /* ... and property id */
515 prpflg(p) = 0; /* no property flags yet */
516 objsnp(objptr, objnprop(objptr) + 1); /* one more prop */
517 objsfree(objptr, objfree(objptr) + siz + PRPHDRSIZ);
518 }
519
520 /* copy the new data to top of object's free space */
521 prptype(p) = typ;
522 if (siz != 0) memcpy(prpvalp(p), val, (size_t)siz);
523 }
524 ERRCLEAN(ctx->mcmcxgl->mcmcxerr)
525 {
526 mcmunlck(ctx, objn); /* unlock the object */
527 }
528 ERRENDCLN(ctx->mcmcxgl->mcmcxerr)
529
530 /* dirty the object, and release lock on object before return */
531 mcmtch(ctx, objn); /* mark the object as changed */
532 mcmunlck(ctx, objn); /* unlock it */
533
534 /* if necessary, rebuild the property index */
535 if (indexed) objindx(ctx, objn);
536 }
537
538 /* set an undo savepoint */
objusav(objucxdef * undoctx)539 void objusav(objucxdef *undoctx)
540 {
541 /* the only thing in this record is the OBJUSAV header */
542 objures(undoctx, OBJUSAV, (ushort)0);
543 }
544
545 /* reserve space in an undo buffer, and write header */
objures(objucxdef * undoctx,uchar cmd,ushort siz)546 uchar *objures(objucxdef *undoctx, uchar cmd, ushort siz)
547 {
548 ushort prv;
549 uchar *p;
550
551 /* adjust size to include header information */
552 siz += 1 + sizeof(ushort);
553
554 /* make sure there's enough room overall for the record */
555 if (siz > undoctx->objucxsiz) errsig(undoctx->objucxerr, ERR_UNDOVF);
556
557 /* if there's no information, reset buffers */
558 if (undoctx->objucxhead == undoctx->objucxprv)
559 {
560 undoctx->objucxhead = undoctx->objucxprv = undoctx->objucxtail = 0;
561 undoctx->objucxtop = 0;
562 goto done;
563 }
564
565 /* if tail is below head, we can use to top of entire buffer */
566 if (undoctx->objucxtail < undoctx->objucxhead)
567 {
568 /* if there's enough space left after head, we're done */
569 if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
570 goto done;
571
572 /* insufficient space: wrap head down to bottom of buffer */
573 undoctx->objucxtop = undoctx->objucxprv; /* last was top */
574 undoctx->objucxhead = 0;
575 }
576
577 /* head is below tail: delete records until we have enough room */
578 while (undoctx->objucxtail - undoctx->objucxhead < siz)
579 {
580 objutadv(undoctx);
581
582 /* if the tail wrapped, advancing won't do any more good */
583 if (undoctx->objucxtail <= undoctx->objucxhead)
584 {
585 /* if there's enough room at the top, we're done */
586 if (undoctx->objucxsiz - undoctx->objucxhead >= siz)
587 goto done;
588
589 /* still not enough room; wrap the head this time */
590 undoctx->objucxtop = undoctx->objucxprv; /* last was top */
591 undoctx->objucxhead = 0;
592 }
593 }
594
595 done:
596 /* save back-link, and set objucxprv pointer to the new record */
597 prv = undoctx->objucxprv;
598 undoctx->objucxprv = undoctx->objucxhead;
599
600 /* write the header: command byte, back-link to previous record */
601 p = &undoctx->objucxbuf[undoctx->objucxhead];
602 *p++ = cmd;
603 memcpy(p, &prv, sizeof(prv));
604
605 /* advance the head pointer past the header */
606 undoctx->objucxhead += 1 + sizeof(prv);
607
608 /* set the high-water mark if we've exceeded the old one */
609 if (undoctx->objucxprv > undoctx->objucxtop)
610 undoctx->objucxtop = undoctx->objucxprv;
611
612 /* return the reserved space */
613 return &undoctx->objucxbuf[undoctx->objucxhead];
614 }
615
616 /* advance the undo tail pointer over the record it points to */
objutadv(objucxdef * undoctx)617 void objutadv(objucxdef *undoctx)
618 {
619 uchar *p;
620 ushort siz;
621 uchar pr[PRPHDRSIZ]; /* space for a property header */
622 uchar cmd;
623
624 /* if we're at the most recently written record, flush buffer */
625 if (undoctx->objucxtail == undoctx->objucxprv)
626 {
627 undoctx->objucxtail = 0;
628 undoctx->objucxprv = 0;
629 undoctx->objucxhead = 0;
630 undoctx->objucxtop = 0;
631 }
632
633 /* if we've reached high water mark, wrap back to bottom */
634 if (undoctx->objucxtail == undoctx->objucxtop)
635 {
636 undoctx->objucxtail = 0;
637 return;
638 }
639
640 /* determine size by inspecting current record */
641 p = undoctx->objucxbuf + undoctx->objucxtail;
642 siz = 1 + sizeof(ushort); /* basic header size */
643
644 cmd = *p++;
645 p += sizeof(ushort); /* skip the previous pointer */
646
647 switch(cmd)
648 {
649 case OBJUCHG:
650 /* change: property header (added below) plus data value */
651 memcpy(pr, p + sizeof(mcmon) + sizeof(prpnum), (size_t)PRPHDRSIZ);
652 siz += PRPHDRSIZ + prpsize(pr);
653 /* FALLTHROUGH */
654
655 case OBJUADD:
656 case OBJUOVR:
657 /* add/override: property header only */
658 siz += sizeof(mcmon) + sizeof(prpnum);
659 break;
660
661 case OBJUCLI:
662 siz += (*undoctx->objucxcsz)(undoctx->objucxccx, p);
663 break;
664
665 case OBJUSAV:
666 break;
667 }
668
669 undoctx->objucxtail += siz;
670 }
671
672 /* undo one undo record, and remove it from the undo list */
obj1undo(mcmcxdef * mctx,objucxdef * undoctx)673 void obj1undo(mcmcxdef *mctx, objucxdef *undoctx)
674 {
675 uchar *p;
676 prpnum prop = 0;
677 objnum objn = 0;
678 uchar cmd;
679 uchar pr[PRPHDRSIZ]; /* space for property header */
680 ushort prv;
681 ushort pofs;
682 objdef *objptr;
683 int indexed = 0;
684
685 /* if there's no more undo, signal an error */
686 if (undoctx->objucxprv == undoctx->objucxhead)
687 errsig(undoctx->objucxerr, ERR_NOUNDO);
688
689 /* move back to previous record */
690 undoctx->objucxhead = undoctx->objucxprv;
691 p = &undoctx->objucxbuf[undoctx->objucxprv];
692
693 /* get command, and set undocxprv to previous record */
694 cmd = *p++;
695 memcpy(&prv, p, sizeof(prv));
696 p += sizeof(prv);
697
698 /* if we're at the tail, no more undo; otherwise, use back link */
699 if (undoctx->objucxprv == undoctx->objucxtail)
700 undoctx->objucxprv = undoctx->objucxhead;
701 else
702 undoctx->objucxprv = prv;
703
704 if (cmd == OBJUSAV) return; /* savepointer marker - nothing to do */
705
706 /* get object/property information for property-changing undo */
707 if (cmd != OBJUCLI)
708 {
709 memcpy(&objn, p, (size_t)sizeof(objn));
710 p += sizeof(objn);
711 memcpy(&prop, p, (size_t)sizeof(prop));
712 p += sizeof(prop);
713 objptr = mcmlck(mctx, objn);
714 indexed = (objflg(objptr) & OBJFINDEX);
715 mcmunlck(mctx, objn);
716 }
717
718 switch(cmd)
719 {
720 case OBJUADD:
721 objdelp(mctx, objn, prop, FALSE);
722 if (indexed) objindx(mctx, objn);
723 break;
724
725 case OBJUOVR:
726 objdelp(mctx, objn, prop, FALSE); /* delete the non-original value */
727 pofs = objgetp(mctx, objn, prop, (dattyp *)0); /* get ignored prop */
728 objptr = (objdef *)mcmlck(mctx, objn); /* lock the object */
729 prpflg(objofsp(objptr, pofs)) &= ~PRPFIGN; /* no longer ignored */
730 mcmunlck(mctx, objn); /* unlock the object */
731 break;
732
733 case OBJUCHG:
734 memcpy(pr, p, (size_t)PRPHDRSIZ);
735 p += PRPHDRSIZ;
736 objsetp(mctx, objn, prop, prptype(pr), (void *)p, (objucxdef *)0);
737 break;
738
739 case OBJUCLI:
740 (*undoctx->objucxcun)(undoctx->objucxccx, p);
741 break;
742 }
743 }
744
745 /*
746 * Determine if it's ok to add undo records - returns TRUE if a
747 * savepoint has been stored in the undo log, FALSE if not.
748 */
objuok(objucxdef * undoctx)749 int objuok(objucxdef *undoctx)
750 {
751 ushort prv;
752
753 /* see if there's any more undo information */
754 if (undoctx->objucxprv == undoctx->objucxhead)
755 return(FALSE);
756
757 /* look for most recent savepoint marker */
758 for (prv = undoctx->objucxprv ;; )
759 {
760 if (undoctx->objucxbuf[prv] == OBJUSAV)
761 return(TRUE); /* found a savepoint - can add undo */
762
763 /* if we've reached the tail, there are no more undo records */
764 if (prv == undoctx->objucxtail)
765 return(FALSE); /* no savepoints - can't add undo */
766
767 /* get previous record */
768 memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
769 }
770 }
771
772 /*
773 * Undo back to the most recent savepoint. If there is no savepoint in
774 * the undo list, NOTHING will be undone. This prevents reaching an
775 * inconsistent state in which some, but not all, of the operations
776 * between two savepoints are undone: either all operations between two
777 * savepoints will be undone, or none will.
778 */
objundo(mcmcxdef * mctx,objucxdef * undoctx)779 void objundo(mcmcxdef *mctx, objucxdef *undoctx)
780 {
781 ushort prv;
782 ushort sav;
783
784 /* see if there's any more undo information */
785 if (undoctx->objucxprv == undoctx->objucxhead)
786 errsig(undoctx->objucxerr, ERR_NOUNDO);
787
788 /* look for most recent savepoint marker */
789 for (prv = undoctx->objucxprv ;; )
790 {
791 if (undoctx->objucxbuf[prv] == OBJUSAV)
792 {
793 sav = prv;
794 break;
795 }
796
797 /* if we've reached the tail, there are no more undo records */
798 if (prv == undoctx->objucxtail)
799 errsig(undoctx->objucxerr, ERR_ICUNDO);
800
801 /* get previous record */
802 memcpy(&prv, &undoctx->objucxbuf[prv+1], sizeof(prv));
803 }
804
805 /* now undo everything until we get to the savepoint */
806 do { obj1undo(mctx, undoctx); } while (undoctx->objucxhead != sav);
807 }
808
809 /* initialize undo context */
objuini(mcmcxdef * ctx,ushort siz,void (* undocb)(void *,uchar *),ushort (* sizecb)(void *,uchar *),void * callctx)810 objucxdef *objuini(mcmcxdef *ctx, ushort siz,
811 void (*undocb)(void *, uchar *),
812 ushort (*sizecb)(void *, uchar *),
813 void *callctx)
814 {
815 objucxdef *ret;
816 long totsiz;
817
818 /* force size into valid range */
819 totsiz = (long)siz + sizeof(objucxdef) - 1;
820 if (totsiz > 0xff00)
821 siz = 0xff00 - sizeof(objucxdef) + 1;
822
823 ret = (objucxdef *)mchalo(ctx->mcmcxgl->mcmcxerr,
824 (sizeof(objucxdef) + siz - 1),
825 "objuini");
826
827 ret->objucxmem = ctx;
828 ret->objucxerr = ctx->mcmcxgl->mcmcxerr;
829 ret->objucxsiz = siz;
830 ret->objucxhead = ret->objucxprv = ret->objucxtail = ret->objucxtop = 0;
831
832 /* set client callback functions */
833 ret->objucxcun = undocb; /* callback to apply client undo */
834 ret->objucxcsz = sizecb; /* callback to get size of client undo */
835 ret->objucxccx = callctx; /* context for client callback functions */
836
837 return(ret);
838 }
839
840 /* discard all undo records */
objulose(objucxdef * ctx)841 void objulose(objucxdef *ctx)
842 {
843 if (ctx)
844 ctx->objucxhead =
845 ctx->objucxprv =
846 ctx->objucxtail =
847 ctx->objucxtop = 0;
848 }
849
850 /* uninitialize the undo context - release allocated memory */
objuterm(objucxdef * uctx)851 void objuterm(objucxdef *uctx)
852 {
853 /* free the undo memory block */
854 mchfre(uctx);
855 }
856
857 /* revert object to original (post-compilation) values */
objrevert(void * ctx0,mcmon objn)858 void objrevert(void *ctx0, mcmon objn)
859 {
860 mcmcxdef *mctx = (mcmcxdef *)ctx0;
861 uchar *p;
862 prpdef *pr;
863 int cnt;
864 int indexed;
865
866 p = mcmlck(mctx, objn);
867 pr = objprp(p);
868 indexed = objflg(p) & OBJFINDEX;
869
870 /* restore original settings */
871 objsfree(p, objrst(p));
872 objsnp(p, objstat(p));
873
874 /* go through original properties and remove 'ignore' flag if set */
875 for (cnt = objnprop(p) ; cnt ; pr = objpnxt(pr), --cnt)
876 prpflg(pr) &= ~PRPFIGN;
877
878 /* touch object and unlock it */
879 mcmtch(mctx, objn);
880 mcmunlck(mctx, objn);
881
882 /* if it's indexed, rebuild the index */
883 if (indexed) objindx(mctx, objn);
884 }
885
886 /* set 'ignore' flag for original properties set in mutable part */
objsetign(mcmcxdef * mctx,objnum objn)887 void objsetign(mcmcxdef *mctx, objnum objn)
888 {
889 objdef *objptr;
890 prpdef *mut;
891 prpdef *p;
892 int statcnt;
893 int cnt;
894 int indexed;
895 prpdef *p1;
896
897 objptr = (objdef *)mcmlck(mctx, (mcmon)objn);
898 p1 = objprp(objptr);
899 indexed = objflg(objptr) & OBJFINDEX;
900
901 /* go through mutables, and set ignore on duplicates in non-mutables */
902 for (mut = (prpdef *)(objptr + objrst(objptr)),
903 cnt = objnprop(objptr) - objstat(objptr) ; cnt ;
904 mut = objpnxt(mut), --cnt)
905 {
906 for (p = p1, statcnt = objstat(objptr) ; statcnt ;
907 p = objpnxt(p), --statcnt)
908 {
909 /* if this static prop matches a mutable prop, ignore it */
910 if (prpprop(p) == prpprop(mut))
911 {
912 prpflg(p) |= PRPFIGN;
913 break;
914 }
915 }
916 }
917
918 mcmtch(mctx, (mcmon)objn);
919 mcmunlck(mctx, (mcmon)objn);
920 if (indexed) objindx(mctx, objn);
921 }
922
923 /*
924 * Build or rebuild a property index for an object.
925 */
objindx(mcmcxdef * mctx,objnum objn)926 void objindx(mcmcxdef *mctx, objnum objn)
927 {
928 uint newsiz;
929 uint avail;
930 objdef *objptr;
931 uint cnt;
932 prpdef *p;
933 uchar *indp = nullptr;
934 uchar *indbase;
935 uint icnt;
936 uint first;
937 uint last;
938 uint cur = 0;
939
940 objptr = (objdef *)mcmlck(mctx, objn); /* get object pointer */
941 cnt = objnprop(objptr); /* get number of properties */
942 p = objprp(objptr); /* get pointer to properties (or old index) */
943 newsiz = 2 + 4*cnt; /* figure size needed for the index */
944
945 avail = mcmobjsiz(mctx, objn) - objfree(objptr);
946
947 /* insert space for the index; expand the object if necessary */
948 if (avail < newsiz)
949 {
950 ushort need;
951
952 newsiz += 10*4; /* add some extra space for later */
953 need = newsiz - avail; /* compute amount of space needed */
954 objptr = objexp(mctx, objn, &need);
955 p = objprp(objptr);
956 }
957
958 /* now build the index */
959 indbase = objpfre(objptr);
960 for (icnt = 0 ; cnt ; p = objpnxt(p), --cnt, ++icnt)
961 {
962 uint ofs = (uchar *)p - (uchar *)objptr;
963
964 if (icnt)
965 {
966 /* figure out where to insert this property */
967 first = 0;
968 last = icnt - 1;
969 for (;;)
970 {
971 if (first > last) break;
972 cur = first + (last - first)/2;
973 indp = indbase + cur*4;
974 if (indp[0] == p[0] && indp[1] == p[1])
975 break;
976 else if (indp[0] < p[0]
977 || (indp[0] == p[0] && indp[1] < p[1]))
978 first = (cur == first ? first + 1 : cur);
979 else
980 last = (cur == last ? last - 1 : cur);
981 }
982
983 /* make sure we're positioned just before insertion point */
984 while (cur < icnt
985 && (indp[0] <= p[0]
986 || (indp[0] == p[0] && indp[1] <= p[1])))
987 {
988 indp += 4;
989 ++cur;
990 }
991
992 /* move elements above if any */
993 if (cur < icnt)
994 memmove(indp + 4, indp, (size_t)((icnt - cur) * 4));
995 }
996 else
997 indp = indbase;
998
999 /* insert property into index */
1000 indp[0] = p[0];
1001 indp[1] = p[1];
1002 oswp2(indp+2, ofs);
1003 }
1004
1005 /* set the index flag, and dirty and free the object */
1006 objsflg(objptr, objflg(objptr) | OBJFINDEX);
1007 mcmtch(mctx, (mcmon)objn);
1008 mcmunlck(mctx, (mcmon)objn);
1009 }
1010
1011 /* allocate and initialize an object */
objnew(mcmcxdef * mctx,int sccnt,ushort propspace,objnum * objnptr,int classflg)1012 objdef *objnew(mcmcxdef *mctx, int sccnt, ushort propspace,
1013 objnum *objnptr, int classflg)
1014 {
1015 objdef *o;
1016 mcmon objn;
1017
1018 /* allocate cache object */
1019 o = (objdef *)mcmalo(mctx, (ushort)(OBJDEFSIZ + sccnt * 2 + propspace),
1020 &objn);
1021
1022 /* set up object descriptor for the new object */
1023 objini(mctx, sccnt, (objnum)objn, classflg);
1024
1025 *objnptr = (objnum)objn;
1026 return(o);
1027 }
1028
1029 /* initialize an already allocated object */
objini(mcmcxdef * mctx,int sccnt,objnum objn,int classflg)1030 void objini(mcmcxdef *mctx, int sccnt, objnum objn, int classflg)
1031 {
1032 objdef *o;
1033 uint flags = 0;
1034
1035 /* get a lock on the object */
1036 o = (objdef *)mcmlck(mctx, objn);
1037
1038 memset(o, 0, (size_t)10);
1039 objsnsc(o, sccnt);
1040 objsfree(o, ((uchar *)objsc(o) + 2*sccnt) - (uchar *)o);
1041
1042 /* set up flags */
1043 if (classflg) flags |= OBJFCLASS;
1044 objsflg(o, flags);
1045
1046 /* tell cache manager that this object has been modified */
1047 mcmtch(mctx, objn);
1048 mcmunlck(mctx, objn);
1049 }
1050
1051 /*
1052 * Get the first superclass of an object. If it doesn't have any
1053 * superclasses, return invalid.
1054 */
objget1sc(mcmcxdef * ctx,objnum objn)1055 objnum objget1sc(mcmcxdef *ctx, objnum objn)
1056 {
1057 objdef *p;
1058 objnum retval;
1059
1060 /* lock the object */
1061 p = mcmlck(ctx, (mcmon)objn);
1062
1063 /* get the first superclass if it has any */
1064 if (objnsc(p) == 0)
1065 retval = MCMONINV;
1066 else
1067 retval = osrp2(objsc(p));
1068
1069 /* unlock the object and return the superclass value */
1070 mcmunlck(ctx, (mcmon)objn);
1071 return retval;
1072 }
1073
1074 } // End of namespace TADS2
1075 } // End of namespace TADS
1076 } // End of namespace Glk
1077