I am currently creating a Multiplayer Game using node.js and web sockets. The client side is receiving the positional information and applying it to all the players on the client side. Each client stores a list of all game elements.
The main issue I am experiencing at the moment is a large amount of Garbage Collection. I am unsure what could be causing this, since I followed all of the tips I have found online; Set Variables to null to make sure they are collected asap. Dont create objects during runtime etc.
After running a profile, this is the result:
As you can see, when the GC kicks in, the game stops for a long time.
How can I reduce/remove GC and object creation from my code. It is fully working, but it performs poorly on low end devices.
I initialize all the arrays at the start.
var energies = [];
var walls = [];
var users = [];
var leaderboard = [];
Those arrays are then filled with data by the server:
socket.on('serverTellPlayerMove', function(userData, strEnergy) {
var visible = [];
for (var i = 0; i < userData.length;) {
if ((userData[i] == 1) == true) {
visible.push({
p : userData[0 + i] == 1,
score : userData[1 + i],
x : userData[2 + i],
y : userData[3 + i],
angle : userData[4 + i],
hue : userData[5 + i],
name : userData[6 + i],
dead : userData[7 + i] == 1,
dying : userData[8 + i] == 1
});
i += 9;
} else {
visible.push({
x : userData[0 + i],
y : userData[1 + i],
angle : userData[2 + i],
hue : userData[3 + i],
name : userData[4 + i],
dead : userData[5 + i] == 1,
dying : userData[6 * i] == 1
});
i += 7;
}
}
// SET DATA:
for (var i = 0; i < visible.length; i++) {
if (visible[i].p) {
var playerData = visible[i];
i = visible.length;
}
}
if (userData != "") {
if (playerType == 'player') {
// HOW MUCH YOU MOVED BY:
var xoffset = player.x - playerData.x;
var yoffset = player.y - playerData.y;
player.x = playerData.x;
player.y = playerData.y;
player.angle = playerData.angle;
player.hue = playerData.hue;
player.xoffset = isNaN(xoffset) ? 0 : xoffset;
player.yoffset = isNaN(yoffset) ? 0 : yoffset;
player.dead = playerData.dead;
document.getElementById("killsText").innerHTML = "Score: "
+ playerData.score;
}
}
// DEATH ANIM:
for (var i = 0; i < visible.length; i++) {
if (visible[i].dying) {
createExplosion(visible[i].x, visible[i].y);
}
}
// DEATH ANIM:
for (var i = 0; i < visible.length; i++) {
if (visible[i].dying) {
createExplosion(visible[i].x, visible[i].y);
}
}
users = visible;
visible = null;
var energiesList = [];
for (var i = 0; i < strEnergy.length; i += 5) {
energiesList.push({
x : strEnergy[0 + i],
y : strEnergy[1 + i],
index : strEnergy[2 + i],
animate : strEnergy[3 + i] == 1,
hue : strEnergy[4 + i]
});
}
var energyT = null;
for (var i = 0; i < energiesList.length; ++i) {
energyT = energiesList[i];
if (energyT.animate) {
energyT.animScale = 0;
} else {
if (energies[energyT.index] != undefined)
energyT.animScale = energies[energyT.index].animScale;
}
energies[energyT.index] = energyT;
}
energiesList = null;
energyT = null;
userData = null;
strEnergy = null;
});
This code is called 30 times a second. This updates every users data on each client side. It also updates the pellet data. Pellets are objects that can be picked up by players in the game. The above snippet does most of the logical work on the client side.
Each object is also rendered as follows:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animloop() {
gameLoop();
animLoopHandle = window.requestAnimFrame(animloop);
}
function doGame() {
now = Date.now();
delta = now - lastUpdate;
lastUpdate = now;
startX = player.x - (screenWidth / 2);
startY = player.y - (screenHeight / 2);
graph.fillStyle = backgroundColor;
graph.fillRect(0, 0, screenWidth, screenHeight);
drawgrid();
drawEnergies(energies);
drawPlayers(users);
drawWalls(walls);
}
And the actual rendering:
function drawPlayers(playersToDraw) {
var playerC = null;
var fontSize = Math.max(playerRadius / 1.2, 11);
var radiusD = (playerRadius * 2.0);
for (var z = 0; z < playersToDraw.length; z++) {
playerC = playersToDraw[z];
if (!playerC.dead) {
var shapeX = playerC.x - startX;
var shapeY = playerC.y - startY;
var rotation = (Math.PI / 180) * playerC.angle;
scale = radiusD;
graph.fillStyle = 'hsl(' + playerC.hue + ', 50%, 45%)';
graph.strokeStyle = 'hsl(' + playerC.hue + ', 50%, 60%)';
graph.lineJoin = "round";
graph.lineWidth = playerHRadius;
graph.save();
graph.translate(shapeX, shapeY);
graph.rotate(rotation);
graph.beginPath();
graph.moveTo(0, (-2 * scale / 3));
graph.lineTo((-scale / 2), scale / 3);
graph.lineTo((scale / 2), scale / 3);
graph.lineTo(0, (-2 * scale / 3));
graph.closePath();
graph.stroke();
graph.fill();
graph.restore();
}
}
playerC = null;
}
As you can see I am setting playerC to null in an Attempt to make it be collected by the GC.
There are some more rendering functions as well, but they are all very similar to the player draw function.
How do I reduce GC on this code and does the data received from the server need to be collected as well?
I have attempted to implement v-sync, but then I realized that the movement data is sent by the server, to there is no way to use delta time or v-sync.
As suggested, I did a Heap Snapshot. Here is the result:
And here is what was actually removed:
After some further looking around, I found this:
It points at the data received from the server. Also I noticed some throw TypeError statements, what could be causing that?
Here is a LIVE DEMO of the code in action: http://www.vertix.io