Mud Designer Alpha 3 Progress

I have spent the last couple of weeks working on the Mud Designer, revising the code-base some. We had done a pretty good job of writing the engine source for Alpha 2, so in order to do some of my revisions to the source I just had to replace a handful of files rather than a full re-write like we had to do in Alpha 2. While I did re-write a couple of files, the majority of the revisions were made to existing code, allowing me to re-use the bulk of what we wrote.

There are several things within the engine that I wanted to improve on and make changes to. The changes will break any projects using the Alpha 2 engine, but I have always been upfront in regards to the lack of forwards compatibility between versions while in the Alpha stage. Let's take a look at whats changing shall we?

Good bye Codeplex / Hello GitHub

The source control is moving from Codeplex to GitHub. The decision was made due to the engine targetting multiple platforms. It made more sense to host the entire project on GitHub since it will run on OS X, Windows and hopefully mobile devices. Codeplex is typically Windows only software, so the repository location needed to be changed.

Async & Task Parallelism

The engine needed to be revised to make use of the whole new Task Parallelism Library (TPL) Microsoft made available in .NET 4.5. We could not make much use of it originally because of our need to support Windows XP. Unfortunetly when Alpha 3 is released, Windows XP support will be dropped. As of now, Windows 7 holds the majority of the marketshare, and that is what I am going to target.

In order to implement TPL and async stuff, the networking code needed to be revised. Essentially, the server has been re-wrote from the ground up, using TPL instead of System Threads. There probably won't be a big performance increase, but the rest of the engine moving forward will be using TPL so I wanted to ensure that the Server was using the same framework feature set.

R.I.P. IDirector

In Alpha 2, the IDirector interface was used to help abstract away some of the functionality of various things from objects. For instance, the Server had it's player data handling abstracted out and placed into a ServerDirector. The director handled the player connection from the time of connection to the time of disconnect. At the time it made sense to do it this way, but it proved to be a little more difficult to maintain.

In Alpha 3, the player management will be handled by the Server and the Player jointly. The Player Type will implement a new IConnectionState interface that will provide a contract to Player objects for disconnecting and connecting. The Player objects will be responsible for cleaning up their own individual socket connections. Once they are completed, the server will handle the player drop and handle server level clean up. This removes the need for a 3rd object sitting in the middle. Since the Player object has always had a Connection property with a typeof(Socket), it makes sense to have the Player object manage its own disconnect.

This also helps simplify the event wiring that I could not get implemented into Alpha 2. It really did not make sense to have a object fire an event, that traveled to another object. Take the following code for example.

public MyPlayerObject()
{
    this.DisconnectEvent += Player_DisconnectEvent;
}

public void Player_DisconnectEvent()
{
    // My Code here...
    this.Director.Disconnect(this);
}

If a user is making a custom Player object that inherits from BasePlayer and they want to run specific code during a disconnect, they still have to make sure and invoke the server director's Disconnect() method. I wanted to revise this some so that in the future all that they have to write is their specific code and they don't have to pass the Player object off to the director. This will help prevent some issues that can come from this. For instance, what if the Player object cleaned up something that the Server Director still needed to use? Or what if the Server Director cleaned up something that the developer's Player object needed to use? This will prevent that issue form occuring. Another thing with events in C# is that they hardly ever call base and to require a event to always invoke a method in another object that is not base is never really done. Events should technically be added using the += operator, allowing the base event to fire first and the developer event to fire second. While I could have invoked the Server Director method in the base event, I would still run into the issue of the child object trying to access things within the Player object that the Server Director had cleaned up and disconnected.

Essentially in Alpha 2 the Server object worked only as a transfer of authority. The player would connect to the server and the server would just pass the Player object off to the ServerDirector object to manage. In Alpha 3, the Server will maintain control of the Player objects and allow the players to manage themselves for the most part.

States & Commands

I liked the set up put in place for player state management in Alpha 2 but revisions were still needed. In Alpha 2, the flow of control went something like this:

  1. Director renders current state to terminal.
  2. Director Requests the users next command from the current state.
  3. Current state asks the Player object to receive input from the client connection.
  4. Current state attempts to find a Command object that can handle the players input.
  5. Current state instances / invokes the Command object.
  6. Command object performs its action.
  7. Command object changes the Player objects state to a new State.
  8. Loop back at 1.

That is a lot of steps and makes it a real pain to debug and add new functionality. I want to revise this workflow a little bit.

States will no longer be responsible for fetching the next command. The network data is already received within the Player object, so it makes more sense to have the Player object determine the command as well. The current State object uses something along the following lines:

public ICommand GetCommand()
{
    var input = this.Player.ReceiveInput();

    // Determine what command the input correlates to.
}

At the moment, the Player object receives the network input but does nothing with it. Other objects around it take the data and manipulate it. It should be the Player object's responsibility to manage it's own data. When a user enters data, the Player object should parse it and determine what needs to be done. Once it determines what must be done, it can pass the action off to something else at that point. Which is where the Command object will take over.

States will continue to be used, but primarily just to render content to the clients terminal. A Player object will be responsible for switching it's state, but any object can tell the Player object to change it's State from what ever it currently is, to another. Which is essentially how it is set up mostly at the moment.

Data Persistance / Storage

Another major change will be on how the engine stores the current state of the game for saving. Currently, the engine stores everything by serializing the objects out to JSon text files. All of the objects are saved using the FileIO object, which implements the ISavable and ILoadable interfaces. The issue with Alpha 2 was that we never abstracted the save calls. So if a developer wanted to use a MySQL database for storing their data instead of files on the local disk, they would have to trudge through all of the scripts and source files and replace the FileIO calls.

This time around, things are going to be a bit different. I have added a DataContextFactory object that is responsible for fetching all of the Data Context's that can be used for storage. A developer can implement a new storage facility by implementing IDataContext and it will become exposed to the engine automatically. Through out the engine and scripts, the save methods will invoke IDataContext.Save. This allows developers to swap out the actual storage type, such as XML, database or a server someplace, without having to change any of the engine or existing script source code.

In order to achieve this, a lot of the engine save and load code had to be revised. It will work out better in the end, with my ultimate goal being to ship both the original JSon flat-file serialization mode along with a SQLite storage mode for Alpha 3 to help demonstrate how to build custom storage options for your games.

Editor

There are a lot of revisions going on with the engine; due to the changes, the editor has essentially become broken. I was never happy with the editor in the first place and always wanted to do something more dynamic, something that provides better support for custom developer scripted objects. So the Editor is the one piece that will be trashed and re-wrote. I haven't decided how I am going to re-write it yet, but I do know that I am not going to use Windows Forms. I am currently debating between either a WPF app or a web-based app that runs on a local server.

Another thing that I have yet to decide on, is if the Alpha 3 will be held up by the editor re-write or if I will ship Alpha 3 with no editor. Alpha 2 shipped with an editor, but it was still fairly gimped. The engine supported 10x what the editor was actually able to do. When Alpha 2 shipped, there was support for Mobs, items, stats, races, classes and mob appearance customizations. None of which was exposed to the editor due to time constraints. Looking back on it, it kind of sucked to ship something that did not make use of 1/2 of the tech that you had built.

Conclusion

Alpha 3 will be a large improvement over Alpha 2, and will allow the engine development to continue moving forward in a solid direction. Alpha 2 was a huge improvement over the first alpha, and the 3rd version takes Alpha 2 and improves on it even more. I am really looking forward to the next release and getting the engine prepared for the first official Beta. At which point, the engine will be considered feature complete and will just require tweaking, feature improvements and scripted content (for developer references) to be created.

(EOF)