HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Garbage collection in JavaScript multiplayer game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
javascriptcollectionmultiplayergamegarbage

Problem

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++) {

Solution

I think you should use a static approach for the visible array, avoiding the push instruction: declare and fill the array with dummy structures outside the socket.on handler and then iterate over the items to change their value.

Use a variable to keep the length of the received values, instead of calling visible.length().

You are also copying many times your data: in Javascript var playerData = visible[i]; makes another copy of the data, consider accessing the player's data by index instead of copying it in another structure.

The same problem arises for the energiesList array. Define it outside the event and only update its values inside, keeping the real lenght in a variable.

Hope it helps.

EDIT

This is how the code should be refactored (you need to test it)

```
MAX_VISIBLES = 500;
VISIBLE_PLAYER = 1;
VISIBLE_OTHER = 0;
var visible = [];
for (var i = 0; i < MAX_VISIBLES; i++) {
visible.push({
type: 0,
p : 0,
score : 0,
x : 0,
y : 0,
angle : 0,
hue : 0,
name : 0,
dead : 0,
dying : 0
});
}

// Just need it once (only if you use it anywhere else in the code)
users = visible;
// You'll need it if you want to iterate over users
var usersLength = 0;

MAX_ENERGIES = 1000;
var energiesList = [];
for (var i = 0; i < MAX_ENERGIES; i++) {
energiesList.push({
x : 0,
y : 0,
index : 0,
animate : 0,
hue : 0
});
}

socket.on('serverTellPlayerMove', function(userData, strEnergy) {
for (var i = 0, visibleLength = 0; i < userData.length; visibleLength++) {
if ((userData[i] == 1) == true) {
visible[visibleLength].type = VISIBLE_PLAYER;
visible[visibleLength].p : userData[0 + i] == 1;
visible[visibleLength].score : userData[1 + i];
visible[visibleLength].x : userData[2 + i];
visible[visibleLength].y : userData[3 + i];
visible[visibleLength].angle : userData[4 + i];
visible[visibleLength].hue : userData[5 + i];
visible[visibleLength].name : userData[6 + i];
visible[visibleLength].dead : userData[7 + i] == 1;
visible[visibleLength].dying : userData[8 + i] == 1;
i += 9;

var playerData = visible[visibleLength];
} else {
visible[visibleLength].type = VISIBLE_OTHER;
visible[visibleLength].x : userData[2 + i];
visible[visibleLength].y : userData[3 + i];
visible[visibleLength].angle : userData[4 + i];
visible[visibleLength].hue : userData[5 + i];
visible[visibleLength].name : userData[6 + i];
visible[visibleLength].dead : userData[7 + i] == 1;
visible[visibleLength].dying : userData[8 + i] == 1;
i += 7;
}
}
// I don't think you need this cycle: playerdata can be assigned
// during userData parsing
// // 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 < visibleLength; i++) {
if (visible[i].dying) {
createExplosion(visible[i].x, visible[i].y);
}
}

// DEATH ANIM:
for (var i = 0; i < visibleLength; i++) {
if (visible[i].dying) {
createExplosion(visible[i].x, visible[i].y);
}
}

// See above
// users = visible;
// visible = null;
usersLength = visibleLength;

for (var i = 0, energiesListLength = 0; i < strEnergy.length; i += 5, energiesListLength++) {
energiesList[energiesListLength].x : strEnergy[0 + i];
energiesList[energiesListLength].y : strEnergy[1 + i],
energiesList[energiesListLength].index : strEnergy[2 + i];
energiesList[energiesListLength].animate : strEnergy[3 + i] == 1;
energiesList[energiesListLength].hue : strEnergy[4 + i];
}

var energyT = null;
for (var i = 0; i < energiesListLength; ++i) {
energyT = energiesList[i];
if (energyT.animate) {
energyT.animScale = 0;
} else {
if (energies[energyT.index] != undefined)
energyT.animScale = energies[energyT.index].animScale;
}

Code Snippets

MAX_VISIBLES = 500;
VISIBLE_PLAYER = 1;
VISIBLE_OTHER = 0;
var visible = [];
for (var i = 0; i < MAX_VISIBLES; i++) {
    visible.push({
        type: 0,
        p : 0,
        score : 0,
        x : 0,
        y : 0,
        angle : 0,
        hue : 0,
        name : 0,
        dead : 0,
        dying : 0
    });
}

// Just need it once (only if you use it anywhere else in the code)
users = visible;
// You'll need it if you want to iterate over users
var usersLength = 0;

MAX_ENERGIES = 1000;
var energiesList = [];
for (var i = 0; i < MAX_ENERGIES; i++) {
    energiesList.push({
        x : 0,
        y : 0,
        index : 0,
        animate : 0,
        hue : 0
    });
}

socket.on('serverTellPlayerMove', function(userData, strEnergy) {
    for (var i = 0, visibleLength = 0; i < userData.length; visibleLength++) {
        if ((userData[i] == 1) == true) {
            visible[visibleLength].type = VISIBLE_PLAYER;
            visible[visibleLength].p : userData[0 + i] == 1;
            visible[visibleLength].score : userData[1 + i];
            visible[visibleLength].x : userData[2 + i];
            visible[visibleLength].y : userData[3 + i];
            visible[visibleLength].angle : userData[4 + i];
            visible[visibleLength].hue : userData[5 + i];
            visible[visibleLength].name : userData[6 + i];
            visible[visibleLength].dead : userData[7 + i] == 1;
            visible[visibleLength].dying : userData[8 + i] == 1;
            i += 9;

            var playerData = visible[visibleLength];
        } else {
            visible[visibleLength].type = VISIBLE_OTHER;
            visible[visibleLength].x : userData[2 + i];
            visible[visibleLength].y : userData[3 + i];
            visible[visibleLength].angle : userData[4 + i];
            visible[visibleLength].hue : userData[5 + i];
            visible[visibleLength].name : userData[6 + i];
            visible[visibleLength].dead : userData[7 + i] == 1;
            visible[visibleLength].dying : userData[8 + i] == 1;
            i += 7;
        }
    }
// I don't think you need this cycle: playerdata can be assigned
// during userData parsing
//    // 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;
       

Context

StackExchange Code Review Q#108742, answer score: 2

Revisions (0)

No revisions yet.