@@TITLE Persistent MUDLibs@@
The DGD server, written by the excellent Dworkin, has changed a lot from its conceptual roots in LPMUD. The language is very different, the server features are very different. Its dialect of the LPC language is strongly based on the original LPC, and thus on C. But one of the visions that has driven some of DGD's most powerful and unusual features is persistence. Persistence makes a major difference in a MUD library, and you'll need to decide early on whether you want to take the effort to support it.
If you do decide to use persistence in a DGD MUDLib, the Kernel Library is worth a second look. It includes extra support for a lot of the issues that persistence can cause your MUD.
You'll hear the phrase 'persistent MUD' batted around a lot, and 'persistent world' batted around even more often. It can mean any of several different things. The DGD community talks about DGD providing excellent support for a persistent MUD. What do they mean by it?
The phrases often refer to the game design. However, DGD can't dictate your game design. Persistent game design is a different topic.
Primarily, the DGD community means that the game runs as though it would never shut down, and as though the server never went down, even if the server will go down. DGD is said to be persistent because the entire state of the game in memory can be preserved when the server is rebooted. This allows the game to resume in exactly the same condition that it was in when the server shut down, including all internal variables of all objects. The game, as a single 'virtual run' of the program, never stops and rarely even pauses. DGD's definition of Persistence involves being able to keep actual LPC-language objects around, potentially forever. The object pointers never expire, you never have to save or restore, you never need to write code to back up your objects — because conceptually, the server never, never goes down.
Par Winzell probably explained it best:
It's almost impossible to convey the experience of running a truly persistent Mud to somebody who has never done it. So many assumptions disappear. The way objects are created, areas designed, everything changes.
In traditional LPMud, virtually everything is an initialization script, like the create() function in your example. Half the code in a wizard's directory is startup code. In a persistent world, rather than write a lot of startup code, you tend to write behaviour code, and configure objects.
Most code in a traditional LPMud isn't real code, it's just a cumbersome configuration (set_this and set_that) technique. That pretty much goes away in a persistent game.
The cumulative effect is that if you design your mudlib to be fully persistent from scratch, you will find yourself making subtly different decisions on pretty much every single design question that comes up, and you end up with something drastically different than LPMud.
If you're never going to shut the server down, you need the ability to upgrade your LPC objects in place. Eventually you'll want to add new features, new content, new code. That means changing the code and data of existing LPC-language objects, and adding new code and data to them. Since you're never going to stop your current run of the program, you'll want to deploy all that immediately, preferably without even logging your players out. DGD allows you to do this.
One way DGD simulates a single continuous run of the program is the State Dump. A State Dump dumps a copy of all your LPC objects, whether in memory or swapped out, with all connections between them, to a file. You may then, later, start DGD from that State Dump file and it will pick up precisely where it left off. There are a (very) few things that necessarily change, such as the time and date, and what network connections are active. But mostly, a persistent DGD program can keep all objects around forever. When you restore from a dump, it just looks like the clock skipped, or like you took awhile to run the next thread of execution, and like all the network connections suddenly dropped. Effectively, that's what just happened.
Keeping a single run of the program going for so long requires a object cleanup solution. You need to be able to find old objects that are no longer in use and destruct them. Over days, weeks or years, an object leak will eventually bring your MUD down because old leaked objects will never go away. Rebooting no longer helps because all your old objects will stick around across reboots.
But the curse of eternal objects is also a disguised blessing. You can save a lot of code if you don't need to rebuild all your object structures when you reboot. You no longer need to write your data to disk. If you make a change in an object, it stays around even if you don't have a way to write that change to a data file. That can be a big, big change in the way you think about your MUD Library.
Normally, keeping all of your objects in RAM at all times would mean using a ridiculous amount of memory. However, DGD is a disk-based server, and believes very strongly in swapping things to disk constantly to save memory. This means that if you store data as 'in memory' LPC objects, they wind up taking no more memory or disk space than if you saved all your idle objects to disk. But you can access LPC objects in memory instantly, and DGD will transparently handle all swapping.
Persistence is also related to how you choose to represent the player in your MUD. Whether the player's body is represented by a single monolithic object or separated into a body and a connection object determines whether you can simply keep the player's body in memory when it's not connected. Note that you don't have to keep the body visible in-game to keep the body's LPC object stored somewhere in memory.
A monolithic object would contain the connection (originating IP address, the ability to send and receive data), but also all the player's information like skills, name, password, history and so on. In a non-MUD application, the equivalent would be user preferences and settings, interface code, and any avatar the user might have in the program. Separating out the connection-specific data (IP address, data transmission) allows you to store the persistent information like preferences and body data in a separate object, which (on a persistent server) never needs to be destructed, nor the settings saved to a file. That saves save⁄load code, as do many other similar situations.
Persistence also affects how equipment may be stored. A player's equipment can be kept with his body, including any customizations. The MUD would simply move it to an unused or virtual room, if the body persists anyway. Doing so is optional, of course, but it's another form of saving-to-file that can be avoided with a persistent MUD if the implementor so desires. If your MUD permits object customization, this can be a way to save it long-term.
You'll need to periodically delete unused players if you need to free up space, just like you'd have to with player files. You'll need to list all players and determine whether they've been idle so long that they need to be deleted. That's usually easier if the players are stored as in-memory LPC objects rather than needing to be parsed from data files.
A problem with upgrading in-place is that if you change certain parts of how your LPC objects work, you'll be left with a lot of LPC data in the old layout, designed for the old code. In a MUD with save and restore, it's possible to write a datafile adapter and kludge around this when you reboot. The equivalent for a persistent MUD is an update function.
To upgrade objects, you'll already need to have object management code to rebuild files. Your object manager can also call upgrade functions on master objects easily, and those master objects can track and upgrade clones. If you're using the Kernel Library, it's also possible to track clones by owner with the ObjRegD, or you can roll your own tracking system.
In any case, it's sometimes necessary to write code to initialize new fields (such as one you just added) in old objects, or move data from old fields into new ones. It's not harder than writing a datafile converter that does the same thing, but the results can be trickier if you do it wrong. Remember, in this case you're updating live data and you may have players connected at the time. Backing up first is a useful thing, and it may be useful to start a secondary 'test' server from a live statedump and try your upgrade code there first to make sure it works.
You may also find that it's useful to do an upgrade in stages. For instance, if you change the type and representation of a field in an LPC object, you may want to add the new one, put the data into its new location, then do a second LPC upgrade to remove the old field.
For instance, I might change my objects to stop storing a "brief description" string and instead store offsets into my arrays of nouns and adjectives to make a description. So if you see a "broad green tabard" then you're guaranteed you can "look broad green tabard" since it uses nouns and adjectives in the object.
However, when I take add the new data field (let's say an array of integer offsets, so it'll be an int pointer), I'll need to fill in decent values. I could make it default to saying the first two adjectives, then the first noun. So I'd set the array to [0, 1, 0], where the last element is the offset into the array of nouns.
So far, so good. But where do I put the code to fill in all those fields? One answer is "in the ObjectD". My favorite answer is to have the ObjectD call a function called "upgraded" if it exists on every object that gets upgraded. That's what Phantasmal does.
So I could put that code I mentioned above into the upgraded() function of my object type, then type "%full_recompile" at the command line, and my objects would all start being described as "big brown table" and "flat checkered floor" and things, all according to the first nouns and adjectives on their current lists. Then I'd go fix all the descs, 'cause that would look funny :-)
But I've had to write specific upgrade code for the specific change I made to my objects. In a standard MUD, I'd make the change to the object loading code, then shutdown the MUD and restart it. Then all my objects would be appropriately altered, but I'd also have to drop everybody's network connection to do it, and generally disrupt things. Not nearly as convenient. Plus, it's slow to do all that loading and saving of LPC objects using LPC code. Much faster to use a statedump.
If you use statedumps, there's no reason to ever move things out of memory, usually. You'll need backups, but statedumps are (under DGD) the fastest available backups if you're writing out most of your MUD's data. That saves you having to write code for reading and writing objects, and more importantly for large interconnected systems of objects.
You may choose to have object load⁄save code anyway, probably for object import⁄export. Skotos and Phantasmal both do this. However, object saving is slow compared to statedumps, so normally object saving will only be used for export to a different MUD or different program. You can also use the traditional save-reboot-load hack for object upgrade, just like a non-persistent server has to do. That can be easier in some cases, at the cost of some downtime (and having to write all the load⁄save code!). That's basically how Phantasmal uses it.
Having the ability to read or write datafiles can also be a good means of object creation, or to import zones from other types of MUDs, or similar builder tricks. Outside of your MUD, everything is going to move around as files, so it can be good to read and write them.
Certainly. A shovel's not the only way to dig a hole - you can use a spoon, if you're so inclined. You can write a large amount of code to save and restore huge interconnected systems of objects. You can do the years (over a decade now) of testing that DGD has already received on those systems, and do it all for yourself. None of this is impossible — DGD itself is written in C, so obviously a C-based system will do all of this just fine if it's designed correctly.
Now: have you asked yourself why you'd ever want to?
@@INCLUDE clones_vla@@