!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! 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;