title: increasingly high latency in node.js multiplayer html5 game
body: <p>I created a webpage using node.js, express js, socket.io, and jquery. It is a simple game wherein players join, give themselves a name, and then move themselves(a square) around a canvas on the page. What I've noticed is: when more people join the game and move around, there is enough lag to make the game unplayable (at only three people connected. Two people doesn't lag it much). I can't figure out if this is server-side lag or client-side (this is my first project dealing with multiplayer). I am doing all of the location calculations on the server, and sending an array of all the player objects back to every socket so that each client can render all of the players. The client only sends input and draws the players. </p>
  
  <p>This is the client side script for the game. This is where I handle the input and rendering.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>$(document).ready(function()
  {
   var socket = io.connect();
  
   var canvas = document.getElementById("canvas_html");
   var ctx = canvas.getContext("2d");
    
   canvas.width = 512;
   canvas.height = 480;
    
   document.body.appendChild(canvas);
  
   var player = 
   {
    id: '',
    is_it: false,
    x: canvas.width / 2,
    y: canvas.height / 2,
    velx: 0,
    vely: 0
   }
      
      //tell the server to initialize this client as a new player
   socket.emit('init_client', player);
      
   var client_player_list = [];
      
      //receive a list of the player objects from the server
   socket.on('load_players', function(players)
   {
    client_player_list = players;
   });
  
   var keysDown = {};
  
   addEventListener('keydown', function(e)
   {
    keysDown[e.keyCode] = true;
   }, false);
  
   addEventListener('keyup', function(e)
   {
    delete keysDown[e.keyCode];
   }, false);
  
   //take input from keys and send input to server
   var update = function()
   {
    if(87 in keysDown)//player holding w
     socket.emit('up');
    if(83 in keysDown)//player holding s
     socket.emit('down');
    if(65 in keysDown)//player holding a
     socket.emit('left');
    if(68 in keysDown)//player holding d
     socket.emit('right');
    if(74 in keysDown)//player holding j
     socket.emit('tag');
   };
  
   //render all players, update players when server updates
   var render = function()
   {
    //ctx.clearColor = "rgba(0, 0, 0, .3)";
    ctx.clearRect( 0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "#079641";
    ctx.textAlign = 'center';
        
          //loop through the players array and render each one
    for(var i = 0; i &lt; client_player_list.length; i++)
    {
              //if the player is_it, render them as red
     if(client_player_list[i].is_it)
     { 
      ctx.fillStyle = "#8F0E0E";
      ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 20, 20);
                  
                  //draw the players name above them
      ctx.fillStyle = "#FFF";
      ctx.font="15px Arial";
      ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
      continue;
     }
              
              //if the player !is_it, render them as green
              ctx.fillStyle = "#079641";
     ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 20, 20);
              
              //draw the players name above them
     ctx.fillStyle = "#FFF";
     ctx.font="15px Arial";
     ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
    }
           
          //when the server sends an update, replace the current players array with the one that the server just sent
    socket.on('sv_update', function(players)
    {
     client_player_list = players;
    });
   };
  
   //main loop
   var main = function()
   {
    setInterval(function()
    {
     update();
     render();
    }, 1000/60);
   };
  
   main();
  
   //trigger the disconnect event when a page refreshes or unloads
   $(window).bind('beforeunload', function()
   {
    socket.emit('disconnect');
   });
  });</code></pre>
  </div>
  </div>
  </p>
  
  <p>Below, I have included the app.js file (the server-side script) for my game. I am not sure exactly where the problem is, so I figured I'd just post the whole thing.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var express = require('express');
  var http = require('http');
  var io = require('socket.io', { rememberTransport: false, transports: ['WebSocket', 'Flash Socket', 'AJAX long-polling'] });
  
  var app = express();
  var server = http.createServer(app);
  server.listen(8080);
  
  app.use(express.static('public'));
  
  io = io.listen(server);
  
  //Declare variables for working with the client-side
  var player_speed = 5;
  var player_size = 20;
  var vel_increment = 0.5;
  var canvas_height = 480;
  var canvas_width = 512;
  
  //Declare list of players connected
  var players = [];
  
  io.sockets.on('connection', function(socket)
  {
    var socket_id = socket.id;
    var player_index, player_exists = false;
    var this_player;
  
    var check_bounds = function()
    {
      //Keep player in the canvas
      if(this_player.y &lt; 0)
          this_player.y = 0;
      if(this_player.y + player_size &gt; canvas_height)
          this_player.y = canvas_height - player_size;
  
      if(this_player.x &lt; 0)
        this_player.x = 0;
      if(this_player.x + player_size &gt; canvas_width)
        this_player.x = canvas_width - player_size;
  
      //Keep velocity between -5 and 5
      if(this_player.vely &gt; player_speed)
        this_player.vely = player_speed;
      if(this_player.velx &gt; player_speed)
        this_player.velx = player_speed;
  
      if(this_player.vely &lt; -player_speed)
        this_player.vely = -player_speed;
      if(this_player.velx &lt; -player_speed)
        this_player.velx = -player_speed;
    };
  
    var sv_update = function()
    {
      io.sockets.emit('sv_update', players);
      if(player_exists)
      {
        if(players.length == 1)
          this_player.is_it = true;
        check_bounds();
      }
    };
    
    //When a client connects, add them to players[]
    //Then update all clients
    socket.on('init_client', function(player)
    {
      player.id = socket.id;
      players.push(player);
  
      for(var i = 0; i &lt; players.length; i++)
        if(players[i].id == socket_id)
          player_index = i;
      player_exists = true;
      this_player = players[player_index];
  
      sv_update();
      socket.emit('load_players', players);
  
      console.log(players);
    });
  
    //====================CHAT==========================//
    var address = socket.request.connection.remoteAddress;
  
    socket.on('new user', function(data, callback)
    {
      if(player_exists)
      {
        this_player.name = data;
        console.log(address + " has connected as '" + data + "'.");
        
        callback();
      }
    });
  
    socket.on('send message', function(data)
    {
      io.sockets.emit('broadcast', this_player.name, data);
    });
    //====================CHAT==========================//
    
    //if player is_it and is within another player, hitting 'j' will make the other player is_it. 
    socket.on('tag', function()
    {
      if(player_exists)
      {
        for(var i = 0; i &lt; players.length; i++)
        {
          if((this_player.x + player_size &gt;= players[i].x &amp;&amp; this_player.x + player_size &lt;= players[i].x + player_size )|| 
            (this_player.x &lt;= players[i].x + player_size &amp;&amp; this_player.x + player_size &gt;= players[i].x))
            if((this_player.y + player_size &gt;= players[i].y &amp;&amp; this_player.y + player_size &lt;= players[i].y + player_size )|| 
            (this_player.y &lt;= players[i].y + player_size &amp;&amp; this_player.y + player_size &gt;= players[i].y))
            {
              if(this_player.is_it)
              {
                this_player.is_it = false;
                players[i].is_it = true;
              }
            }
        }
        sv_update();
      }
    });
  
    //Gather key input from users...
    socket.on('up', function()
    {
      if(player_exists)
      {
        this_player.y -= player_speed;
  
        sv_update();
      }
    });
  
    //Gather key input from users...
    socket.on('down', function()
    {
      if(player_exists)
      {
        this_player.y += player_speed;
  
        sv_update();
      }
    });
  
    //Gather key input from users...
    socket.on('left', function()
    {
      if(player_exists)
      {
       this_player.x -= player_speed;
  
        sv_update();
      }
    });
  
    //Gather key input from users...
    socket.on('right', function()
    {
      if(player_exists)
      {
        this_player.x += player_speed;
  
        sv_update();
      }
    });
  
    //When a player disconnects, remove them from players[]
    //Then update all clients
    socket.on('disconnect', function()
    {
      for(var i = 0; i &lt; players.length; i++)
      {
        if(players[i].id == socket.id)
        {
          players.splice(i, 1);
        }
      }
  
      sv_update();
    });
  });</code></pre>
  </div>
  </div>
  </p>
  
  <p>--------------------------------------------------------- EDIT ------------------------------------------------------------------ I took Ruslanas Balčiūnas's suggestion for moving the sv_update handler in the client out of the render function. This stops the player from lagging, but the new problem is that the server doesn't send enough updates for all the players to move fluidly on any given client. Each client seems to be running smoothly for just themselves, but the other clients see them as choppy/lagging.</p>
  
  <p>Here is the updated code:</p>
  
  <p>CLIENT:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>$(document).ready(function()
  {
   var socket = io.connect();
  
   var canvas = document.getElementById("canvas_html");
   var ctx = canvas.getContext("2d");
   canvas.width = 512;
   canvas.height = 480;
  
   document.body.appendChild(canvas);
  
   var player = 
   {
    id: '',
    name: '',
    is_it: false,
    x: canvas.width / 2,
    y: canvas.height / 2,
    velx: 0,
    vely: 0
   };
  
   var client_player_list = [];
   socket.on('load_players', function(players)
   {
    client_player_list = players;
   });
  
   var keysDown = {};
  
   addEventListener('keydown', function(e)
   {
    keysDown[e.keyCode] = true;
   }, false);
  
   addEventListener('keyup', function(e)
   {
    delete keysDown[e.keyCode];
   }, false);
  
   //take input from keys and send input to server
   var update = function()
   {
    if(87 in keysDown)//player holding w
     socket.emit('input', 'up');
    if(83 in keysDown)//player holding s
     socket.emit('input', 'down');
    if(65 in keysDown)//player holding a
     socket.emit('input', 'left');
    if(68 in keysDown)//player holding d
     socket.emit('input', 'right');
    if(74 in keysDown)
     socket.emit('input', 'tag');
   };
  
   //render all players, update players when server updates
   var render = function()
   {
    //ctx.clearColor = "rgba(0, 0, 0, .3)";
    ctx.clearRect( 0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "#079641";
    ctx.textAlign = 'center';
  
    for(var i = 0; i &lt; client_player_list.length; i++)
    {
     if(client_player_list[i].is_it)
      ctx.fillStyle = "#8F0E0E";
     else
      ctx.fillStyle = "#079641";
  
     ctx.fillRect(client_player_list[i].x, client_player_list[i].y, 25, 25);
  
     ctx.fillStyle = "#FFF";
     ctx.font="15px Arial";
     ctx.fillText(client_player_list[i].name, client_player_list[i].x + 8, client_player_list[i].y - 3);
    }
   };
  
   socket.on('sv_update', function(players)
   {
    client_player_list = players;
   });
  
   //main loop
   var main = function()
   {
    setInterval(function()
    {
     update();
     render();
    }, 1000/60);
   };
  
   main();
  
   //---- Chat stuff ----
   var toggle = 1;
  
   $('#users').fadeIn(1000);
   $('#name_field').focus();
  
   $('#user_form').submit(function(e)
   {
    console.log('ezpz');
    e.preventDefault();
    socket.emit('init_client', player, $('#name_field').val(), function()
    {
     $('#users').fadeOut('slow');
     window.setTimeout(function(){$('#chat').fadeIn('slow');$('#canvas_html').fadeIn('slow');$('#info').fadeIn('slow')}, 1000);
    });
   });
  
   $('#desk').submit(function(e)
   {
    e.preventDefault();
    socket.emit('send message', $('#message').val());
    $('#message').val('');
   });
  
   socket.on('broadcast', function(name, data)
   {
    $('#message_window').append('&lt;p class="p' + toggle + '"&gt;' + name + ": " + data + '&lt;/p&gt;');
    $('#message_window')[0].scrollTop = $('#message_window')[0].scrollHeight;
  
    if(toggle == 1)
     toggle = 2;
    else
     toggle = 1;
   });
  
   //trigger the disconnect event when a page refreshes or unloads
   $(window).bind('beforeunload', function()
   {
    socket.emit('disconnect');
   });
  });</code></pre>
  </div>
  </div>
  </p>
  
  <p>SERVER:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var express = require('express');
  var http = require('http');
  var io = require('socket.io', { rememberTransport: false, transports: ['WebSocket', 'Flash Socket', 'AJAX long-polling'] });
  
  var app = express();
  var server = http.createServer(app);
  server.listen(8080);
  
  app.use(express.static('public'));
  
  io = io.listen(server);
  
  //Declare variables for working with the client-side
  var player_speed = 5;
  var player_size = 25;
  var canvas_height = 480;
  var canvas_width = 512;
  
  //Declare list of players connected
  var players = [];
  
  io.sockets.on('connection', function(socket)
  {
    var socket_id = socket.id;
    var player_index, this_player, player_exists = false;
  
    //When a client connects, add them to players[]
    //Then update all clients
    socket.on('init_client', function(player, name, callback)
    {
      player.id = socket.id;
      players.push(player);
  
      for(var i = 0; i &lt; players.length; i++)
        if(players[i].id == socket_id)
          player_index = i;
      player_exists = true;
      this_player = players[player_index];
  
      this_player.name = name;
      callback();
  
      sv_update();
      socket.emit('load_players', players);
    });
  
    socket.on('send message', function(data)
    {
      io.sockets.emit('broadcast', this_player.name, data);
    });
  
    //Gather key input from users...
    socket.on('input', function(key)
    {
      if(player_exists)
      {
        if(key == 'up')
          this_player.y -= player_speed;
        else if(key == 'down')
          this_player.y += player_speed;
        else if(key == 'left')
          this_player.x -= player_speed;
        else if(key == 'right')
          this_player.x += player_speed;
        else if(key == 'tag')
          for(var i = 0; i &lt; players.length; i++)
            if(can_tag(players[i]))
            {
              this_player.is_it = false;
              players[i].is_it = true;
            }
  
        sv_update();
      }
    });
  
    //When a player disconnects, remove them from players[]
    //Then update all clients
    socket.on('disconnect', function()
    {
      for(var i = 0; i &lt; players.length; i++)
        if(players[i].id == socket.id)
          players.splice(i, 1);
  
      sv_update();
    });
  
    var check_bounds = function()
    {
      //Keep player in the canvas
      if(this_player.y &lt; 0)
          this_player.y = 0;
      if(this_player.y + player_size &gt; canvas_height)
          this_player.y = canvas_height - player_size;
  
      if(this_player.x &lt; 0)
        this_player.x = 0;
      if(this_player.x + player_size &gt; canvas_width)
        this_player.x = canvas_width - player_size;
    };
  
    var sv_update = function()
    {
      io.sockets.emit('sv_update', players);
      if(player_exists)
      {
        if(players.length == 1)
          this_player.is_it = true;
        check_bounds();
      }
    };
  
    var can_tag = function(target)
    {
      if(this_player.x &lt; target.x + player_size &amp;&amp; this_player.x + player_size &gt; target.x &amp;&amp; this_player.y &lt; target.y + player_size &amp;&amp; player_size + this_player.y &gt; target.y &amp;&amp; this_player.is_it)
        return true;
    }
  
    sv_update();
  });</code></pre>
  </div>
  </div>
  </p>
  
  <p>Your help would be greatly appreciated :)</p>