[DGD] Saving to disk

Noah Gibbs noah_gibbs at yahoo.com
Tue Apr 16 17:51:40 CEST 2013


The all-in-one /obj/thing isn't primarily to allow mutation of one object type into another.  It's primarily to avoid the repetitive and error-prone coding of many nearly-the-same sub-paths in your code for various types of objects that don't quite share properties, but almost do.  By saying "there is one global object type with properties and I will just code the properties", you avoid that.  Sufficiently careful coding of your various types, for instance by flagging them with properties, will also allow you to avoid that...  And then you basically have one object type, but with properties.

The various player-body stuff isn't actually about save files versus persistent.  You can implement any of the player-body features you mention with either design.

Your various arguments about swap files also apply to your save files.  That is, none of your nominal reasons for choosing one over the other actually favor one over the other significantly.  There's nothing wrong with save files for objects, of course.  The basic tradeoffs are:

* swapfiles are faster because the save code is done custom in C, and you don't need to separately marshal the objects.
* swapfiles, managed well, are already 50%+ pre-saved at any given time because DGD swaps out idle objects.
* swapfiles can preserved leaked objects, while good save files never should.
* swapfiles are less human-friendly to edit.
* swapfiles require upgrading objects in place rather than allowing save/restore-style upgrades.
* swapfiles always automatically fully preserve object relationships, while save files can have save/restore errors.
* swapfiles require no marshalling code, while save files require writing it.

That's not a slam-dunk either way.  A hybrid design gets a hybrid of these tradeoffs -- you have to write some save/restore code and it can have some (fewer) errors.  Savefile objects are fairly unlikely to leak, while non-savefile objects can.  Savefile objects are editable and upgradable, while your non-savefile objects aren't.  And so on.

You're right about areas -- being able to write, import and export areas requires some kind of disk format.


________________________________
 From: Blain <blain20 at gmail.com>
To: All about DGD and Hydra <dgd at dworkin.nl> 
Sent: Tuesday, April 16, 2013 8:01 AM
Subject: [DGD] Saving to disk
 

I'd like to put out there one of my mudlib design ideas regarding saving to
disk vs. letting the swapfile handle it alone.  I've read numerous
discussions in the archives, but I think that a lot of the points made for
letting the swapfile handle saving objects only weren't quite accurate in
my humble opinion.  I also discuss other persistence-related topics after.

For starters, users should not be ever-present in the game.  In a standard
LPMUD/MudOS game, a user is represented by a .o file.  This makes sense.
This also means that the user connection can and should be destructed on
logout.  The body, if separate can also be saved to a .o file and
destructed.  Other players and NPC's don't usually need to interact with an
offline player in most MUD designs.  If a designer really wanted to, they
could redirect private messages (usually called 'tells') to an internal
mail system or a cache waiting for the player to return.  Also, I don't
think an environment object should be trying to load a player's body when
that player isn't on.  Of course, there are ways which this can handled
appropriately in most any persistence scheme.

Persistence seems to be a great way to keep the Game going without skipping
a beat, but players can choose not to be logged in, and so they should not
quite be included in the persistence of the Game.  Someone mentioned
activating an AI program to drive the player's body around while the user
isn't logged in, but this can really only cause confusion.  I do have plans
to move a player's body to a nearby inn (or their personal home if it's
nearby).  This is about as far as manipulating a player's body I am willing
to go, lest I piss off the masses.

So, to implement this in a globally standard way (that is, other objects
may wish to be excluded from being handled by the environment) I devised
the concept of an object flagging itself as a 'save point'.  While I intend
to use persistence in my lib design, I am also allowing for saving
individual state to disk for certain objects using this flag.  When an
object is asked to save(), it will usually return a mapping of its internal
variable data.  If an object is flagged as a save point, it will instead
save itself to its own personal save file and return nil to the calling
environment object.  The object will also recursively save its inventory
and compile the data mappings received into a string variable, and it will
reconstruct its inventory using that string upon reconstruction.  The
environment zones are also save points and will save any inventory objects
to disk, except for save-point objects, such as players.  The only example
of a non-player save-point I have so far is a travelling NPC who will
control where it is at a given point in time.  It will do this using 'load
point' objects which it drops in the various environment zones that its
travels take it through.  This way, when a zone is loaded from disk, the
'load point' is loaded and it notifies the NPC in case the NPC should be in
that particular zone at that moment in time.  This is for NPC's who operate
on a cicular schedule, not those who wander aimlessly.

The main reason I can't see going swapfile-only as a good thing is because
of a couple main issues:
- The loss of a swapfile due to corruption means the loss of everything the
builders ever worked on, or
- Booting from a backup will cause the loss of any recent changes, which
could be catastrophic if any real complicated work has been done since the
backup was made.
- Being able to edit a save file can sometimes be the last resort if
something just won't load with it.  Without save files, one would have to
hand-edit the swapfile.

I think that a hybrid mudlib is really the better way to go.  Persistence
for normal operation, but saving to disk for dealing with problems.
Persistence for most NPC's and world events, but saving to disk for
players, who control their own existence in the game world.

Saving players to disk (including their inventories) means that they need
to be upgraded on login.  I intend to code an upgrade service to do exactly
this.  All player save files will be loadable by data LWO's.  The version
of the save file can be detected and the appropriate LWO can then load and
update the data to migrate it from one version to the next.  After all
upgrades have been performed, the body object can then load the
newly-updated save file.  Also, while upgrading save files from version to
version, certain processes can inform the player of any changes they need
to know about, such as:

As of <patch date>, the Sword of DOOM is no longer available.

Then it removes the sword from the player's inventory.

I think that this system design is probably a lot similar to how a lot of
MMO's may run, although I know WoW just has item number pointers in your
inventory list, and items are updated separately, though WoW does keep
arbitrary data for your version of an item, such as enchantments and stat
modifications, etc.  I would likely do the same.

Another problem I have with some of the ideas brought up in these
persistence discussions is the all-in-one /obj/thing.  I can't see a reason
why any object should ever be mutated into another object as far as the
mudlib is concerned.  I agree that most objects should be wieldable, even
if they suck as a weapon, but this should be done from the player body just
using the object's physics as an indicator of being wieldable and its
effectiveness as a weapon.  If an object truly is mutated into another by a
magic spell, one can merely replace the object with another.  If reversal
is wanted, repeat the process, or store the original in some kind of magic
effect object held inside the new object.  Then swap and destruct the
mutated one.  I intend to have inheritance chains because some behavior
code will need to accompany different types of objects.  I tried mapping
out some sort of module tracking array, but it seemed inefficient.
Inheritance allows for easier masking of a parent function, but if modules
are held as siblings, it's difficult to decide which module's decision wins
out over that of the other modules.  I'm sure it can be done just fine
either way, but modular objects felt kludgy, and I'm trying to use a design
which uses minimal objects.  A shared library is often better than a cloned
module.  I will use modules to define the behavior of a given sword, but
all swords will load from a standard sword inheritable which defines
default behavior that non-swords need not worry with.  Of course, the sword
lib will inherit the weapon lib, so there probably doesn't need to be much
different in the sword lib itself.

All in all, my design requires that a full, true shutdown save all
save-point objects to disk with save strings of their non-save-point
inventory objects.  When there are no problems to deal with, the MUD can
use the swap to do mini-boots once a day or so for memory reasons (if I
understand all this correctly).  Clearing the memory of all game objects
also helps with finding object leaks, I'd think.  I also like the idea of
the MUD booting only the Kernel and System and not loading the Game until a
player logs in.  Using only the swap for a continued state prevents that.
Choosing to do a cold boot with no state dump could also achieve this, but
then you'd be right back where you started if you shut down and booted from
a state dump.  I'll have to play with this to be sure I'm thinking
correctly.

Another aspect of my design which requires save files is building areas and
installing them.  If a user builds an area, they will need to save
everything to disk so that the save files can be copied over to the Game
directory structure and be loaded from there.  I can't see how this can be
done if the objects only ever exist in memory and swap, aside from loading
objects and pulling the data over for each one, which could introduce
problems of its own.  I think I'll have to do this before I can truly see
what problems can arise, though.

Using LPC startup files can also be useful in some cases where an object
needs very exact behavior.  The builder code I'm designing will also allow
for this.  So, in summary for building, builders will deal with .c and .o
files for some of their area objects, but usually only .o files.  Quest
handler services and the like will likely need to be coded in LPC also.
I'll see about making some of this standardized and thus be loadable from
the main lib files, though.

Thoughts?

--Blain
____________________________________________
https://mail.dworkin.nl/mailman/listinfo/dgd


More information about the DGD mailing list