! Entry - A library to extend Inform's entry point functionality to allow routines to effectively "insert themselves" into entry point routines.
! By Brendan "BrenBarn" Barnwell, alias OKB (
[email protected])
!!!
! Development Log
! Date What the dilly-o
! 2001/04/09 Began!
! 2001/04/09 Finished. That was quicker than I expected.
! 2001/04/10 Added IdentifyGlkObject entry point.
!!!
!!!!!!!!
! Documentation
!!!
! Including this library
! Include this library as early as possible. It must be included before the declaration of any Enterer objects, so you must include it before you include any Entry-compatible libraries.
! The Inform Designers Manual (3rd edition) says that you must Include Parser before you create any objects. Since Entry creates one object (Initializer), this would mean you'd have to Include Entry after including the Parser. I have tried Including Entry before Parser, and have felt no ill effects; however, this may be dependent on the specific compiler/interpreter you are using. (In any case, since most libraries do not need to be Included before Parser, you can probably get away with Including Entry immediately after Parser.)
!!!
! Overview
! Many times, if you're writing a library extension, you may need some kind of initialization procedure to be performed at run-time, but before the game actually starts, to set up your library. Or maybe your library needs to have access to some entry point routine. Unfortunately, the only way to make this happen under normal circumstances is to have the game author put a line in their Intialise routine that calls your initialization function (or, in the latter case, to explicitly call your library from the appropriate entry point routine).
! This is prone to error. For one thing, the author can forget to call your function, which can cause mysterious errors later in the game. Moreover, if the author is using several libraries, one of which must be initialized before another, which must be initialized after another, and so on, it can be a trial to figure out the order in which to initialize the various libraries.
! Entry gets around this problem by taking over the Initialise routine. Entry provides an Initialise routine which sorts out the proper order in which to initialize libraries, then initializes them all. The game author can provide his own Initialize routine (actually an object property) which takes care of game-specific tasks. This allows you to write effectively "self-initializing" libraries.
!!!
! How to make use of Entry if you're writing your own library
! In your library, define an object ofclass Enterer. You can call it whatever you want. This object will be the "base of operations" for your initialization (although it can make use of other objects or routines if needed). For information on how to set up your Enterer, see the definition of the Enterer class below.
! If a game author using your library uses Entry as well, it will initialize automatically. (I, the author, give explicit permission to authors of Inform library extensions to distribute Entry.h along with their library, to save the game author the trouble of having to search for more libraries.)
! If the game author is NOT using Entry, he will have to initialize your library "manually" from his Initialise routine. However, you can still use the initialize routine you have created as part of your Enterer object -- just put a note in your documentation telling the game author to call YourLibrary.initialize() from his Initialise routine (where YourLibrary is, obviously, the name of the Enterer object which you have created).
!!!
! How to use Entry in a game you're writing
! Include Entry as early as possible (see above). You cannot provide an Initialise routine. Instead, provide an object called Game ofclass Enterer, and give it a property called execute. This property must be a routine, and it will take the place of the normal Initialise routine. You can use it just like a standard Initialise routine (notably, you can return 2 to prevent the game banner from being displayed).
! Entry can only automatically handle libraries which are specially designed to make use of Entry's features. Any other libraries you use that require initialization at run-time must be initialized "manually" from your Game.initialize routine.
!!!
! A note
! Entry can be extended to allow libraries to hook into ANY entry point routine. I intend to add support for additional routines as I get around to it. Ideally, ALL entry point routines will eventually have Entry support.
! Conceivably, Entry could also provide a more flexible version of the Replace directive, allowing authors to add bits and pieces to the beginning or end of ANY routine, not just the entry points. This would, in effect, allow authors to modify existing routines without actually having to rewrite any code. (Of course, the modifications would have to come at the beginning or end of the exisiting routine -- changing the actual innards of a routine around is, as far as I know, impossible.)
!!!!!
! Supported entry points:
! Initialise
! IdentifyGlkObject
!!!!!!!!
System_file;
#IfNDef Entry;
Message "[Including <Entry>]";
Constant Entry;
! Top-level control
Default MaxEnterers 18; ! Maximum number of entries to any one routine
Array ExecuteOrder --> MaxEnterers; ! Holds the current list of entries, in order
Object Initializer "[Initializer]" ! Sorts entries in order of priority
with
offset 0,
debugMode 0,
organize [enterclass a b i;
! enterclass is the class of objects which can enter the calling routine
if (self.debugMode) {
print "{";
for (i=0 : i<Initializer.offset : i++) {
print (name) (ExecuteOrder-->i)," ";
}
print "}^";
}
for (i=0 : i<Initializer.offset : i++) ExecuteOrder-->i=0; ! Clear out old entries
self.offset=1;
! Loop through all Enterers pertinent to this entry point
objectloop (a ofclass enterclass) {
! By default, the game-provided routine (if there is one) has precedence
if (parent(a)==Game) ExecuteOrder-->0=a;
for (i=0 : i<(a.#follows)/WORDSIZE : i++) { ! Go through the follows
if ( ~~((a.&follows-->i) ofclass enterclass) ) { ! property, making sure
objectloop (b in (a.&follows-->i)) { ! all entries refer to
if (b ofclass enterclass) { ! objects pertinent to
(a.&follows-->i)=b; break; ! this entry point.
}
}
}
}
for (i=0 : i<(a.#precedes)/WORDSIZE : i++) { ! Do the same with
if ( ~~((a.&precedes-->i) ofclass enterclass) ) { ! the precedes property.
objectloop (b in (a.&precedes-->i)) {
if (b ofclass enterclass) {
(a.&precedes-->i)=b; break;
}
}
}
}
}
! Now to construct the priority-sorted list, object by object
objectloop (a ofclass enterclass && parent(a)~=Game) {
! Loop through the list we have so far
for (i=0 : i<self.offset : i++) {
! If the object under consideration must come before the currently listed object. . .
if ( (IsInProp(a,precedes,ExecuteOrder-->i)) || (IsInProp(ExecuteOrder-->i,follows,a)) ) {
self.insertAt(a,i); ! Insert it there
jump Inserted; ! and move to the next object
}
! If it must come after, keep on looking (since it might have to follow other, later objects)
if ( (IsInProp(ExecuteOrder-->i,precedes,a)) || (IsInProp(a,follows,ExecuteOrder-->i)) ) {
continue;
}
}
! If we've come this far, the object in consideration goes at the end of the list
self.insertAt(a,self.offset); ! So put it there
.Inserted;
}
if (self.debugMode) {
print "{";
for (i=0 : i<Initializer.offset : i++) {
print (name) (ExecuteOrder-->i)," ";
}
print "}^";
}
],
insertAt [obj position i; ! Inserts obj at position in ExecuteOrder
for (i=MaxEnterers-1 : i>position : i--) ExecuteOrder-->i=ExecuteOrder-->(i-1);
ExecuteOrder-->position=obj;
(self.offset)++;
];
Class Enterer ! Top-level class for all entry-point-entering objects
with
precedes 0,
! Each entry in this property (which should be a list of objects ofclass Enterer)
! represents another library which your library precedes in terms of initialization order. For
! example if you create object with "precedes OneLibrary OtherLibrary", your library's
! initialization will take place before those of OneLibrary and OtherLibrary.
! You can also include Game as an entry in this property to indicate that your library's
! initialization should take place before the game-specific initializing (Game.initialize).
! In fact, Game is the only "safe" thing to put in this property, since any other library you
! might reference could conceivably not be Included; this would result in a "no such constant"
! error when the game was compiled.
follows 0,
! Each entry in this property (which should be a list of objects ofclass Enterer)
! represents another library which your library follows in terms of initialization order. For
! example if you create object with "follows OneLibrary OtherLibrary", your library's
! initialization will take place after those of OneLibrary and OtherLibrary.
! You can also include Game as an entry in this property to indicate that your library's
! initialization should take place after the game-specific initializing (Game.initialize).
initialize [; rfalse; ];
! This property should contain your library's initialization routine.
! It is generally safest to only have your library specify (in its follow property) the libraries which it should FOLLOW in initialization. Presumably if your library must follow another, it requires some function of the other. However, specifying that your library PRECEDES another is questionable -- what if that other library is not subsequently Included? The only safe use of the precedes property, therefore, is to indicate that your library should be initializated prior to the execution of Game.initialize.
! In some situations, you may have a library which must follow another if that other exists, but which can also function even without that other library. In such a case, the safest course is to include a Default directive along with your Enterer object, like this:
! Enterer SomeLibrary
! with precedes Game, follows WeNeedThisLibrary ButWeCanDoWithoutThisOne,
! initialize [; "Initializing SomeLibrary. . ."; ];
!
! Default ButWeCanDoWithoutThisOne;
!
! This way, if ButWeCanDoWithoutThisOne is not included, the Default declaration will create a "placeholder" constant. This will compile correctly, and (assuming your library can indeed function without ButWeCanDoWithoutThisOne) there will be no side-effects.
! Initialise
[Initialise i gamereturn;
Initializer.organize(EnterInitialise);
for (i=0 : i<Initializer.offset : i++) {
if (parent(ExecuteOrder-->i)==Game) gamereturn=(ExecuteOrder-->i).execute();
else (ExecuteOrder-->i).execute();
}
return gamereturn;
];
Class EnterInitialise
class Enterer;
! IdentifyGlkObject
Constant Boozlbob;
#IfDef Boozlbob;
[IdentifyGlkObject phase type objref objrock i;
Initializer.organize(EnterIdentifyGlkObject);
for (i=0 : i<Initializer.offset : i++) {
if (ExecuteOrder-->i) (ExecuteOrder-->i).execute(phase,type,objref,objrock);
}
];
Class EnterIdentifyGlkObject
class Enterer;
#EndIf;
#IfNDef OKBGlulx;
! IsInProp obj prop value
! object property something
! Returns: number
! Returns true if value is an entry in obj.prop, false if not.
[IsInProp obj prop value length i;
length=(obj.#prop)/WORDSIZE;
for (i=0 : i<length : i++) {
if ((obj.&prop-->i)==value) rtrue;
}
rfalse; ];
#EndIf;
#EndIf;