Phantasmal MUD Lib for DGD

Phantasmal Site > DGD > Writing a Library > Object Management

Managing and Upgrading DGD Objects

DGD is impressive in the degree of upgradability and persistence that it offers. However, to take full advantage of the upgradability, you'll need to observe some ground rules.

DGD's inheritance features aren't quite the same as a regular LPMUD, though they're far more powerful if used correctly.

You'd like to be able to upgrade all your code while leaving your data in place. For instance, you'd like to be able to write a new feature for your MUD, and enable it while players are logged in. Before the recompile, the feature isn't there. After the recompile, it is. Players don't see any difference until they try the new command, which works after the recompile. This is less of a pipe-dream than it sounds. Phantasmal already does it using the %full_rebuild command, and the Kernel Library has a less automated, more awkward way to do it, but it can work. Sadly, few other DGD libraries (except the closed-source Skotos servers) seem to do this.

One reason that few libraries bother to do this is that it requires tracking all object compilation and inheritance. The Kernel Library makes this slightly easier by encapsulating that functionality into an Object Manager (see the LPC Examples section for several Object Managers, or download Phantasmal and look at that one).

There are some other related tricks you can do. It's usually very convenient to call a function on objects that are recompiled but also have data. The data may need to be updated after the change in code. Phantasmal calls an "upgraded" function in any recompiled master object, if it exists (if clones or LWOs need to be updated, the master must keep a list of them or they must be looked up using the Kernel Library tracking facilities). Skotos' libraries use a different method based on the call_touch kfun to update objects as they are used. This makes a great deal of sense, given the quantities of objects that Skotos' libraries deal with.

As a random aside, remember that an object isn't compiled until the end of the thread where compile_object() gets called. So there are some subtle issues about what version of the "upgraded" function gets called. The old one, already compiled into the object? The new one, the one that isn't currently compiled? You may need to call_out() to a function that will call upgraded() after the end of the current thread so that the object will be recompiled, and the new version of upgraded() will be called.

John "West" McKenna's Inferno library has a significantly more complex approach, which allows for more interesting upgrades in some cases. Its upgrader builds a list of objects that need recompiling. It calls uninit() on them, then upgrading(). Then it recompiles, then calles upgraded() and finally init().

The uninit() and init() functions are expected to do everything that's required when an object moves from one place to another except the actual move. This includes removing⁄adding commands defined by the object from⁄to the user's grammar table.

upgrading() and upgraded() are very rarely used. They're there in case the new version of the code needs extra initialising.

The Kernel Library uses what's called an Object Manager, or ObjectD, to do all of this. That doesn't change what you can do, but it does alter some of the code.

Turning Stuff Off

If you're upgrading more than one object, or you're upgrading an object and its upgraded() function hasn't been called yet, you'll need to be sure to suspend network connections and network input while all that is going on. If you're halfway through an upgrade, it's dangerous to allow commands to execute, whether from players or NPCs, because objects may call other objects that aren't recompiled or upgraded yet. If you turn off new network connections, and turn off call_out() statements to every object except the one doing the upgrading (the Kernel Library has good ways to do this) then you can do a multi-object upgrade safely.

If you do this, be absolutely certain to have appropriate catch() and rlimits() statements that will allow you to turn call_out() statements and network input back on. Otherwise an error in an upgraded() function can hang your MUD completely, with no way to fix it, not even with the 'safety' binary port that the Kernel Library supplies.

From: John "West" McKenna
Subject: Re: [DGD]compile_object() kfun
Date: Wed, 14 Mar 2001 17:15:30 +0800 (WST)

The auto-upgrader builds a dependancy tree from inherits, includes,
and calls from the object to a depends() function.  The object can
declare its dependance on any files or directories.  Directory
dependence allows the object to be re-initialised when, for example, a
new soul command file is added.

From: dgd at list.imaginary.com (Felix A. Croes)
Date: Mon Jan 12 07:24:01 2004
Subject: [DGD] Re: Recursion in recompile(), is this correct?

Bart van Leeuwen wrote:

> On Sat, 10 Jan 2004, Noah Gibbs wrote:
>
> > --- Erwin Harte wrote:
> > > Which is where the 'block_input' kfun might come in
> > > handy. ;)
> >
> >   Yeah.  I do it during the recompile, I just don't do
> > it 'til all of the upgraded() call_out statements are
> > done.  I need to.
>
> I was thinking a bit about this and am wondering how safe this is.
> Of course if you rely on everything on your mud being upgraded, you will
> need some mechanism for upgrading everything before anything else happens
> on the mud.
>
> Will it work to do this in call_outs and just block input untill you are
> done?
> I can imagine that this will still allow for other call_outs to run in
> between your upgrade call_outs, and as a result, other threads can run
> while you are doing your upgrade still.
>
> My take would be that you really need to do this all at once, and just
> ensure you do not run out of ticks by properly using rlimits
>
> Also, I do not see what you get from splitting it up over multiple
> call_outs, you are going to block the entire mud anyway, and imho it would
> be more efficient to finish the task at hand as quickly as possible.
>
> What I am suggesting is to use unlimited ticks for a 'main loop' that goes
> through all the objects that need to be upgraded, and use a limit only for
> each individual upgrade.

This works fine as long as all of your objects with programs can be held
in memory together.  In some muds, that is not feasible.  In such a case,
you have to break up the upgrading process in order to let DGD swap out
objects between LPC threads.

You do have to block callouts as well as input from users, in order to
prevent interruptions.  These are both dangerous operations, since an
error can effectively halt the mud without a way to restart it.  But it
can be done.

Regards,
Dworkin

From: dgd at list.imaginary.com (Noah Gibbs)
Date: Mon Jan 12 08:42:01 2004
Subject: [DGD] Re: Recursion in recompile(), is this correct?

--- Bart van Leeuwen wrote:
> > You do have to block callouts as well
> > as input from users, in order to
> > prevent interruptions.  These are both
> > dangerous operations, since an
> > error can effectively halt the mud without
> > a way to restart it.  But it
> > can be done.

  Yeah.  It's not actually so bad...  It's just a
matter of making sure you've got strategically-placed
catch() statements so that an error can be recovered
from.  At least, that's been my experience.

> Yep, call_outs, input of any kind... sounds like
> lots of fun to me..

  Yup.  There's an easy way to block call_outs, it
turns out, in the Kernel Library resource manager. 
Ditto for input.  I don't think the input-blocking
thing works for connections, so I actually have a
separate "suspended" message that you get when you try
to connect to the MUD while it's upgrading.  It tells
you to try back in about ten seconds.  It doesn't take
nearly a full ten seconds to rebuild, but I figure a
conservative estimate is better :-)

  But yeah, as Felix notes, make sure you have catch
blocks in the right locations so that an error will
cancel everything (or at least, cancel the operation
that just had the error and not reschedule it) and
restore call_outs and input and new connections.

  I actually do this when doing my %datadump operation
too...  Since I'm saving all the objects in the MUD, I
have to keep people and critters from moving around
and giving me an inconsistent state.  That would be
yet *another* disadvantage of data files compared to
state dumps, alas.  So for periodic backups, I
recommend statedumps :-)

From: dgd at list.imaginary.com (Felix A. Croes)
Date: Mon Jan 12 14:26:01 2004
Subject: [DGD] Re: Recursion in recompile(), is this correct?

Noah Gibbs wrote:

> --- Bart van Leeuwen wrote:
> > Hmm. has anyone ever tried a setup where
> > upgrades of 'children' of a
> > recompiled library are delayed untill the
> > next time the child is
> > referenced?
>
>   I guess you could use the (very new) call_touch for
> this...  Prior to call_touch there was no real way to
> tell when the object was referenced so you couldn't do
> it that way.

Don't forget that a recompiled program is not replaced until the end
of the current LPC thread.  You can use call_touch() in the upgrade
of object dataspaces, though.  For example, to initialize a new
variable that has been added.

Regards,
Dworkin

From: dgd at list.imaginary.com (Felix A. Croes)
Date: Tue Jan 13 16:08:01 2004
Subject: [DGD] 1.2.71

What I forgot to mention: the 1.2.71 patch includes a change to the
driver object in the kernel library.  To upgrade this object to the
new version in a persistent mud, you have to recompile it and then
immediately reboot the mud.

The upgrade to the auto object is safe and can be performed at any
time.

Regards,
Dworkin

From: dgd at list.imaginary.com (Felix A. Croes)
Date: Sat Feb 14 10:46:01 2004
Subject: [DGD] 1.2.77

diff -c dgd/src/Changelog:1.314 dgd/src/Changelog:1.315
*** dgd/src/Changelog:1.314     Thu Feb  5 15:01:00 2004
--- dgd/src/Changelog   Sat Feb 14 17:32:13 2004
***************
*** 1573,1575 ****
--- 1573,1577 ----
   - Make sure that a callout handle is not truncated to 16 bits before an
     attempt is made to remove it.
   - Include system files before DGD files on Windows.
+  - Rather than causing an error, let the callout and precompiled object lists
+    returned by status() be nil if they would not fit in an array.

ftp://ftp.dworkin.nl/pub/dgd/experimental/patches/1.2.76-1.2.77.gz

This patch includes the promised changes to the kernel library.  It
also simplifies upgrading a persistent mud from kernel library version
1.2.21 (included with DGD 1.2.71); rebooting immediately after the
driver object is recompiled is no longer necessary.

Regards,
Dworkin

From: dgd at list.imaginary.com (Felix A. Croes)
Date: Wed Aug  4 17:51:02 2004
Subject: [DGD] Object handling

Steve Wooster wrote:
>      I'm trying to plan writing an object handler, but I've come across an 
> issue (pardon the pun) where I'm not sure what would be better... I'm 
> trying to decide whether or not updating the auto object and doing a full 
> recompile should be unnoticeable, or whether it should affect inheritance 
> "trees" (is hierarchy a better word?).
>
>      Let's say you have ObjectA which inherits ObjectB, and they both 
> inherit Auto. You make a change to ObjectB, so now there's ObjectB_Issue2 
> which inherits Auto, but ObjectA is still inheriting the old version of 
> ObjectB. Now, let's say you make a new version of the auto object and do a 
> full recompile... Should ObjectA still inherit the old issue of ObjectB 
> (but with both now inheriting the new auto object), or should it inherit 
> ObjectB_Issue2? It'd be much simpler to do the latter, but I worry about if 
> that could make updating the auto object less transparent, as it would 
> bring inheritance "trees" up-to-date as a side effect (and possibly an 
> undesirable one). Any opinions? Thanks!

If bringing all objects involved up to date can cause problems, then not
doing so can cause problems as well.  For example, suppose that the
change in ObjectB anticipated a change in the auto object.  In that case,
reverting back to the older ObjectB issue would be an error.

Regards,
Dworkin

From: dgd at list.imaginary.com (Erwin Harte)
Date: Wed Mar 20 23:21:01 2002
Subject: [DGD] Inherits.

On Wed, Mar 20, 2002 at 10:44:45PM -0600, Erwin Harte wrote:
> On Thu, Mar 21, 2002 at 04:19:09AM +0100, Vladimir I. Lazarenko wrote:
> > Hello DGD,
> > 
> >   Hmm. I'm a bit puzzled here. Let's say I'm making changes to a
> >   files, that is inherited to some kind of other file. If i try to
> >   update inherit itself, it says that i can't update inherited file.
> 
> Right.
> 
> >   If i dest the object that inherits that file, update the inherit and
> >   clone object back, my changes do not work.

Forgot to finish a sentence here:

> If you destructed and compiled both objetcts (in the right order) it
> should now be effective for new clones, at least.  However
                                                             if you
  never destructed the inherited object and only destructed and compiled
  the inheriting object, that won't do you any good and actually means
  that any clones already made of the object now use code that you can't
  replace anymore.

Cheers,

Erwin.
-- 
Erwin Harte

(Blocking Input - necessary for upgrades!)

From: dgd at list.imaginary.com (Stephen Schmidt)
Date: Fri Oct 24 09:33:00 2003
Subject: [DGD] dgd crashes on fatal errors

Soja wrote:
> > I have discovered that dgd crashes (dumps core) after reporting a fatal
> > error. For example if you remove the binary_connect() function from a driver
> > object, dgd crashes....

So Don't Do That.  :)

There's no reason to omit this function from the driver. If you don't
want a binary connection object, then

object binary_connect() {
    return 0;
}

ought to work just fine for you. As Dworkin pointed out in his
post, there are good reasons for the driver to abort in this
case and no reason not to insist that certain critical functions
be defined, if only minimally, in the driver object.

Question: Is it possible to turn off listening on a binary
port, either by not specifying a binary port at all in the
config file, or perhaps by specifying -1? I can see where
someone might want to not even be listening for a connection,
rather than listening but refusing to provide an object if
a connection request comes in.

Steve

From: dgd at list.imaginary.com (Erwin Harte)
Date: Fri Oct 24 09:39:00 2003
Subject: [DGD] Re: dgd crashes on fatal errors

On Fri, Oct 24, 2003 at 10:32:11AM -0400, Stephen Schmidt wrote:
> Soja wrote:
> > > I have discovered that dgd crashes (dumps core) after reporting a fatal
> > > error. For example if you remove the binary_connect() function from a driver
> > > object, dgd crashes....
> 
> So Don't Do That.  :)
> 
> There's no reason to omit this function from the driver. If you don't
> want a binary connection object, then
> 
> object binary_connect() {
>     return 0;
> }

This is the same as what the original poster tried with the 'return
nil;', except you're running with a different strict-typing mode.
It'll cause an intentional coredump as s/he showed.

[...]
> Question: Is it possible to turn off listening on a binary
> port, either by not specifying a binary port at all in the
> config file, or perhaps by specifying -1? I can see where
> someone might want to not even be listening for a connection,
> rather than listening but refusing to provide an object if
> a connection request comes in.

You can currently provide an array of port-numbers.  It's on the todo
list to make it possible to put an empty array there and have no
binary (or telnet) ports open at all.

For now, create a dummy object that self-destructs as soon as it has
been created, so you can clone one of those and return it in
binary_connect() instead of nil or 0.

Hope that helps,

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Tue Apr 20 22:29:01 2004
Subject: [DGD] Re: blocking the telnet port

On Wed, Apr 21, 2004 at 12:55:02PM +1000, Mercy wrote:
[...]
> Annyway, I was just wondering if there's a way to block the telnet port, 
> under the Kernel Lib without making direct changes to the kernel itself.
> You might wonder why I'd want to, so briefly, I intend to write a telnet 
> handler to run on the binary port, and rather than just ignoring the 
> existance of the open telnet port, I'd like to close it altogether.

In recent versions of DGD the 'telnet_port' entry in the configuration
file can be an integer or a mapping and as far as I remember you
should be able to make that an empty mapping, causing no telnet ports
to be opened at all.

>From the 1.2.72 Changelog diff:

+  - Config file change.  ({ 6047, 6048 }) for ports changed to a mapping in
+    which the address must also be specified: ([ "*":6047, "localhost":6048 ])

Hope that helps,

Erwin.
-- 
Erwin Harte

From: DGD Mailing List (Robert Forshaw)
Date: Thu Apr  1 17:55:01 2004
Subject: [DGD] players, inheritance, cloning, a better solution?

I have an object called primary_object.c, which contains behaviour code for 
all objects existing in the game world (rooms, items, and entities). This 
object was not intended for inheritance, but rather it would be cloned have 
each clones' behaviour defined by configuration files. I also want actual 
players to be primary objects.

Now, I could put all the code for interactivity into the primary_object, but 
this seems a rather inefficient solution since most objects wouldn't be 
making any use of the interactive code. So I've got a seperate object, 
called player.c. But if I inherit primary_object in order to adopt its 
behaviour, it won't be clonable any longer.

My solution has been to make another file, _primary_object.c for lack of a 
better name, all that does is inherit primary_object.c. So primary_object.c 
becomes a library and _primary_object.c the clonable.

I'm just wondering if anyone can spot a more attractive solution than the 
one I've just described, as it seems rather 'hackish', if you know what I 
mean.

From: DGD Mailing List (Par Winzell)
Date: Thu Apr  1 22:29:01 2004
Subject: [DGD] players, inheritance, cloning, a better solution?

Robert,

> My solution has been to make another file, _primary_object.c for lack of 
> a better name, all that does is inherit primary_object.c. So 
> primary_object.c becomes a library and _primary_object.c the clonable.
> 
> I'm just wondering if anyone can spot a more attractive solution than 
> the one I've just described, as it seems rather 'hackish', if you know 
> what I mean.

Your choice of names bewilders me slightly, but the idea itself is not 
hackish. Skotos, for example, has

   /base/lib/thing.c

which is the base of all physical objects, and contains all the code, 
and we have

   /base/obj/thing.c

which consists of little but an inherit-statement.

Zell