Networking!

After weeks of work, I finally got Asteroid Outpost to work over the network! There are a few bugs to work out, but Mike and I were able to mine the galaxy together, WOO! I was surprised how easy networking was with the right method. I have tried and failed a few times to get networking working before, so I was expecting many issues. This method was quite effortless, I’m using a combination of Reflection and Events, and I would highly recommend this approach to anyone who is designing a networking application and is able to use these tools. Now for an in-depth view at the implementation:

First off, a friend of mine blew my mind with how he handled inter-entity communication. Now that I’ve implemented it, it’s completely obvious: use Events to communicate status changes. Status changes can be things like: moving, health changing, power level changed, dying, etc. Each of these will have an event that fires and tells anyone who’s listening. An example would be in my AI: my AI locks on to a target entity and tries to kill it, duh. If you don’t use events, in every update you’d have to check that your target is still alive. With events, when you first lock-on to the target, you’d listen to the target’s dying event. When the dying event fires, you’d get a chance to respond by picking a new target. Convenient, right?

If you don’t know much about Reflection, either read a little from the link, or here’s a short summary: Reflection allows you to look up information about types, create new types, and invoke arbitrary methods on instances of types at run-time. You can create a type by name, call a method by name, get a list of events that some random object has, and many other useful things.

So secondly, I use reflection to call methods on the remote machine. I send some way to look-up a target object along with the name of the method to execute, then a list of all the parameters the method accepts. On the receiving end, I first look up the target object, typically by entity ID, the networking class, or the game instance. Once I know which object will be receiving the message, I reflectively look up the method I want to call using the method name we received, then invoke this method with the parameters that we also received. Calling a remote method becomes as simple as calling a local method.

Now… for the cool part, how I’ve integrated my event system with this network reflection system. Each of my events uses a different delegate, so each event uses a different set of parameters. In my case, each event has a single argument, and that argument extends from this nifty class:

public class ReflectiveEventArgs : EventArgs
{
    public ReflectiveEventArgs(IReflectionTarget target, String theRemoteMethodName,
                               object[] theRemoteMethodParameters)
    {
        Target = target;
        RemoteMethodName = theRemoteMethodName;
        RemoteMethodParameters = theRemoteMethodParameters;
    }
    public IReflectionTarget Target { get; private set; }
    public string RemoteMethodName { get; private set; }
    public object[] RemoteMethodParameters { get; private set; }
}

So each event contains all the information required to reproduce itself over the network using reflection. This generic class is never used directly, even though it could be, but it is even more powerful when you extend from it like this (class hierarchy simplified slightly):

public class EntityMovedEventArgs : ReflectiveEventArgs {
    public EntityMovedEventArgs(Entity theEntity, Vector2 theNewPosition)
        : base(theEntity, "SetCenter", new object[] { theNewPosition })
    {
        NewPosition = theNewPosition;
    }
    public Vector2 NewPosition { get; private set; }
}

This way, the entity just invokes its moved event like it normally would, but behind the scenes, it has actually encoded a way to travel over the network. The method SetCentre is part of any Entity that will be able to move, and it accepts a single parameter of type Vector2 (theNewPosition).

Now for some real fun. How does the network class know what to listen to? Sure, all entities will be able to move, but not all entities will have a power level or a mineral count or… something specific to your class. Reflection! Every time an entity is added to the game world, the network class is given a chance to listen to events. Some events will only want to travel from the server to the clients (eg. moving), and others will only want to travel from the client to the server (eg. request a building). I have handled this by using custom attributes. I plan to change my current implementation, but right now I have an attribute for every event. Using reflection, I can look up these attributes and tell the difference between server->client events and client->server events. The network class will only listen to the events that it deems appropriate based on whether you are a server or not.

After all of that, the network will be able to catch all events that you would like to send over the network, AND it has all of the information required to tell the receiver what to do when an event gets fired. That’s it! When your entity moves, the entity will fire an event telling anyone interested, the network picks that up, serializes the method call embedded in the event args, sends that across the network, deserializes the method call on the receiving end, then the receiver invokes the method using reflection, in this case, it will invoke SetCentre with the new location! It is nearly trivial to add new events too, simply make a new class that extends ReflectiveEventArgs, make an event that accepts this as a parameter, then invoke the event! Poof, the event has affected everyone on the network.

Before I release a new version on SourceForge, I want to have a human way to connect to network games. Right now, the binary will only connect to the localhost, so you have to change the code to connect to a friend. I have just finished writing a simple, and flexible match-making service in PHP that will service this game, and potentially other games I make in the future or games my friends make. I will be working to integrate this match-making service into Asteroid Outpost right away, and along with this, I will need a list control and a simple lobby system. Should be fun, :D