!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!   IMem                An Inform 6 library to implement Run-time allocation
!                       of dynamic memory to objects. By L. Ross Raszewski
! Version 2           <[email protected]>
!
! New in this version:
! *It is now possible to swap out objects other than
! the last one swapped in.
! *Big news: Support for array properties.
! Debugging verb "showheap" will show the current status of the heap
! This should be just about compliant with inform 6.20's strict
! error checking. If it isn't, TURN STRICT ERROR CHECKING OFF.
! A bug stemming from inform's lack of unsigned arithmatic was removed.
!
!
! Things to look out for:
!  A number of people have asked me about problems that have crept in
!  when using imem.
!  The biggest problem seems to be that, for one reason or another,
!  objects were being swapped out more than once, or without
!  first being swapped in. Imem now silently rejects attempts to
!  swap out of the heap objects that have the swapped_out flag set.
!  Make sure you know EXACTLY when the code to swap an object in or
!  out will be called. Remember, there's lots of ways to move the player,
!  So trapping the before rule for "go" in an adjacent room isn't sufficient
!  to guarantee that a room's contents will be swapped in when the player
!  enters.
!
!
! First, I would like to apologize if I use terms that are not standard
! for refering to the workings of this library. I will endeavor to be
! consistant with myself, if not with the rest of the world.
!
! Recently, it seems that more than a few people have run into a rather
! unfortunate limitation of the Z-machine, the maximum 64K dynamic ("low")
! memory. Ideas have been batted around to discard Z and target inform
! to something else, or whatnot, but so far, no one's been willing to
! actually DO anything about it. I wrote IMem to give authors a way around
! the limitations of Z until someone comes up with something better. I'm sure
! that even if we do get a new format, some people will still want to stick
! to the Z-machine, and this library can help maximize the amount that Z
! can be extended.
!
! IMem uses a system which I call "Pooled dynamic memory". It does have
! several limitations compared to normal low memory, which will be discussed
! in depth. IMem's usefullness relies on several assumptions which I have
! made:
!  1. In a game large enough to break 64k low memory, there will be a
!     large number of objects which are only accessible during certain
!     sections of the game;
!       ex: Jigsaw, Time: All things come to an end, So Far
!  2. Most of these objects are scenery, and do not change much over the
!     course of the game.
!       ex: Curses, etc.
!  3. Any changes which occur to this sort of object is recored in the
!     attribute flags, not in the object's properties.
!       ex: My own coding practices. Counterexamples: any object which
!           updates itself by overwriting one of its properties
!  4. Every object does not need to be abvailable at the same time
!       ex. Lots of games
!
!  Now, if a large number of objects in your game meet these qualifications,
!  IMem may be the thing for you. IMem-managed objects reside almost entirely
!  in high-memory, except when they are swapped in. As a result, these objects
!  are partially static -- the sections contained in high-memory cannot change
!  during gameplay (sort of. I'll explain later). For the purposes of this
!  document, "High-memory objects", "semi-static objects" and
!  "IMem-Managed Objects (IMOs)" all refer to the same thing -- objects managed
!  by IMem.
!
!
!  Compatability issues:
!   IMem is based on the current architecture of the v5/8 Z-machine.
!   The constants WORD_SIZE, NUM_PROPS, OBJECT_LOCATION, OBJECT_SIZE
!   and PROPERTIES_TABLE are based on values given in the specification
!   of the Z-machine, section 12, and may need to be adjusted for any
!   new and mysterious Z-machine version.
!
!   IMem does not work with version 3 games.
!   Well... I don't THINK it works for version 3 games. You might be able to
!   Alter the symbolic constants and coerce it into operation.
!
!  Usage:
!   before using a high-memory object, you must swap it into low memory.
!   this library gives the attribute "swapped_out" to IMOs
!   which are currently not residing in low memory.  Before performing an
!   action on an object which may potentially be an IMO,
!   always check that it is not "swapped_out" (there are some exceptions
!   which I will discuss later.)
!
!   To understand how IMOs are swapped in, it is nessecary
!   to first understand how they are stored...
!   HOW IMEM-MANAGED OBJECTS (IMOs) ARE STORED:
!    In the Z-machine, an object consists of two (sometimes three) major
!    parts:
!       Part 1: The Object Header
!         The object header is an array of size OBJECT_SIZE bytes. This
!         array contains the attribute flags, the location of the object
!         within the object tree, and the location of the property
!         table for the object.
!         In IMem:
!          The object header for an IMO resides in low memory all the time.
!          This means that even IMOs leave an OBJECT_SIZE "footprint" in
!          the game file.
!          Consequentially, the information in the header is fully dynamic,
!          and may be both read and written to even when the object is
!          swapped out. Thus, attributes may be set or tested even when the
!          object is swapped out. The object may also be moved within the object
!          tree, via the "move" command.  Any IMO you create should
!          start with its "swapped_out" attribute set.
!      Part 2: The Property Table
!        This is a variable-sized table, which contains the hardware short
!        name of an object, and the common property values.
!       In IMem:
!        When an object is swapped into low memory, IMem creates a property
!        table for the object based on a function in high memory. While
!        the property table is in low memory, you can treat it just like a
!        normal object. It is legal to reassign a property
!        (ie. obj.prop=constant), however, this reassignment will not persist
!        between swapping out and back in (in fact, this can be beneficial,
!        as swapping an object out, then back in will "reset" the object's
!        properties (but not the attributes or location).
!        IMem DOES NOT give objects a hardware short name. Instead, provide
!        the object with a short_name property.
!!!!!!!!!!!!!!!
!  (This block of text is obsolete, but is left in for reverse compatability)
!        IMem also does not currently support array properties.
!        The most common place where this will be noticable is the
!        name property. If an object has more than one word as its typable
!        name, use a parse_name function instead.
!!!!!!!!!!!!!!!
!
!        The property builder function which creates the property table has the
!        following format:
!         The function accepts two arguments. The first argument contains
!         a common property number. If the second argument is 0, the function
!         should return true is the object should provide the property listed
!         in the first argument.
!
!NEW!!  To generate an array in a property, return the number of entries
!       to be placed in the array. When the second argument is 2, the FOURTH
!       argument to the function will hold an array. Information written to
!       that array becomes the array data for the object. Imem will happily
!       allow you to write past the end of dynamic memory, or write more
!       words than you asked for. In the latter case, the extra entries
!       will be happily chopped off by imem. In the former, you will
!       probably crash the interpreter. Be careful.
!       Entires not initialized by the object builder will still be allocated
!       but will contain garbage and NOT zeroes. (at least, they may not be 0)
!       There is a limit of about 30 Word-length array entries per property
! NOTE TO ADVANCED USERS:
!       This can be used to kick imem into a sort of "fully manual
!       operation". An object builder can return the total size of the
!       property table (less two bytes) as an array request. Subtract 2
!       from the fourth argument to recieve an array which describes
!       the entire property block for the object, which can be
!       constructed "by hand" according to the specification of the Z-machine
!
!
!         For non-array properties, this happens:
!         Then the second argument is 1, it should return the value of the
!         property given by the first argument.
!
!         An example:
!         object foo "" has swapped_out;
!         [ FooProps prop code obj array;
!           if (code==0)
!            if (prop==description or before or initial or short_name) rtrue;
!            else if (prop==name) return 4;
!            else rfalse;
!           else switch (prop)
!                { description: return "It's a bar of Foo";
!                  before: return Foo_Before;
!                  initial: return "There is a Foo bar here.";
!                  short_name: return "FooBar";
!                  name: if (code==2)
!                         array-->0='foo'; array-->1='bar';
!                         array-->2='foobar'; array-->3='baz';
!                }
!         ];
!        will create the following object:
!        object foo "" with
!         name 'foo' 'bar' 'foobar' 'baz',
!         short_name "FooBar",
!         initial "There is a Foo bar here.",
!         before Foo_Before,
!         description "It's a bar of Foo";
!       NOTE:  In inform, functions attached to objects return FALSE
!              be default, and all other functions return TRUE.
!              Because functions for IMOs are not attached to objects
!              at compile-time, you need to add a explicit rfalse; to the
!              end of the function.
!       NOTE: An optional third argument holds the object beign created
!       IMEM AND CLASSES: The class of an object is stored in property 2
!       of the object. returning the name of a class object for property 2
!       will make "ofclass" evaluate to true, however, it does not copy the
!       class properties to the object.
!
!      (Part 3: The individual property table
!        Property 3 of an object with individual properties containes a pointer
!        to the individual property table of that object if it provides any
!        individual properties.
!        In IMem:
!         IMem does not provide any direct support for individual properties.
!         A user CAN create an individual property table according to the
!         specification given in the Inform Technical Manual (9.5), and then
!         return that array for property 3)
!
!   Now that the technical stuff is out of the way, on to using IMem:
!   To Swap An Object into Memory:
!       Call SwapIn(Object,Function); where Object is a swapped-out IMO,
!       and Function is the property builder function for the object.
!       NOTE: There is nothing to stop you from using a property builder
!       function for another object.
!       SwapIn(Foo,Foo_props); SwapIn(Bar,Foo_props); will produce two objects
!       with identical property tables.
!       SwapIn will return the object if it is successful. If the object
!       is already swapped in, it will not reload the property table,
!       and returns FALSE.
!       IMem does no run-time range checking. If you load more objects than
!       will fit into the IMem heap, you risk corrupting memory. SwapIn
!       will assign the property table to an out of range variable and
!       crash the interpreter. It is the responsibility of the author to
!       make sure the heap is large enough. Define the constant HEAP_SIZE
!       before inclusion to set the heap size (default is 1024 bytes, which
!       isn't much really.)  A general rule of thumb is that each property
!       of an object is 3 bytes, and each object has an overhead of 6 bytes.
!       Array properties have a length equal to twice the number of entries
!       plus two.
!       The Heap is always (Heap_Pointer-Heap) bytes long, and has
!       (HEAP_SIZE*WORD_SIZE)-(Heap_Pointer-Heap) bytes remaining
!
!   To swap an object out of memory:
!       Call SwapOut(Object); to take the object out of low memory.
!       NOTE: It is MUCH faster to treat the heap as a stack, and
!       always swap out from the top -- that is, try to swap items
!       out in the reverse order as they were swapped in.
!
!   In normal usage, it is advisable ot swap in objects a "page" or set at
!   a time. For example, when a player enters a particular section of a game,
!   a function may be called to swap in all the IMOs for that section,
!   and a second function could swap out all the items when the player leaves
!   (Note that items must be swapped out in the reverse order as they were
!   swapped in)
!
!  CONDITIONS FOR USE
!  This library is free for use in any game released as public domain,
!  freeware, or free software, on the condition that the name of the author
!  and of the library appears in the game credits.
!
!  For commercial, shareware, and other for-profit games, this library may
!  be used, provided that the name of the author appears in the game credits,
!  in exchange for one copy/registration of the game. Contact
!  L. Ross Raszewski <[email protected]> for details.
!
system_file;
ifndef IMEM_LIBRARY;
ifndef temp_obj;
object temp_obj "IMEM temp";
endif;
Constant IMEM_LIBRARY 10;
Default WORD_SIZE 2;
Default NUM_PROPS 63;
Default OBJECT_LOCATION $0a;
Default PROPERTIES_TABLE 6;
Default OBJECT_SIZE 14;
Default HEAP_SIZE 512;
Attribute swapped_out;
Array Heap-->HEAP_SIZE;
Global Heap_pointer=Heap;
[ Obj_Offset obj;
 return (OBJECT_LOCATION-->0)+(NUM_PROPS*WORD_SIZE)+((obj-1)*OBJECT_SIZE);
];
[ SetPropertyTable obj a;

  obj=Obj_offset(obj);
  @storew obj PROPERTIES_TABLE a;
!  Obj_Offset(obj)-->PROPERTIES_TABLE=a;
];
[ CreatePropTable fun obj i j;
 j=heap_pointer+WORD_SIZE;
 j-->0=Obj_Offset(obj)+(PROPERTIES_TABLE*WORD_SIZE);
 j=j+WORD_SIZE;
 i=objbuilder(j,fun,obj);
 j-->i=i;
 Heap_Pointer=j+(i*WORD_SIZE);
 if ((Heap_Pointer-Heap)>(WORD_SIZE*HEAP_SIZE))
  { print "^***IMEM ERROR: HEAP OVERFLOW***^"; return -1;}
 return j;
];
[ DeletePropTable i;
 i=Heap_pointer-->0;
 Heap_pointer=Heap_pointer-((i+2)*word_size);

 if ((Heap_pointer-Heap)<0) { Heap_pointer=heap; return -1; }
];
[ ObjBuilder Hp fun obj i j k size;
 Hp->0=0;
 k=1;
 for (i=Num_props:i>0:i--)
 {
  size=fun(i,0,obj);
  if (size)
  {

    if (size==1)
   {
    Hp->k=i+64;
    k++;
    j=fun(i,1,obj);
    (Hp+k)-->0=j;
   }
   else
   {
   Hp->k=i+128;
   Hp->(k+1)=(size*WORD_SIZE)+128;
   k=k+2;
   fun(i,2,obj,Hp+k);
   }
   k=k+(size*(WORD_SIZE));
  }
 }
 Hp->k=0;
 k=k/WORD_SIZE;
 k++;
 return k;
];

[ SwapIn Obj Builder;
 if (obj hasnt swapped_out) rfalse;
 SetPropertyTable(Obj,CreatePropTable(Builder,obj));
 give obj ~swapped_out;
 return obj;
];
[ PopOut Obj i;

 if (Obj_Offset(obj)+(PROPERTIES_TABLE*WORD_SIZE)==(Heap_Pointer-((Heap_pointer-->0+1)*WORD_SIZE))-->0)
 {
  DeletePropTable();
  i=Obj_Offset(temp_obj);
  @loadw i PROPERTIES_TABLE i;
  SetPropertyTable(obj,i);
  give obj swapped_out;
  rtrue;
 }
 else rfalse;
];
[ SwapOut Obj prop_addr i prop_tab_addr j k l;
 if (obj has swapped_out) rfalse;
 if (PopOut(Obj)) rtrue;
 prop_tab_addr=Obj_Offset(Obj)+(PROPERTIES_TABLE*WORD_SIZE);
 @loadw prop_tab_addr 0 Prop_addr;
 i=(Heap_Pointer-((Heap_pointer-->0+1)*WORD_SIZE));
 k=Heap_pointer;
 j=Heap_Pointer+WORD_SIZE;
 while(i-->0~=prop_tab_addr && (i-Heap)>=0)
 {
  j=i;         ! j gets the address byte of the last object
  i=i-WORD_SIZE;       ! i gets the size byte of the next object
  k=i;
  i=i-((i-->0+1)*WORD_SIZE); !i gets the address byte of the next object
 }
 if (i==heap) rfalse;
 l=Heap_Pointer-k;
 l=-l;
 k=(k-->0+2)*WORD_SIZE;
 Heap_Pointer=Heap_Pointer-k;

 if (l) {
  @copy_table j i l;
  j=Heap_pointer-((Heap_Pointer-->0+1)*word_size);
  while((j-i)>=(0))
 {
  (j-->0)-->0=(j-->0)-->0-k;
  j=j-WORD_SIZE;       ! i gets the size byte of the next object
  j=j-((j-->0+1)*WORD_SIZE); !i gets the address byte of the next object
 }
 }
j=Obj_Offset(temp_obj);
@loadw j PROPERTIES_TABLE j;
SetPropertyTable(obj,j);
give obj swapped_out;
];

ifdef debug;
[ ShowHeapSub;
 print "Heap: ", Heap;
 print "^Heap Pointer: ", Heap_pointer;
 print "^Heap Size: ", (Heap_pointer-heap), " bytes";
 print "^Heap remaining: ", (HEAP_SIZE*WORD_SIZE)-(Heap_Pointer-Heap),
 " bytes^";

];
verb meta 'showheap' * -> ShowHeap;
endif;