MUDPyE v0.04 Engine Interface Documentation:
Contact the author at MUDPyE@gmail.com

Index:
  Section 1 - What Is MUDPyE?
  Section 2 - Object Writing Interface
  Section 3 - Coding Caveats



Section I:  What Is MUDPyE?
    MUDPyE stands for "(M)ulti-(U)ser (D)imension (Py)thon (E)ngine".  If you are unfamiliar with the concept of a MUD, I suggest that you search for the term at http://www.google.com/ as there is a lot to be said on the subject.  In relation to other MUD engines, MUDPyE has several goals in mind:

    *  Cross-platform compatibility.
    *  Easy to use interface. (Working on this one)
    *  Little overhead. (Working on this one too)
    *  Quick development and building.

As can be guessed, MUDPyE uses the Python language for both scripting and engine development; therefore, serious programming for MUDPyE requires that the user understand Python.  Python is an extremely powerful object-oriented scripting language and is also, fortunately, easy to learn.  The Python tutorial at http://docs.python.org/tut/tut.html should be sufficient to prepare most people for simple building under MUDPyE.

    Note:  This guide does not intend to fully document the MUDPyE engine.  This guide is for builders and programmers who wish to write MUDs under the engine, not for those who want to modify the engine itself.

    MUDPyE is currently developed by me, Magna.  I can be reached for contact at MUDPyE@gmail.com.



Section II: Object Writing Interface
    To write MUDs in MUDPyE, you must create objects from source files.  An object source file is a python file like any other, ending with a .py extension.  It is run in a specially constructed environment, and may define functions via which the engine may interact with it.  All engine hook functions begin with "_Sys_", meaning that you shouldn't use this prefix on any user-defined functions.  The object may also interact with the system in several ways, as will be described below.

    First of all, to make a new type of object you must associate its filename with an object type.  An object type is simply a unique, global identifier that tells the engine what source file to use for an object.  This allows changing of object source files without confusing the engine.  A mechanism is also included to convert object types seamlessly, but that is beyond the scope of this document(plus I haven't tested it).
    To associate a filename with an object type, one must associate the object type and source file inside a .mxf file([M]UDPyE Inde[x] [F]ile).  By default, the most important .mxf file is main.mxf in the root directory.  This can be changed by modifying the world settings file, which will be discussed later.  Basically, .mxf files support two types of command lines:
    1.  "index anotherMxfFileName"
    2.  "object objectType objectFileName"
The first command tells the engine that it should recursively scan the indicated .mxf file for more object type associations.  The second command tells the engine to associate "objectType" with "objectFileName", which means that any objects created with "objectType" will search for their source in "objectFileName".  Note, all objectFileName's are specified from the directory in which the .mxf file resides in.  Thus if you had an .mxf file at Bunnies/bunnies.mxf, the line "object BigBunnies BigBunnies.py" would look for the file Bunnies/BigBunnies.py.
    This is all a lot easier than it sounds.  Just check out main.mxf in the default distribution for an example.

    All objects should state the following three variables at the beginning of their file.  If they are not stated, the engine will fill them in with default values.

    "_imports" as a list of strings:  A list of objTypes to import as Pseudo-Modules.  These can be treated just like regular modules, except that they update automatically like any other mud object.  Note, if you need to import anything in the Python standard library, you can still use the "import" keyword.  However, you must take care that you don't define any objTypes in _imports that would conflict with regularly imported modules, or else the former will take precedence.
    DANGER WILL ROBINSON:  At the time of this writings, pseudo-modules give you a direct link to the global source context for the given object type.  Although you can't directly change it's attributes, you can modify attributes such as lists and dictionaries; thus, you should take extreme care that you don't do that(unless you have an insane reason to want to) or else you could royally mess things up.

    "_parents" as a list of strings:  This is a list of objTypes that this object is derived from.  This works like standard python inheritance in most ways.  Multiple parents are dealt with in a depth-first way, in order of their appearance in the list.  As an example, say B derives from A, D derives from C, then E derives from B and D(in that order).
The precedence list for what objTypes functions will be called is: E->D->C->B->A.  Thus, it is highly advisable that child classes be sufficiently specialized that they don't interfere with each other, especially in the case of diamond inheritance.

    "_version" as an integer:  This should default to 1.  When an object is loaded, the engine checks if the _version of the source file disagrees with the _versions dictionary of the object.  If it does, it attempts to call the function "_Sys_VersionUpdate".  This is convenient for cascadingly updating objects.  Note: It is _Sys_VersionUpdate's responsibility to change the object's version number.  If it doesn't, the object will continue to call _Sys_VersionUpdate every time it is loaded.  Changing the number is simple, just include a line such as:
  self._versions["thisObjType"] = newVersion


    The following is a list of _Sys_ hooks that all objects can define.  Not all of them need be defined, only those which you will be utilizing.

    "_Sys_Create(self, [additional arguments])" - Called when an object is created using objDB.CreateObj(objType, *args, **kwargs).  self is the newly created object reference, and additional arguments may be added that will be passed along from CreateObj.
    "_Sys_Load(self)" - Called when the database loads this object, and also immediately after the object is created.  Should be used to initialize data structures that can not be saved by mudPickle.
    "_Sys_Unload(self)" - Called when the database unloads the object, and also immediately before it is destroyed.  Should be used to do things such as close open files and data streams.  DO NOT COUNT ON THIS BEING CALLED.  If the engine crashes for some reason, this will not be called.  It is also not called before saving an object unless it's being specifically unloaded.
    "_Sys_Destroy(self)" - Called when an object is destroyed using objDB.DelObj(objRef).

    The following are called only on the world object.

    "_Sys_Boot(self, bootArgs)" - Called when the MUD is fully booted.  This is where you should start everything important up, such as servers.  It's also where you should tell important objects to register with the Callback server, as Callbacks are not saved over Boots.
    "_Sys_Unboot(self)" - Called when the MUD is unbooted.  Note, you shouldn't necessarily rely on this being called, as in most real-life cases the only time the MUD would be shutdown is if it crashes.  Use it to perform casual clean-up(no need to save anything).
    "_Sys_LogString(self, logString)" - Called whenever a string is logged.  logString is nicely formatted and perfect for printing to a log channel.

    The following are called only on objects of type "clientAvatarType" created by a server(see the Server interface) when using the TelnetClient type.  Currently, the TelnetClient type isn't exactly Telnet compliant, but it works.

    "_Sys_TelnetDO(self, doType)" - Called upon receiving a valid Telnet DO command.
    "_Sys_TelnetDONT(self, dontType)" - Called upon receiving a valid Telnet DONT command.
    "_Sys_TelnetWILL(self, willType)" - Called upon receiving a valid Telnet WILL command.
    "_Sys_TelnetWONT(self, wontType)" - Called upon receiving a valid Telnet WONT command.
    "_Sys_DNSResolved(self, hostAddr)" - Called when the DNS thread resolves this clients host address.
    "_Sys_ReceiveCommand(self, commandString)" - Called when an end-of-line is detected in the stream when in linemode(default).
    "_Sys_ReceiveChar(self, char)" - Called when any character is received while in charmode.

    The following are called only on objects passed as an eventObj to a server.

    "_Sys_ClientConnect(self, newAvatarObj)" - Called when a client has connected.
    "_Sys_ClientDisconnect(self, avatarObj)" - Called when a client has been disconnected by a script, e.g. server.DisconnectClient.
    "_Sys_ClientLinkdeath(self, avatarObj)" - Called when the client disconnects of their own accord, e.g. by shutting down their client without typing quit.

    Objects are created via calling the CreateObj function of an instance of class ObjectDatabase(of which there is only one).  From within an object's file, you can do this in several ways.  One is "mudWorld.objDB.CreateObj(objectType, *args, **kwrgs)" where objectType is a valid object type as discussed above and the additional args are anything you want to send to the object.  You can also use the CreateObj lambda included in the standard distrubtion to save typing.  The function CreateObj returns an instance of class MudObjectRef, which is an object that hides the details of loading and saving MudObjects.  In truth, there is no MudObject class, there is simply a MudObjectRef class.  MudObject's are handled internally as a dictionary by the database engine; however I will continue to use the term MudObject both for historical purposes and because it looks nifty that way.





Section III: Coding Caveats
    Writing objects in MUDPyE isn't exactly the same as writing normal python programs.  Because of the way the engine attempts to hide the fact that your object code is actually running inside a controlled environment, some strange things can occur.  Here are some coding caveats all MUDPyE coders need to be aware of:

    * When accessing a function variable of a MudObjectRef, it will always be lambda-dized to act as a bound method of that MudObject(even if it's not meant to act that way).  If you want to store a function in a MudObject that won't act in this way, you can do something like: "myMudObject.myFunc = [funcIWantToStore]".  Then, when you want the function, simply extract it from the list: "funcIWantToGet = myMudObject.myFunc[0]".  Trying to get MudObjectRef's to act exactly like class instances in this respect would take some incredible coding, and I'm not up to the task.  However, the minor inconvenience caused by the 1 out of 1000 occurances where you don't want the function lambda-dized is a small price to pay.
***** UPDATE:  The engine now users a wrapper class for all functions defined in a global source context.  For functions created elsewhere(like lambda's), this behavior won't occur.  You can also extract the raw function from the wrapper by accessing its .func member and save this to avoid this type of behavior.

    * When accessing a non-existant variable of a MudObjectRef, an exception is NOT raised.  Instead, a special value "callNone" is returned.  It simulates None in equality comparisons(callNone == None returns True), however, it is also callable with any number of arguments.  When called, it does nothing but return None.  This odd behavior allows for scripts to call functions without being sure they exist.  Great for system-hooks that may or may not exist(such as "On_Drink" being called on a sword by those silly newbies trying to quaff solid objects); however, you should understand that encompassing such calls in a try block is not necessary.  If you want to be sure a function is called, simply do this:

------
funcToCall = myObj.myFunc
if funcToCall == None:
  print "BIG TIME ERROR BOSS!"
else:
  #Do Stuff
------

    * Objects can be saved and loaded at any time; there are no guarantees about object persistancy(unless you set the object's _persist variable to True, then an object will stay in memory until the MUD reboots or it is manually removed from memory).  This is what the _Sys_Load function is for; anything you have that can not be saved by the mudPickle module must be restored in _Sys_Load.  mudPickle can save constants(int/float/str/MudObjectRef/etc), tuples consisting of constants, and lists and dictionaries containing any of the forementioned as well as other lists and dictionaries.  Recursion is okay in lists and dictionaries.  Tuples can only contain constants due to the difficulties in handling recursives inside of tuples.  Things that can not be properly saved are replaced with "None" values, so don't worry about crashing the engine or anything.
EXTREMELY IMPORTANT:  Objects are saved individually, therefore shared references between them are NOT SAVED AS SUCH(excluding MudObjectRefs, since they are just saved using their ID).  This really only applies to lists and dictionaries(and maybe tuples).  Here's an example of what I mean:  If object A has a list variable myList, and object B decides to store A.myList in a member variable for convenience, it might do something like this:

def _Sys_Create(self, A):
  self.AList = A.myList

The problem with this is, when either object is saved and then loaded, *IT WILL HAVE A NEW COPY OF ALIST*.  Imagine calling copy.deepcopy on AList and giving each object its own copy.  This means that when B attempts to append or delete or modify items in AList, it'll only affect its local version, not A.myList.  The solution to this is simple:

def _Sys_Create(self, A):
  self.A = A

def _Sys_Load(self):
  self.AList = self.A.myList

This way, B grabs the real version of A.myList everytime it is loaded, ensuring that it is sharing a reference with A.  You should also keep this in mind when making use of the "is" keyword.

    * You shouldn't permanently store a global variable from a PseudoModule, since you'll experience the reference breaking as above if the PseudoModule is updated.  Either store it in a temporary variable only for the duration of your function, or just refer to as as pseudoModuleName.myVar.

    * As I mentioned before, PseudoModules(what you get when you put an objType in the _imports list) provide a direct link to the *global source context* for that objType.  Although you can't directly set or delete variables from a PseudoModule, you can indirectly change mutable variables(like lists/dictionaries).  Be careful of this, as it will affect any other source context using that PseudoModule and any objects of the PseudoModule's type.

    * _imports and _parents are imported BEFORE the source is executed(they're actually manually parsed with a regular expression).  This means that imported PseudoModules and parent's data are available globally in the source context at execution time.  This isn't really a caveat, its kind of an anti-caveat.  A little something to soothe your nerves and make you feel at home.  Actually, it was a caveat for a while because they weren't available in the global part of the source, but I fixed it.  So don't worry.  Really.

    * Be careful of having too many callbacks executing too swiftly.  If the engine takes more time to execute its callbacks than the frequency rate of the callbacks themselves, it'll start calling the callbacks exponentially more and more until it hogs all the system resources. and essentially freezes.  I've never actually had this happen(you'd need a whole lot of callbacks at high frequency, or a really really slow system), but its good to keep in mind.