category: Good pratices

Serialization

Stormancer let you decide between automatically serialize your packets or writting your own before sending them.

How do i create my own packets ?

Not as easily as serializing data, unfortunately.

'send' and 'requests" methods give you the ability to send a byte array. To create your custom packets, you'll have to fill this array use a stream.

The best way is to use a lambda:

client.Send("route", s =>
{
    using(BinaryWriter writer = new BinaryWriter(s, System.Encoding.UTF8))
    {
        writer.Write( int/float/double/whatever is a native object );
        writer.Write( something else );
        writer.Write( etc...) ;
    }
}

On reception:

void Reception(Packet<IsceenPeer> packet)
{
    using (BinaryReader reader = new BinaryReader(packet.stream))
    {
        int i = reader.ReadInt32();
        float f = reader.ReadSingle();
        etc...
    }
}

PS: 'using' is very important as it will close the stream on scope exit.

As you can see, it is not as fast and easy than using the serializer. So why would we use this feature ?

Why create my own packets ?

Basically, bandwidth optimisation.

Even if msgpack is really efficient in serializing data, it will still add some extra bytes so it can deserialize whitout generating errors.

The increase in bandwidth consuption is ridiculous: it goes from nothing to 6 bytes per serialized types.

Bandwidth consuption comparison

Let's imagine we are about to send to our clients every positions of one antoher in their games.

Most games will use the coordinates, rotation and an Id for their position updates, making a grand total of 4 (using floats) * 3 (x, y, z) * 2 + 4 (id) = 28 bytes per client position.

If every floats are equal to 0, msgPack would be more efficient, sending only 12 bytes. However, on a common scenario, it will send 41 bytes (1 "type" byte + 4 data bytes).

Now, we assume that we have 10 clients and are sending position updates to everyone every 100 ms (10 times per second).

Using message pack, we send 41 * 9 (9 positions*) * 10 (10 clients) * 10 (10 message per second) = 36900 bytes per seconds. (maximum. It can be less if message pack compress the datas).

using your custom packet: 25200 bytes per seconds.

  • 9 position if the clients are authoritative. 10 on the other case.

Creating your own packets makes your messages more bandwidth friendly (assuming you do it correctly).

Aggregating your messages

There's something we decided not to show you when calculating our bandwidth consuption: UDP, Raknet and stormancer Headers.

Headers cumulates about 60 bytes of datas on every of your messages. It is then important to aggregate your messages in one single packet:

Using your own custom packets:

sending:

_scene.Broadcast("route", s =>
{
    using(BinaryWriter writer = new BinaryWriter(s, System.Encoding.UTF8))
    {
        foreach (position pos in _clientsPositions)
        {
            writer.Write(id);
            writer.Write( pos.x );
            writer.Write( pos.y );
            writer.Write( etc...) ;
        }
    }
}

receiving:

void Reception(Packet<IsceenPeer> packet)
{
    using (BinaryReader reader = new BinaryReader(packet.stream))
    {
        while (reader.Stream.Position < reader.Stream.Length)
        {
            uint id = reader.readInt32();
            Position pos;
            if (_clientsPosition.TryGetValue(id, out pos) == true)
            {
                pos.x = reader.ReadSingle();
                pos.y = reader.ReadSingle();
                etc...
            }
        }
    }
}

using msgpack:

sending:

List<positionDTO> posdto = new List<positionDTO>();

foreach (position pos in _clientsPosition)
    {
        posdto.Add(new positionDTO(pos));
    }
_scene.Broadcast("route", posdto);

receiving:

void receiving(Packet<ISceenPeer> packet)
{
    var posdtos = packet.ReadObject<List<positionDTO>>();
    foreach(positionDTO p in posdtos)
    {
        Position pos;
        if (_clientsPosition.TryGetValue(id, out pos) == true)
        {
            pos.updatePos(p);
        }
    }
}

Please note that Raknet sends its messages once every 25ms, aggregating on the fly every packets that are to be sent. Dispite this, always manually aggregate your messages as you'll be able to do it more efficiently than Raknet (raknet will keep stormancer headers for each packets. if you send 1 messages to 10 peer every 100 ms, stormancer headers weights 300 bytes).

Conclusion

We recommend using the msgpack serialiazer unless you are aware of what you are doing as it is harder to perform.

However, creating your own packets will make you be able to perform minors optimisations with your bandwidth consuption.

Nevertheless, The best bandwidth optimisation you can, and should, implement is the packet aggregation: you will save a lot of bandwidth, even if RakNet do it natively.