Page 1 of 1

Network compression and optimization!

Posted: Tue Feb 15, 2011 8:16 pm
by EdBoon
There are a number of small tricks to compress space such as taking a float and turning it into a byte when it is used for rotation like so:

Code: Select all

packetWriter.Write((byte)(localPlayer.rotation * 256 / (2*Math.Pi)));
then when it is read, just apply the correct multiplier and it is fine. This works because ~about~ 1° precision is fine(most of the time) in the case of rotation. Other floats may be different, it just depends on the application.

Another example of compression that I used is for Vector2 types.

Code: Select all

HalfVector2 packedVector = new HalfVector2(localPlayer.position);
                packetWriter.Write(packedVector.PackedValue);
//thanks shawn hargreaves!
The reason for this post is I am wondering if there is anything I could do with my code similar to the above to decrease traffic. My application is a top down shooter with multiple (up to 300) enemies at a time so it is very important to get the traffic as low as possible. And i know how much you guys love optimization 8-)

The method I am using right now is mostly client/host. Each player in the game, be it client or host, updates their player position and rotation and any newly created projectiles position/rotation and sends that data to the host. The host takes that info, updates the player objects in its game, sends out this info to each player. The server also handles all collision, and all enemy seek functions. It sends the updated positions/rotations of the enemies to each client(there can be more than 2). I am not sure if this is the best way to do things, on the client side i take the position of each enemy and draw them at their locations, they arent really created enemies, just sort of copying the screen from the host. to more easily see the data transfer:

Server to client:
for each player:
position - half vect2
rotation - byte
weapon - byte
state (running/firing/dead/etc) - byte
health - byte
lives - byte

all projectiles
position - half vect2
rotation - byte
type - byte

Other network items (pickups)
position - half vect2
type - byte

Client to Server
for each local player
position - half vect2
rotation - byte
weapon - byte
state - byte
health - byte
lives - byte

I am also using a very simple network prediction method, and only sending receiving about every 6 frames, so roughly 10 times a second. The prediction could prob use some tweaking, but it works for now:

Code: Select all

public void UpdatePrediction(Vector2 newPosition, float networkDelay)
        {
            if (lastPos == Vector2.Zero)
            {
                lastPos = newPos;
            }
            else
            {
                netStep = ((lastPos + 2 * (newPosition - lastPos)) - position) / networkDelay; // subtract the newPosition that is received in the packet, subtract the 
//last position that the previous packet sent, estimate the position from that direction, add it to last place we were at, and divide by the amount of time 
//before a new packet comes , then add that to its position over each frame.
            }

            lastPos = newPosition;

        }
public void UpdateRemote()//updating the position each frame, also clamp to make sure the dude isnt going crazy fast because my prediction is way off :)
        {
            Vector2 clampStep = Vector2.Clamp(netStep, new Vector2(-speed * 1.1f), new Vector2(speed * 1.1f));
            position += clampStep;
        }

then below is some of the network code for packet transfer just for reference:

local gamer to server

Code: Select all

public void UpdateLocalGamer(LocalNetworkGamer gamer, bool sendthisframe)
        {
            Player localPlayer = gamer.Tag as Player;

            for(int i = 0; i < 4; i++)
            {
                if (Game1.player[i] != null && Game1.player[i].gamertag == gamer.Gamertag)
                {
                    gamer.Tag = Game1.player[i];
                    localPlayer = gamer.Tag as Player;
                }
            }

            // Only send if we are not the server. There is no point sending packets
            // to ourselves, because we already know what they will contain! Prob porn!
            if (!Game1.networkSession.IsHost &&  sendthisframe)//sendthisframe is true when networkdelay has passed so we arnt sending each frame
            {
                // Write our latest input state into a network packet.
                packetWriter.Write(true); //just saying its a gamestate packet (false for in lobby)
                packetWriter.Write(localPlayer.pressedA);
                
                HalfVector2 packedVector = new HalfVector2(localPlayer.position);
                packetWriter.Write(packedVector.PackedValue);

                packetWriter.Write((byte)(localPlayer.rotation * 256 / MathHelper.TwoPi));

                packetWriter.Write(localPlayer.weaponEquipped.gunName);
                packetWriter.Write(localPlayer.weaponEquipped.upgraded);

                packetWriter.Write(projectileVectorList.Count);
                for (int i = 0; i < projectileVectorList.Count; i++)
                {
                    packetWriter.Write(projectileVectorList[i]);
                }

                // Send our input data to the server.
                gamer.SendData(packetWriter,
                               SendDataOptions.InOrder, Game1.networkSession.Host);
            }
            projectileVectorList.Clear();
        }
server to remote gamers:

Code: Select all

public void UpdateServer()
        {
            enemylist.Clear();
            
            foreach (Actors.Enemies.Enemy enemy in Game1.gameItems.OfType<Actors.Enemies.Enemy>())
            {
                enemylist.Add(enemy);
            }
            projectilelist.Clear();
            foreach (Actors.Projectile bullet in Game1.projectileItems.OfType<Actors.Projectile>())
            {
                projectilelist.Add(bullet);
            }

            // Loop over all the players in the session, not just the local ones!
            packetWriter.Write(true);
            packetWriter.Write(Level.Stage.eventTracker);
            packetWriter.Write(Game1.enemAlive);

            foreach (NetworkGamer gamer in Game1.networkSession.AllGamers)
            {
                Player localPlayer = gamer.Tag as Player;

                for (int i = 0; i < 4; i++)
                {
                    if (Game1.player[i] != null && Game1.player[i].gamertag == gamer.Gamertag)
                    {
                        gamer.Tag = Game1.player[i];
                        localPlayer = gamer.Tag as Player;
                        break;
                    }
                }
                
                packetWriter.Write(gamer.Id);


                HalfVector2 packedVector = new HalfVector2(localPlayer.position);
                packetWriter.Write(packedVector.PackedValue);

                packetWriter.Write((byte)(localPlayer.rotation * 256 / MathHelper.TwoPi));
                

                packetWriter.Write((byte)localPlayer.health);
                packetWriter.Write((byte)localPlayer.lives);
                packetWriter.Write(localPlayer.weaponEquipped.gunName);
                packetWriter.Write(localPlayer.weaponEquipped.upgraded);

                packetWriter.Write(localPlayer.networkPickup.Count);
                for (int i = 0; i < localPlayer.networkPickup.Count; i++)
                {
                    packetWriter.Write(localPlayer.networkPickup[i]);
                }
                localPlayer.networkPickup.Clear();
            }

            packetWriter.Write(enemylist.Count);
            foreach (Actors.Enemies.Enemy enemy in enemylist)
            {
                packetWriter.Write(enemy.type);

                HalfVector2 packedVector = new HalfVector2(enemy.position);
                packetWriter.Write(packedVector.PackedValue);

                //packetWriter.Write(enemy.rotation);
                packetWriter.Write((byte)(enemy.rotation * 256 / MathHelper.TwoPi));
            }
            packetWriter.Write(projectilelist.Count);
            foreach (Actors.Projectile bullet in projectilelist)
            {
                packetWriter.Write(bullet.type);
                packetWriter.Write(bullet.rotation);
                packetWriter.Write(bullet.position);
            }
            packetWriter.Write(Game1.networkItems.Count);
            foreach (Vector3 gameitems in Game1.networkItems)
            {
                packetWriter.Write(gameitems.X);
                packetWriter.Write(gameitems.Y);
                packetWriter.Write(gameitems.Z);
            }
            Game1.networkItems.Clear();


            packetWriter.Write(pickupIconX.Count);
            for (int i = 0; i < pickupIconX.Count; i++)
            {
                packetWriter.Write(pickupIconX[i]);
                packetWriter.Write(pickupIconY[i]);
                packetWriter.Write(pickupIconType[i]);
            }
            pickupIconX.Clear();
            pickupIconY.Clear();
            pickupIconType.Clear();

            // Send the combined data for all tanks to everyone in the session.
            LocalNetworkGamer server = (LocalNetworkGamer)Game1.networkSession.Host;

            server.SendData(packetWriter, SendDataOptions.InOrder);
        }
ouch that one was long...

I don't expect anyone to read through everything and optimize it, just seeing if anyone that skims through it sees something obvious that could help out my traffic a bit, or if you have any suggestions on my client/server method, any extra knowledge is good knowledge!