Hitchhiker's guide thru psyced source

When you first look at psyced code you may be quite confused, but a few words will help you find yourself around. most funny looking things have a good reason to be the way they are.
  1. LPC runs in a "sandbox" that starts in the "world" directory, like a less strict chroot environment, so all the real code is in there, although a few symlinks lead back to the top where the files for your local administration are placed, like the "config" and "place" directories.
  2. Don't be confused by the fact that traditional LPC drivers keep their LPC files with a ".c" suffix and data files with a ".o" suffix. This is obviously something that should be changed one day, but it is actually tricky to do in the driver source code, so we leave it to Lars to wonder if it's worth the trouble. :)
  3. We have put the heart of the psyced library into "net", presuming that, should other LPC projects like MUDs want to merge psyced into their system, psyced would take over several networking jobs for them. "drivers" contains the driver abstraction kits, although we currently support psyclpc and LDMUD. it does NOT contain the driver itself. "default" contains the text database. you can think of it as a more advanced form of gettext. it is serviced by "net/text.c". For each ".textdb" file a corresponding "/text" object is cloned, thus reloading one of them goes something like "/reload default/en/plain/text"
  4. We have avoided mentioning these paths literally in the code, in case you want to use the code in different directory names. That's why the code has so many NET_PATH and similar #define macros in it.
  5. All psyced code uses #include <net.h>. Reading that file and the files it includes first, will answer a lot of questions, like ME being the macro that points to the current object (could have been "this") and MYNICK and MYLOWERNICK containing the nickname of the current entity. UNIFORM(sender) will always produce the string uniform for a sender. HTTPS_URL and HTTP_URL contain the prefixes to the built-in webserver, if activated.
  6. You can distinguish system functions from object methods by their naming style. Methods defined within object classes do not contain underscores in their names, but have the second word capitalized instead, as in "createUser()" or "isNewbie()". Whereas system functions, no matter if provided by the driver like "lower_case()" and "convert_charset()", or if implemented in the psyced library like "register_target()" or "find_place()", have underscores in their names. Only one-word-names like "create()" or "sendmsg()" are admittedly impossible to distinguish.
  7. Now you think we could use capitalized names, but we prefer to keep those for complex data structures like the struct for parsed URLs as defined in url.h. It allows you to access parts of a uniform by name as in "uniform[UHost]" and "uniform[UScheme]". grep for UScheme to find examples.
  8. Completely uppercased words are used by preprocessor macros, as normal with C programming. They can be extremely powerful and save us from a lot of blues we would otherwise have in languages like perl or java.
  9. Our debugging macros are a sublime example of the power of the preprocessor. Just look at debug.h to find how the debug levels are converted into Px macros. P0 thru P4 print messages to the console depending on the debug level provided at system start. They are used as follows:
        P2(("%O got %O which should have been %O instead!\n", ME, this, that))
    If the debug level is 1, the complete statement will simply not exist. The Px-macros use sprintf inside, which behaves much like C sprintf except for %O which lets you output any variable safely. There is also a PT macro which is for temporary debugging. It will *always* show on your development system and *never* show in a productive environment. Similar to the Px-macros there are also Dx-macros. They let you construct more complex statements, like
        D2( if (this != that) PP("%O: %O != %O\n", ME, this, that); )
    and you can always "#if DEBUG > 2" directly, whenever you need to enclose several lines into conditional debug compilation. The S() macro is just a synonym for sprintf() and should not be used for real code. When working on psyced please add -DDEVELOPMENT to your start scripts so you can see messages generated by PT() and DT() as described before.
  10. You can apply varying degrees of debug level to specific parts of psyced, like for example the textdb subsystem, by adding -DDtext=2 to the psyclpc flags in psyced.ini. You can figure out which other parts of psyced are debuggable by doing a "grep -r 'define DEBUG D' ." in the world directory, then assign a debug level in the same way as the global debug level described above.
  11. We admittedly use ifdef/else/endif preprocessor constructs too much, we keep all sorts of dysfunctional code in it, maybe because one day we want to make it work, or we want to learn from it, or we want to keep ourselves from doing same old mistakes twice. Still all this ifdef'd out stuff stands in the way of getting started with psyced and getting a complete view of the code as it is in use. Sending those code fragments into repository history will probably delete it from consciousness and make us do old mistakes twice. A different approach to this is to use folding editors. Since all psyced developers are vim users, we have added "fold markers" to make inactive parts of code disappear. Should you be using a different editor to look at psyced files, try folding away anything between {{{ and }}}.
  12. The text database is a major source of voodoo in the psyced system. You can use it by including text.h. This will make you inherit the text client class textc.c, and define a T(mc, default) macro. You can specify which language or variation of the text database to use by specifying sTextPath(), then access the elements using the PSYC-typical message code hierarchy, as in T("_notice_session_end", "Boom!"). If you also need the PSYC-typical variable replacements in a template, you need to use the library function "psyctext()", which also gets used by the user output method "w()".
  13. Perl/php's hashes in LPC are called mappings. Similar to objects in ECMAscript. See the LPC documentation for details about them. They are in fact not implemented by means of hash tables, in order to save memory.
  14. In LPC, global variables in object classes are never accessible from outside. This is good programming design and I am glad this is enforced. So whenever you need to access somebody else's data, you need to use a properly set up interface method. They are typically called "qSomething()" to query a variable and "sSomething(value)" to set a variable to a certain value.
  15. But since objects are usually also PSYC entities we hardly have any traditional inter-object communication using the -> operator (also called call_other in LPC-speak). Instead the internal PSYC API is used, which brings a PSYC message from a source to a target. Only when the target is outside the psyced, the message will be rendered into actual PSYC protocol. To send a message the library function sendmsg is used as follows:
    varargs int sendmsg(mixed target, string mc, mixed data, mapping vars);
    and similarely the reception method that every PSYC entity class implements:
    msg(mixed source, string mc, mixed data, mapping vars)
    Here's a typical example on how to transmit a message:
    sendmsg(target, "_request_version", "[_nick] requests your version.",
    	([ "_nick" : MYNICK ]));
  16. There is a special set of variables which are by definition persistent. They are defined in storage.c and are always accessed using the "v()" macro as in v("name"). They typically hold the properties of a person or a place, and since they may one day come from an external data base, no direct access to the underlying mapping is granted. Instead you can "vSet()" or "vDel()" them. When accessing them in an other object, you can't use "->v()" so we had to introduce "->vQuery()".
  17. Global variables can however have modifiers. Most of them are declared "volatile" as they would by default get persisted away, and we don't want that. Persistent data should almost always be put into v().