First http://lpc.pages.de you shall read. Then http://lpc.pages.de/doc/LPC/ gives you an overview of the LPC syntax (we don't use alists or structs however, and we don't use the builtin line editor ed). When it comes to using closures we try to get away with inline closures where possible and keep the harder to read lambda closures for special cases (Yet it is quite an interesting lesson in stack machine design learning to read and understand lambda closures. Computer science at its bare bottom ;)). LPC is the language of the LPmud, ldmud and subsequently psyclpc drivers, and there are even more LPC implementations out there.
1.1 Why LPC for psyced?
Work on psyced started in 1996. Which technology to build upon was a tough decision. The obvious candidates were:
- Java - In fact there is a large PSYC implementation also in Java, because my company at the time absolutely wanted to have one. But even back then at the beginning of the Java hype I could see that threaded networking plus too minimalistic bytecodes would make Java a horrible server development technology. Also it isn't very nice for rapidly getting somewhere, and it has no multiple inheritance.
- C++ - If being rapid wasn't a criterium (as in rapid development, not speed of performance), then I could as well have gone for C++. At least C++ has solid object orientation, multiple inheritance and the oh so nice syntactical sugar. But PSYC is a smartly distributed protocol, that is why terrific performance in one single server is not generally necessary. Rapid development was more important. And what's more important. In a complex system such as psyced there are always forgotten special cases that lead to a runtime error. In C++ the program would crash. In LPC the error is logged, and the service continues. Very valuable!
- C was no option because the complexity of psyced absolutely required an object-oriented language. This doesn't exclude that one day we may have specialized lightweight PSYC servers in C or C++, but psyced isn't.
- Perl was pretty close to being an option, as it has so terrifically powerful bytecodes (like for instance s/red/blue/gm). It fell out of the option list as I had tried doing complex applications with it before, and things simply get messy after a while. The object-oriented syntax of perl is horrid. Still perlpsyc is a hell of a powerful PSYC implementation, just not as complex as psyced.
- Python - Did it address the problems I had with perl? I don't know. I didn't really review it. But from what I hear the Twisted networking library is only getting reliable now. Why is the native networking less popular? Whatever. I'm glad I didn't try Python - not because of the syntax, people seem to often be disturbed by that, I'm not - I hate missing bracket errors sufficiently to appreciate that - but a continous instability in the way networking is done wouldn't have been healthy for psyced. Maybe python would have been fine after all, too late to find out.
- Pike (or μLPC as it was named back then) was actually a serious candidate. I knew I was going to need an http implementation so I unpacked roxen and tried figuring out how to add the PSYC protocol to it. I clamourously failed seeing through roxen. Also I noticed μLPC came with threads and semaphores and all those things that make a chat server slow. I know that today's Pike made all of that optional, so today I would go for pike.
By the way, Yahoo Chat is also written in LPC.
1.2 Do I have to learn LPC to extend psyced?
Since we're answering FAQs here, we might as well bring up the next one. The answer is yes and no. If what you want to do can be clearly seperated into its own application, then there are two ways of integrating it with psyced. Either let it run as a totally independent PSYC entity, as we usually do with perlpsyc applications, or you can plug it into psyclpc as an external process which is launched on demand by psyced and exchanges data with it by means of a bidirectional pipe (somewhat like a CGI, but more efficient as the CGI is never unnecessarily terminated). We can help you with the necessary LPC glue to do that. If you look into the run directory you will find examples of external applications that have been used that way in the past. You can see it is pretty simple. But then some things you may want to do are deeply related to psyced and need to be addressed right there.
1.3 What does it take to learn LPC?
A real hurdle can be to understand syntax and working of the C preprocessor. Also C++ and pike have that, but no other obvious languages I know of. If you know what #ifdef #endif and #define mean and do, you're well on your way.
What's left to understand is a few extras like mappings and the super-natural object orientation in LPC. See below for that.
Finally, depending on what you want to do in LPC, you may have to dig very deep into psyced - that indeed is hard, but that would be hard with any language, as the language isn't really the problem - it's the complexity of the application which just cannot be denied.
All of this begs the question, "If so many people do not like LPC... why has most of the stuff been done in psyced?"
<Kol_Panic> It's psychological. There's a cognitive bias that assumes things are going to be more difficult than they appear. That's not true of LPC, especially in psyced. The docs don't prepare people for LPC to require very little trial and error to nail whatever appears not to be documented and psyced provides so many more useful programming hooks than are currently documented. For a couple examples of this, think about the ways that you would implement external programs that communicate with psyced over stdio or another interface that isn't yet defined in psyced. The LPC documentation leads you to think that you'll have to work with ERQ, but when you get involved with psyced code, you find that communication over stdio can be done with a spawn function provided in psyced's net library while adding a listening process for an additional network service in psyced not only does not involve learning the LPC ERQ interface, it is even easier than writing a socket application in most other programming languages.
Mappings are what other languages call associative arrays or simply hashes. They map keys to values. Very simple, but C doesn't have such a data type natively whereas in psyced we use that extensively. You can read about them in http://lpc.pages.de/doc/LPC/mappings but you don't really need to, as it is quite likely that you will immediately understand how they work the first time you see them.
3 LPC Inheritance
LPC has a huge advantage over Java being multiple inheritance. It makes LPC a better suited prototype language for modeling according to UML as UML also uses the multiple inheritance concept. See the LPC Guide on Inheritance for a tutorial.
4 LPC Networking
LPC sometimes takes surprisingly simple approaches to solving application programming interface (API) issues. By making the simple rule that every instance of an object class may only be in charge of one TCP connection, it throws out the need for a "descriptor" on its programming level. Instead, a couple of methods are called in the instance whenever something happens to the socket and a couple of system functions are available to modify the state of the TCP connection. As with every rule there is also a way to circumvent it (using erq), but if you're doing a good programming job you will not need that.
4.1 Accepting and Losing the Connection
When the driver triggers methods in objects the LPC terminology for this is applying them. The most obvious method is logon which gets called as soon as a connection has been established or could not be established. Similarely disconnected() is called when the connection is closed or lost (in the master to be exact, so the event doesn't get lost in case the handler object has gone away, but we forward it to the object if it is available and in good shape).
4.2 Receiving Input
The actual input is received by setting up a hook using input_to. LPC comes with a built-in very impressive command parser that finds commands defined in all objects in your virtual environment, but since we don't implement a MUD we don't use that (In theory you could put all connections into a "bag" together with a lot of objects that implement various parts of a protocol, then remove the "ctcp" object for instance, and whoops your IRC implementations would no longer understand CTCP, but hey nobody needs that level of modularity. We prefer to just #ifdef something like that).
4.3 Writing Output
4.4 Listening To Ports
Receiving a connection is handled by the LPC master object, which will create a handler object for each new object depending on the incoming port number. For traditional MUD paranoia reasons the ports to listen to are given to the driver on the command line while LPC runs in its sandbox (but could still allocate ports dynamically using erq). At the same time the master object needs to know which ports are allocated, so we have to put the port numbers into two places. Our configuration tools do that job.
4.5 Creating a TCP Connection
Opening up a connection is done using net_connect instead. Once the connection is created, logon() is called, so you can quite often use the same code for incoming and outgoing connection creations.
4.6 And Beyond
There are further functions to handle UDP, telnet negotiation, character set filtering, socket buffer sizes, prompting, zlib compression and SSL/TLS encryption. users returns a list of all objects with open TCP sockets (some historic function names have to be dealt with, sigh). If you had enough of all of this, remove_interactive closes the connection.
5 Conditional Compilation
Even if LPC isn't compiled in a traditional C/C++ fashion, it is still translated into stack machine code (bytecode if you will) and during that process a C-style preprocessor is applied which gives us enormeous compile time flexibility. We make extensive use of the preprocessor. place.gen to create places is an exhaustive demonstration, even if only applied to one topic. In fact we even try to keep a certain degree of abstraction from different LPC dialects as we may one day want to also support Pike or MudOS, not just psyclpc. This we do using apparent language extensions which are actually provided by the preprocessor:
5.1 lynXified language extensions
- ME should probably be called this or self and it actually maps to this_object.
- abbrev(string SMALL, string BIG) returns true if SMALL is an abbreviation of BIG.
- trail(string SMALL, string BIG) returns true if the BIG string ends in SMALL.
- replace(string STRING, string OLD, string NEW) replaces all occurrencies of OLD in STRING with NEW. See also regreplace in case you need to use a regular expression instead of simple text.
- index(string HAYSTACK, int NEEDLE) returns the index of the NEEDLE character (caution, that's not a string) in the string HAYSTACK or -1 if not found (In psyclpc this function is done using member, but we only want to use member() for finding keys in mappings as it returns either true or false and thus gets used in a completely different way as index).
- hhmm() and hhmmss() return current time as strings in colon format.
- stricmp(string ONE, string TWO) does a case insensitive comparison of two strings, but unlike its libc counterpart it simply returns true if there is a difference between the strings and false if they are the same (Currently this is done using lower_case as LPC has no built-in case-insensitive string comparison operator).
- each(ITERATOR, LIST) is a wrapping over foreach to support differing foreach syntaxes in LPC and Pike. same goes for mapeach(KEY, VALUE, MAPPING)
- array(TYPE) is the Pike way of declaring an array, for psyclpc the preprocessor maps it to the equivalent LPC syntax.
- volatile is a nicer name for the LPC variable modifier nosave. It keeps a variable from being serialized to harddisk using the built-in save_object mechanism (the funny .o files which aren't linkable objects at all).
5.1.1 neat things from perl
- unless() is "if not".
- until is "while not".
- chop() removes the last character of a string.
- chomp() does the same, but only if it is a newline character.
6 Regular Expressions
Modern psyclpc and ldmud drivers are linked against the pcre library, so whenever you want to use functions like regreplace you can use full-fledged perl regexps. Just remember one little gotcha: When writing the regexp into an LPC string you need to escape the backslashes, thus always write two where there should be one. Here's a typical example:
s = regreplace(s, "url://(\\S+)", "<a href='url://\\1'>url://\\1</a>", 1);
7 Web Applications
- Multicast Distribution: Your web application can be implemented in form of a decentralized context, scaling with your needs and popularity.
- Implicit Authentication: Web users don't need to be strangers. Since they are typically also users of psyced as a messaging server, you can work with their identity and even send them real-time messages anytime you like.
- Event Notification: Your application can trigger events in a chatroom, with all subscribers being informed immediately.
- Template system and Internationalization: The textdb helps you manage templates in various languages and designs, in order to separate application logic from HTML layout.
- Persistent Storage of Objects: You don't need to write any SQL, you just use variables and they are saved to the file system. In a future version of PSYC you can define variables which are mirrored to all subscribers of your application, the so-called decentralized state. This means they will always have up-to-date values in their copies of the variables. This kind of replication can also be used to maintain a backup server fully synchronized with the master.
8 Further Readings
8.1 More Pages from the LDMUD/psyclpc Documentation
8.2 The psyced API
is described in API.
8.3 The Developer's Guide to the Psyced Source
8.4 More Material on LPC in General
9 Historic Stuff
Aehnliches gilt auch fuer MUDs, wo sonst koennen Sozialpaedagogen (z.B.) aus reiner Spielfreude/faszination heraus auf die Schnelle objektorientiert Programmieren lernen? LPMUDs sind die beste Schule fuer OO Programmierung. Punkt. Meine Erfahrung. C++ und C sind schon halb gegessen wenn man LPC gelernt hat im MUD! Würden MUDs offiziell gefördert werden, ließe es sich dann auch anbieten, dass Menschen an die Programmierebene randürfen ohne das ganze Spiel durchspielen zu muessen (Geht derzeit nicht anders wegen dem grossen Andrang!).