Storing Data from a Mud

Posted by Greven on Wed 01 Feb 2006 04:17 PM — 3 posts, 16,831 views.

Canada #0
I'm looking at storing data for a custom codebase. The codebase will be open source, and so portability is a big factor. With this in mind, I've started to examine a couple different ways to store the info. If anyone has thoughts, comments, suggestions, and personal experiences, it would be muchly appreciated.


SQL server
  *  Fast, easy way to access info
  *  No versions needed as tables can change easily
  *  Requires a server of a specific kind or heavy configurations

XML files
  *  Easily laid out and interpret
  *  Easy to change a version
  *  Requires more work and code to make an easy class interface

Smaug style files
  *  Very easy to write
  *  Pain in the ass to read
  *  Versioning is marganally useful

Any other options would be great too.
USA #1
SMAUG-style files are evil, error-prone and all sorts of bad things. I would stay away if at all possible. Still, if you *are* going to do plain-text, there are much smarter ways of doing it than SMAUG's approach.

I tried using a database (BerkeleyDBXML) and it wasn't the best experience. One major problem was that it was very sensitive to being opened and closed correctly, so if the MUD crashed, it got extremely upset and had to be massaged back into working again.

XML is good, although a bit verbose at times. Nick uses it for his TinyMudServer, I believe. Still, it is not nearly as good as the next option.



Lua is what we're starting to use on BabbleMUD. I also just implemented a questing system on Darkstone that uses a Lua backend.

There are several advantages to Lua:
  • You don't have to write any code to do the actual loading of the data. You just run the datafile through the interpreter, and you've got it all in memory.
  • You have to do very little work saving the data. Nick has for instance written a serialization routine that is on these forums somewhere. There are plenty of more-or-less elaborate serialization libraries out there, e.g. Pluto, a heavyweight champion of sorts.
  • The data files are relatively easy to read by a human. Not harder than XML, in any case.
  • Unlike XML, your data is typed, which helps to avoid continuous conversions back and forth. (You do however have to convert back and forth between Lua's types and yours. Still, it's not as bad as having to convert strings to/from numbers all the time.
  • Lua is F-A-S-T.
  • Using Lua for data opens the possibility of writing Lua scripts to act directly upon that data.


Once you've read the Lua data table into memory, you can do one of two things:
  • Convert the entire structure to a C/C++ structure. This is practical for very small structures (it's what I'm doing for the quest system), but gets a little unwieldy for larger tables. The advantage is that your C code simply talks C to access its data, once it has been loaded.
  • Access the Lua table, with Lua API calls. You need to do a little more effort to wrap around the Lua table. But you won't take a significant speed hit, again, because Lua is very fast.



I will be writing a forum post sometime soon examining the case study of the questing system, with code and all that. I have not finished the entire system yet, so it should be within a few days to a week or two, homework allowing.
Amended on Wed 01 Feb 2006 06:28 PM by David Haley
Australia Forum Administrator #2
I'm inclined to agree with Ksilyan on this one. My Lua serialization routine actually ships with MUSHclient, however apart from converting "Note" to "print" or "io.write" where appropriate it would work in any application.

As he says, simply do a dofile to read it back in. For example, in a MUD server we are working on together right now, this is how we save the player data:


-- open file to serialize into
  local f = assert (io.open (dir .. "/" .. name, "w"))

-- save all player data
  assert (f:write (serialize (name, char)))

-- done with file
  f:close ()


In this case the "char" field is a table of all relevant data for that player. Can't get much simpler than that!

Loading a character (when they connect) is equally simple:


  -- load the file, get a function to execute  
  local f = assert (loadfile (dir .. "/" .. name))

  -- want to load this character into the chars table
  setfenv (f, chars)

  -- load it
  f ()


The "setfenv" line is designed to make the "chars" table (a table of all connected characters) the global space for the function call that actually does the loading (the "f ()" line), so that the character data ends up in that table and not into global address space.

The nice thing about this design is that you can totally extend your player definitions without changing any code in the load/save routines, as you can see they don't refer to any character fields at all.

Also, using Lua, tables can contain sub-tables, so you can express things like player inventory, or "list of all known spells" simply.