Data Persistence pitfalls

Today I ran in to a little bit of a snag while working on a generic approach to data persistance. I wanted to have the ability to do the following:

this.Worlds = this.StorageSource.Load< IWorld>().ToList();

The result of the above call would go to the data source, find all of the objects that implement IWorld and restore them. The problem however is that there might be multiple implementations of IWorld. The engine ships with DefaultWorld and someone might come along and write a SullyWorld. Both of which can be used simultaneously in the game. With that however came issues with data persistance. How do you persist this to the data store and maintain data integrity? For instance, the StorageSource must identify how it can save an object to its data store and know what that object is during restore. This would be easy if the game was going to built by developers using the source. You would just manually specify

this.StorageSource.Save< DefaultWorld>(this.Worlds.First());

However the goal is to have the entire game created within a editor. On top of that, I want the ability to download custom IWorlds from other users and plug them straight in to the engine via the editor and have the persistance code continue to work. This puts a bit of a dampner on Generics because you can't do the following with Generics:

this.StorageSource.Save< typeof(this.Worlds.First())>(this.Worlds.First());

You must strongly type the generic parameter. This presents a couple of tricky issues. One, the persistant store knows ahead of time that the IWorld object it is saving is DefaultWorld that implements IWorld. However, once saved and I request that the IPersistanceStorage object re-fetch the IWorld objects from the data store, it doesn't know what objects belong to what Types. It only knows that they all are IWorld. How do we fix this? Once way to address that, is to store some meta-data that links everything together. I'm a bit apprehensive of that because it makes the persistance store to brittle in my opinion.

Since any one can create a IPersistedStorage based object, it really becomes even more difficult to track by the engine. So I think the best choice is to have each individual implementation track this by themselves. Forcing all IPersistedStorage implementations to handle type inferrence itself won't break the fact that you can hot-swap data stores at runtime. As long as the data is loaded in memory (which it will be), you can re-persist to any storage provider you want.

By default, I want to ship with both XML Serialization support and SQLite support. I can solve this problem for XML serialization because I can load the XML before deserializing, determine the Type specified in it, instance that Type and then perform the deserialization. For the database, I will store all of the Types in a table that matches their interface implementation. Each table will have to have a column that specifies what the original Type actually was, so that I can re-instance that. The downside is I will not be able to use any existing ORMs and will have to create my own wrapper to read in the data and instance objects based off of the column data. Not optimal, so I will continue to explore it.

Mud Designer's Revised State Management

This morning I wired up a revised state management system in the Mud Designer. The system is a bit more flexible, as it make use of the input agnostic player objects. In the past, the States required the Player object as a parameter within its Render method so that it could fetch input directly from the player object. This brought with it a tight coupling that I have been trying to get rid of. It also allows for the new state system to fit in to the Single Responsibility Principle, which was previously being violated. What essentially would happen, is that the states would take control of the players input and not return it to the rest of the engine until the user was completed with the state. The state system had become responsible for fetching input, parsing and rendering.

As an example, the following state in the original engine captured control within itself and would not let it go until the user had completed the login process.

private ServerDirector director;
private IPlayer connectedPlayer;

//Used to manage the state of the Login in a more readable manor
private enum CurrentState
{
    EnteringName,
    EnteringPassword,
    CharacterSelection,
}
private CurrentState currentState;

public ClientLoginState(ServerDirector serverDirector)
{
    director = serverDirector;
    currentState = CurrentState.EnteringName;
}

public void Render(IPlayer player)
{
    //Store a reference for the GetCommand method to use
    connectedPlayer = player;

    //Check which state we are in
    switch(currentState)
    {
            //User is entering his/her character name
        case CurrentState.EnteringName:
            player.SendMessage("What is your username, adventurer? ", false);
            break;
            //User is entering his/her password
        case CurrentState.EnteringPassword:
            player.SendMessage("Please enter your password " + player.Name + ": ", false);
            break;
    }
}

public ICommand GetCommand()
{
    //Check which state we are in
    switch (currentState)
    {
            //User is entering his/her character name
        case CurrentState.EnteringName:
            {
                GetUsername();
                break;
            }
            //User is entering his/her password
        case CurrentState.EnteringPassword:
            {
                IState startState = connectedPlayer.CurrentState;

                    if(GetUserPassword())
                {
                    //Was originally LookCommand() but that was overriding the State specified in GetUserPassword.
                        return new NoOpCommand(); 
                }
                break;
            }
    }

    return new NoOpCommand();
}

    private void GetUsername()
{
    //Recieve the user input
    var input = connectedPlayer.ReceiveInput();

    //First input received on connection, so clean it.
    //Some telnet clients send header information with it.
    input = System.Text.RegularExpressions.Regex.Match(input, @"\w+").ToString();

    //Make sure the text entered is valid and not null, blank etc.
    if (!ValidateInput(input))
    {
        connectedPlayer.SendMessage("You have entered an invalid name, please try again!");
        return;
    }

    //Check if the character exists. If so, change the state of Login so s/he can enter the password
    if (UserExists(input))
    {
        connectedPlayer.Name = input;
        currentState = CurrentState.EnteringPassword;
    }
    else //No character by the supplied name found, so lets create a new one!
    {
        //New user messages are sent from within the NewCharacter state.
        connectedPlayer.Name = input;
        connectedPlayer.SwitchState(new CreationManager(director, CreationManager.CreationState.CharacterCreation));
    }
}

private bool GetUserPassword()
{
    //Recieve the user input
    var input = connectedPlayer.ReceiveInput();

    //Make sure the text entered is valid and not null, blank etc.
    if (!ValidateInput(input))
    {
        connectedPlayer.SendMessage("Your password is invalid!");
        return false;
    }

    var file = new FileIO();

    IPlayer loadedplayer = (IPlayer)file.Load(
        Path.Combine(
            EngineSettings.Default.PlayerSavePath,
            string.Format("{0}.char", connectedPlayer.Username)), 
            connectedPlayer.GetType());

    if (loadedplayer != null && loadedplayer.CheckPassword(input))
    {
        /*Make sure we are disconnecting the user if they are connected already.
        foreach (var connectedUser in director.ConnectedPlayers.Keys)
        {
            if (connectedUser.Name == loadedplayer.Name && connectedUser != loadedplayer)
                connectedUser.Disconnect();
        }
        */

        connectedPlayer.SendMessage("Success!!");

        //Can use inherited built-in CopyState method instead
        //connectedPlayer.LoadPlayer(loadedplayer);

        //Use IGameObject.CopyState to use a uniform method across the engine
        //A little slower than the LoadPlayer method, but it can be revised to be quicker.
        //Notes on revising the method are under GameObject.cs
        IGameObject tmp = (IGameObject)loadedplayer;
        connectedPlayer.CopyState(ref tmp); //Copies loadedPlayer state to connectedPlayer.

        //Make sure the player is properly added to the world.
        IWorld world = connectedPlayer.Director.Server.Game.World;
        IRealm realm = world.GetRealm(connectedPlayer.Location.Zone.Realm.Name);

        if (realm == null)
            return false;

        IZone zone = realm.GetZone(connectedPlayer.Location.Zone.Name);
        if (zone == null)
            return false;

        IRoom room = zone.GetRoom(connectedPlayer.Location.Name);
        if (room == null)
            return false;

        connectedPlayer.Move(room);

        Log.Info(string.Format("{0} has just logged in.", connectedPlayer.Name));
        connectedPlayer.SwitchState(new LoginCompleted());

        return true;

    }
    else
    {
        Log.Info(string.Format("{0} has failed logged in at IP Address: {1}.", connectedPlayer.Name,
                                connectedPlayer.Connection.RemoteEndPoint));

        return false; 
    }
}

As you can see, in the GetUserName and GetUserPassword methods, the methods invoke connectedPlayer.ReceiveInput() which would not proceed any further until after the user has provided the required text.

Now, with the new ReceivedMessage event that the player object has, we can register to receive a notification when the event is fired, without having to capture control and hold it hostage. This allows the state objects to do what they are designed to do, render state to the screen and then waits for additional input when ever it arrives before proceeding or altering its state.

private IPlayer connectedPlayer;

private enum CurrentState
{
    FetchUserName,
    FetchPassword,
    InvalidUser,
}

private CurrentState currentState;

public void Render(IMob mob)
{
    if (!(mob is IPlayer))
    {
        throw new NullReferenceException("ConnectState can only be used with a player object implementing IPlayer");
    }

    //Store a reference for the GetCommand() method to use.
    this.connectedPlayer = mob as IPlayer;
    var server = mob.Game as IServer;
    this.connectedPlayer.ReceivedMessage += connectedPlayer_ReceivedMessage;

    if (server == null)
    {
        throw new NullReferenceException("LoginState can only be set to a player object that is part of a server.");
    }

    this.currentState = CurrentState.FetchUserName;

    switch(this.currentState)
    { 
        case CurrentState.FetchUserName:
            this.connectedPlayer.Send(new InputMessage("Please enter your user name"));
            break;
        case CurrentState.FetchPassword:
            this.connectedPlayer.Send(new InputMessage("Please enter your password"));
            break;
        case CurrentState.InvalidUser:
            this.connectedPlayer.Send(new InformationalMessage("Invalid username/password specified."));
            this.currentState = CurrentState.FetchUserName;
            this.connectedPlayer.Send(new InputMessage("Please enter your user name"));
            break;
    }
}

void connectedPlayer_ReceivedMessage(object sender, IMessage e)
{
    // We have the text from the user now, create a command from it. 
    // ATM this does not do anything with the command.
    ICommand command = this.GetCommand(e);

    // Be good memory citizens and clean ourself up after receiving a message.
    // Not doing this results in duplicate events being registered and memory leaks.
    this.connectedPlayer.ReceivedMessage -= connectedPlayer_ReceivedMessage;
}

public Commands.ICommand GetCommand(IMessage command)
{
    if (this.currentState == CurrentState.FetchUserName)
    {
        this.connectedPlayer.Name = command.Message;
        this.currentState = CurrentState.FetchPassword;
    }
    else if (this.currentState == CurrentState.FetchPassword)
    {
        // find user
    }

    return new NoOpCommand();
}

Since the state is associated with the player object (via a StateManager class I will discuss in another post) we can register to receive an event notice when the user inputs text and then continue handling our state with the new data. This also allows other objects that might be registered to act on the users input. The benefit here is that a user can hold a conversation with another user, or be in the middle of interacting with a NPC or deep in a training course and still be able to execute other commands without messing up their current state.

The IGame objects are responsible for specifying what the initial state of the game should be. Since the MultiplayerGame object instances and invokes a Connect method within the ServerPlayer object (that wraps an IPlayer), I specify that the initial state should be a ConnectState within the Connect method.

public virtual void Connect(System.Net.Sockets.Socket socket, IPlayer player)
{
    this.Connection = socket;
    this.Player = player;

    // Set the users initial state.
    this.Player.StateManager.SwitchState(new ConnectState());

    this.OnConnect();
}

As soon as the SwitchState method is invoked on the StateManager, the players state is changed and immediately rendered to the user.

Vastly improved Online & Offline support in Mud Designer

The Mud Designer engine received a much better system for supporting multi-player and single-player games. The previous version of the engine did not ship with single-player support out of the box, and implementing single-player support required you to re-write the BasePlayer Type, the Game Type and a large percentage of the IState Types that shipped with the engine because they all relied on the ServerDirector.

Things are much more flexible this time around and the engine will ship with both single-player and multi-player support out of the box (when I finally get it shipped). With the new version, any Type that implements IPlayer can be used both online and offline. The player sends and receives it's messages in the same manor, regardless if it is online or not. The engine contains a DefaultGame object that is used for offline games, and a MultiplayerGame object that is used for online MMO styled games. The MultiplayerGame object creates an IServerPlayer object that can hold an instance of an existing IPlayer Type. The IPlayer Type has an event for when a message is both sent and received from the player. The IGame object registers to receive those events and can handle them accordingly.

For instance, DefaultGame can receive the message that the player is supposed to receive (like from a look command) and it will display them to the user via Console.WriteLine. The MultiplayerGame can receive the same message, but rather than writing it to the Console, it will write it the the Socket connection, sending it to the players telnet client. This all takes place within a new IGame.BroadcastToPlayer method. If a developer wishes to create a special UI that will display the received messages (such as a local test client UI), then the developer only needs to create a new IGame object that inherits from MultiplayerGame or DefaultGame, and override the BroadcastToPlayer method to route the content to where the developer wants to display it. This essentially allows the engine to work independently of the network status, with the only thing relying on it being an IGame implemented object.

The following shows an example MUD game server wrote in C# using the Mud Designer. The MultiplayerGame object implements both IServer and IGame. The IServer interface exposes the Start method that is used to start the server.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Server app starting...");

        // Instance the server.
        var game = new EngineFactory< MultiplayerGame>().GetObject();

        game.Initialize< DefaultPlayer>(null);
        game.Start< ServerPlayer, DefaultPlayer>();

        Console.WriteLine("Server running...");
        while (game.IsRunning)
        {
        }
    }
}

If you want to use the same DefaultPlayer object in a single player game, you can. You just fetch a DefaultGame Type from the EngineFactory and invoke it's Initialize method. Since DefaultGame doesn't implement IServer, there is no Start() method used to start the server.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("App starting...");

        // Instance the game.
        var game = new EngineFactory< DefaultGame>().GetObject();

        game.Initialize< DefaultPlayer>(null);

        Console.WriteLine("Game running...");
        while (game.IsRunning)
        {
        }
    }
}

There are a lot of other improvements made that I will cover in a series of blog posts associate with the new IGame, IServer and IServerPlayer implementations.