January 27, 2010

Persistence

I've had a few questions about how I plan to handle persistence, as in saving the states of game objects in case the server is restarted. The quick answer is I'm not -- with the exception of player character data. Bogboa is using two forms of data storage; YAML files and an SQLite database. I'll discuss each.


YAML Files

Currently, YAML is the only third-party library used in Bogboa. I didn't want to use any really, but YAML is such a human friendly format that I went with it.

On startup, the world is created from one or more directory trees filled with YAML files that define individual rooms, items, races, etc. This is a one-way conversion where each YAML file is parsed into a Python dictionary that is used to set attributes of game classes. One of my long-term goals is to support reading these YAML trees from zip files so that designers could release adventure modules that contain complete dungeons with new monsters and items that load seamlessly into existing game worlds. This is one of the reasons I'm using UUID's for keys, since they don't require multiple developers to coordinate or negotiate around potential collisions like vnums do.

So restarting the server cleans every floor of dropped loot and every dead NPC is alive again. This appeals to me because it's simple and troubleshooting scripts is easier from known states.


SQLite Database

Starting in Python 2.5, SQLite3 is part of the standard library (and a kick-ass little SQL database) which makes using it a non-brainer.

My early tables were pretty vanilla with columns; (name, race, level, guild, stat1, stat2, etc) and I was always changing them. Also, I was using a lot of sub-tables to hold information like skills, inventory, and character flags. These sub-tables all had nearly identical structure; (character_uuid, skill_name) and (character_uuid, inventory_slot).

At the same time, I was making structural changes to how I split player from their avatars (which is why the codebase is currently b0rked). I knew that the closer I handled avatars like NPCs the easier things would be down the road. Moving all the various traits into dictionaries was helpful and this transition to key-value thinking migrated to how I stored character data as well.

What I wanted was a way to load whole dictionaries at a time but update individual key-value pairs on the fly. When you log in, your character's profile dictionary is retrieved containing (name, race, guild, level, ...) and when you gain a level, your characters (profile + level) record is incremented by one and if the server crashes a moment later you, hopefully, didn't lose it.

One of the nuisances of SQL is you often have to do a SELECT before you can save data because sometimes you cannot be sure if you need to use the INSERT or the UPDATE command. SQLite supports a non-standard but convenient variation of this called INSERT OR UPDATE. The catch is the key has to be unique.

What I did was make the key = (Character UUID + Some Catergory Name + Key Name);

CREATE TABLE IF NOT EXISTS key_value
(
uuid TEXT,
category TEXT,
key TEXT,
value TEXT,
PRIMARY KEY(uuid, category, key)
);


In the above example, the category name would be 'profile'.

Reading all the rows of a category would look like;

SELECT key, value
FROM key_value
WHERE uuid = ? AND category = ?;



And saving an individual key-value would look like;

INSERT OR REPLACE INTO key_value (uuid, category, key, value)
VALUES (?, ?, ?, ?);

No comments: