10+ Tips For Optimizing Websockets Bandwidth Usage in HTML5 Games

Try the effect before reading

These are the techniques I’ve used in my upcoming game

Zom Zok – Realtime multiplayer shmup

0. Einaros

For all examples I will be using einaros/ws websocket implementation which is much more lightweight and straightforward than Socket.io

Einaros/ws: https://github.com/einaros/ws

1. Use BISON to reduce packet size about 50%

Get very nice implementation here https://github.com/BonsaiDen/BiSON.js

  var packet = { 
    command: "something", 
    data: { 
      all: "my", 
      pretty: "data", 
      number: 1337 
    }
  };

  ws.send(BISON.encode(packet));
  ws.onmessage = function(event) {
    var packet = BISON.decode(message.data);
    /* do whatever with the packet */
  }

2. Define numeric values for your string constants

This rule applies to everything what is enumerable but you are doing it using strings.

  var packet = {
    action: "update_entity",
    entity: [ ... ]
  };
  var DEFS = {
    UPDATE_ENTITIY: 1
  };

  var packet = {
    action: DEFS.UPADTE_ENTITY,
    entity: [ ... ]
  };

In this case:

  • string consistent of ASCII characters takes 1 byte for each character
  • integer takes 1 byte for value up to 128, then another byte for value up to 32768

3. Clever structured packets

All above examples are somewhat wrong – take a look below:

  var packet = {
    command: "update-something",
    minerals: [32, 12, 16],
    name: "fancy",
    jesus: "christ"
  }
  var packet = [ DEFS.UPDATE_SOMETHING, [32, 12, 16], "fancy", "christ" ];

tip: You can write a converter between this two formats – however – for performance sake I advise you to just keep the packets well documented.

4. Commands

When something happens from time to time – not at every step you don’t really want to send it in every frame. For example:

  var packet = [ ..., player.shooting ];
  /* only if the state changes send this */

  var packet = [ DEFS.PLAYER_SET_SHOOTING, player.shooting ];

5. It doesn’t changed? DON’T SEND IT

For example – you have static objects like asteroids in mine game (which are randomly spread at the beginning of the game) – you only need to send them once – don’t send them in each snapshot.

6. Ads by google

[inlinead]

7. (again) It doesn’t changed? DON’T SEND IT

This is kind of a tricky thing.

   var packet = [ DEFS.UPDATE, "101001", "something", 32, "anything" ];

Now what it does:

  • I keep previous state of shared entity.
  • If value changed I add it to the packet.
  • Then I build an identifier which tells are the values present or not in the packet.
  • “10100″ means “yes skip yes skip skip”.
  • Of course it is binary so I am using integer to store it – string above is to make it more understandable
  • 8. Bullets

    If there are a lot of them you should simulate them 100% at the client side. I am using commands PLAYER_START_SHOOTING, PLAYER_STOP_SHOOTING. Note that this is not fully accurate – and can lead to bullet missing the target yet still damaging it – however it is not very noticeable when the count is great.

    9. Grouping/summing events

    Instead of sending *Player lost 12 hp* whenever it happens – sum it and send once per some time / snapshot.

    10. BISON vs LZW

    If your data relies on strings more than numbers you could give a try to LZW algorithm instead of BISON. LZW is very good when it comes to compress strings with a lot of repetitive words/segments.

    Here is an example implementation. It is used in the same way as JSON.stringify or BISON.encode

    11. Fixed structure packets

    If you have done all the optimisation and still not satisfied with the result you can try fixed packets. It means that there is no separators between values but each size has to be constant. It means you have to define how many bits are used by each value in packet. It also means that you will have to add headers for each collection describing how many objects are within.

    Prologue

    These are the major ideas that made my realtime multiplayer games capable of handling a lot of entities. I’ve wasted a lot of time (and toilet paper) to come out with them as the internet isn’t very generous at multiplayer knowledge. If you have any thoughts – ideas – improvements – please share them below.

    If you want to show appreciation consider using share/like buttons or a donation <3,

    Comments like awful or awesome with no meritorical value will be deleted as I want to keep this thread helpful.

    Cheers.

    Readers addendum

    Reddit

    You may find some of reddit comments useful

    1. Use protocol buffers

    by Amit Patel on #bbg

    http://en.wikipedia.org/wiki/Protocol_Buffers
    https://github.com/sirikata/protojs#readme

    2. Use flat structure

    This is similar approach to using fixed structure but doesn’t require understanding on binary level.

    You can ensure packet structure as a flat array by yourself or use PacketFlattener which converts complex/deep objects structure to flat array.

    by Karel Crombecq from Sileni Studios on #bbg

    Javascript multiplayer concepts: cutting off unchanged data

    Here is a concept which I use on my snapshot based multiplayer games to reduce ammount of sent data. The idea is basically to send only the data which have changed since last snapshot.

    Let’s assume we have array of objects representing some ships. I recommend klass library for object-oriented javascript (omg, I really said that – I recommend vanilla js now), but for simplicity sake we gonna do it in raw prototypal approach (that’s better). Before we start make sure that you are familiar with extend function which can be found in libraries like jQuery or underscore.

    We have to monitor which data has changed. As javascript doesn’t have convinient and fast method to overload operators (getters, setters) we are left with two resonable options:

    • defining set/get (key, value) function and use it instead of operator =
    • keep an array of last sent values for further comparison

    Second option will be in charge this time.

    function Ship(args) {
      // id corresponding with index of this element in ships collection array/object
      $.extend(this, {
        id: 0,
        x: 0,
        y: 0,
        direction: 0,
        destination: [0, 0],
        type: "fighter",
        foo: "something",
        bar: "i like trains",
        // values from our last snapshot
        lastExportedValues: {}
      }, args);
    }

    Properties foo and bar represents variables which are usable only for server and we shouldn’t waste bandwidth for sending them.

    // keys for export
    Ship.prototype.exports = ["x", "y", "direction", "destination", "type"];

    Now the export function which will give us an object containing only properties which have changed. Also we want to get rid of key names from our JSON so we have to inform receiver which keys/indexes has been skipped. We simply create a string like “1000100″ where 0 means that key responding it’s position is not included in packet.

    // ships collection
    var ships = {};
    
    Ship.prototype.export = function() {
      // packet as array, we don't need {keys: values}
      var result = [];
      // which values has been exported/changed ?
      var exported = ""; 
    
      for(var i = 0; i < this.exports.length; i++) {
        // for readibility
        var key = this.exports[i];
        // if value has changed since last export push it into results
        if(this.lastExportedValues[key] != this[key]) {
          this.lastExportedValues[key] = this[key];
          result.push(this[exports[i]]);
          // set flag which tells that we sent this key or no
          exported += "1";
        } else exported += "0";
      }
      // tell receiver which ship is this
      result.unshift(this.id);
      // tell receiver what have we exported
      result.unshift(exported);
    
      return result;
    }

    So our single ship now looks like [id, exportedValues, data1, data..x] example: [3, "100010", 3, "fighter"...]

    function synchronize() {
      // this array will contain our "compressed" packet
      var packet = [];
      // we use associative array so we have to waste some ops for var in loop
      for(var i in ships) {
        packet.push(ships[i].export());
      }
    
      // send with your favorite mean of transport, ex. Websockets or AJAX
      send(packet);
    }

    Now for the client/receiver:

    function onMessage(data) {
      // I assume you have had already decoded data to object and it contains an array of packed ships
      for (var i = 0, len = data.length; i < len; i++) {
    
        var packedShip = data[i];
    
        // remember that our ship is still a numeric array so we have to translate it to usable state
        var ship = {}
    
        // as we remember first key was ship's ID
        var id = packedShip.shift();
        // and the second will give us info which values has been exported
        var exported = packedShip.shift();
    
        // at which value in exported packet are wee looking now
        var lookupIndex = 0;
    
        // let's loop through values which could be exported
        for (var j = 0; j < Ship.prototype.exports.length; j++) {
          var key = Ship.prototype.exports[j];
          // if the key was exported let's take the value under current lookup index and move it one place right
          if (exported[key] == "1") {
            ship[key] = data[lookupIndex++];
          }
          // if value hasn't been exported we just do nothing and lookup cursor stands its ground
        }
    
        console.log(ship);
    
        // hooray now ship contains our exported object, we have ID so we can
        // for example insert this object into collection if it doesn't exist
        // or update if it does
      }
    }

    Need more compression ? Tips for minimizing bandwidth usage in HTML5 + websockets games.