Онлайн игра на Node.js очень сильно виснет

Как можно оптимизировать код игры так, чтобы лаги были минимальные? Сервер запускал на более мощных машинах (Linux, Windows), но особой разницы не видно. В изменении направления присутствует задержка примерно 10 секунд, на мощной машине до 8 секунд. Задержка присутствует именно при изменении угла движения игрока, то есть кадры в секунду нормальные.

P.S. Клиент здесь абсолютно ни при чем, лагает именно сервер.

Код:

var http = require("http");
var app = require("express")();
var websocketServer = require("websocket").server;
var httpServer = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('<h1>;-;</h1>');
  res.end();
});
var WebSocket = require("websocket").client, PORT = 0, players = {}, masterHost = "localhost", objectsCount = 0, oldname = {}, playersCount = 0, updateZ = null, foodCount = 0, mouseMid = {}, setMov = {}, deletetimer = {}, systimer = {}, gameMouse = {}, sendTimer = {}, animTimer = {}, hydroloss = {}, reviveHP = {}, objects = {}, defsystemmsg = "cyan", stars = {};

var topx;
var imp = 1;

srconfig = {
    maxConnections: 100,
    leftBorder: 0,
    rightBorder: 7000,
    upBorder: 0,
    downBorder: 7000,
    movspeed: 1.05,
    startPlayerMass: 0,
    uName: "no name",
    foodMCount: 800
};

function gameServer() {
    this.moduleManager = new ModuleManager(this);
    this.players = players;
    this.config = srconfig;
    this.objects = objects;
    this.defsystemmsg = defsystemmsg;
    players = this.players;
    objects = this.objects;
    defsystemmsg = this.defsystemmsg;
}

stars["type"] = {
    desc: "Some description"
}

var wsServer = new websocketServer({
    "httpServer": httpServer
})

function angle(cx, cy, ex, ey) {
  var dy = ey - cy, dx = ex - cx;
  var theta = Math.atan2(dy, dx);
  theta *= 180 / Math.PI;
  return theta;
}

function sq(x) { return x*x;  }

function updateGame() {
    for (var name in objects) {
        objectC = objects[name];
        if (objectC.canmove == 1) {
            if (objectC.mY == 0 && objectC.mX == 0) {
                objectC.mY = Math.floor(Math.random() * 11) -5;
                objectC.mX = Math.floor(Math.random() * 11) -5;
            }
            if (objectC.speed == 0) {
                objectC.speed = Math.random();
            }
            needSpeedObj = objectC.speed || srconfig.movspeed / 3;
            var objAngle = Math.atan2(objectC.mY, objectC.mX);
            var objDist = Math.sqrt(sq(objectC.mX) + sq(objectC.mY));
            objectC.anglez = angle(0, 0, objectC.mX, objectC.mY);
            speed = Math.min(needSpeedObj, 0.035 * Math.pow(objDist, impImitz));
            if (speed < 0.06 && speed > -0.06) { speed = 0 }
            objectC.vx = speed * Math.cos(objAngle);
            objectC.vy = speed * Math.sin(objAngle);
            objectC.x += objectC.vx;
            objectC.y += objectC.vy;
        }
        if (objectC.x <= srconfig.leftBorder || objectC.x-objectC.size <= srconfig.leftBorder) {
            objectC.x = srconfig.rightBorder - objectC.size - 25;
        } else if (objectC.x >= srconfig.rightBorder || objectC.x+objectC.size >= srconfig.rightBorder - 25) {
            objectC.x = srconfig.leftBorder + objectC.size + 25;
        }
        if (objectC.y <= srconfig.upBorder || objectC.y-objectC.size <= srconfig.upBorder) {
            objectC.y = srconfig.downBorder - objectC.size - 25;
        } else if (objectC.y >= srconfig.downBorder || objectC.y+objectC.size >= srconfig.downBorder - 25) {
            objectC.y = srconfig.upBorder + objectC.size + 25;
        }
        if (objectC.angle >= 360) {
            objectC.angle = 0;
        }
        objectC.angle += objectC.speedZ;
        objectC.size = objectC.mass / 2;
    }
    for (var name in players) {
        var checkingPlayer = players[name];
        var checkingPlayerMouse = gameMouse[name];
        if (checkingPlayer.spectator == 1) {
            checkingPlayer.x += checkingPlayer.specspeedx;
            checkingPlayer.y += checkingPlayer.specspeedy;
            checkingPlayer.name = "";
            if (checkingPlayer.x <= srconfig.leftBorder) {
                checkingPlayer.specspeedx = 3;
            } else if (checkingPlayer.x >= srconfig.rightBorder) {
                checkingPlayer.specspeedx = -3;
            }
            if (checkingPlayer.y <= srconfig.upBorder) {
                checkingPlayer.specspeedy = 3;
            } else if (checkingPlayer.y >= srconfig.downBorder) {
                checkingPlayer.specspeedy = -3;
            }
        }
        if (checkingPlayer.spectator == 0) {
            if (checkingPlayer.hp <= 0) {
                checkingPlayer.spectator = 1;
                checkingPlayer.hp = 100;
                checkingPlayer.hydrogen = 100;
                checkingPlayer.mass = checkingPlayer.mass / 2;
                setSysmsg("You're dead! Watch your health!", checkingPlayer.clientid);
            } else {
                if (checkingPlayer.abilitycooldown > 0) {
                    checkingPlayer.abilitycooldown -= 1;
                }
                checkingPlayer.mX = checkingPlayerMouse.x;
                checkingPlayer.mY = checkingPlayerMouse.y;
                needSpeedPlr = checkingPlayer.speed || srconfig.movspeed;
                var mouseAngle = Math.atan2(checkingPlayer.mY, checkingPlayer.mX);
                var mouseDist = Math.sqrt(sq(checkingPlayer.mX) + sq(checkingPlayer.mY));
                checkingPlayer.angle = angle(0, 0, checkingPlayer.mX, checkingPlayer.mY);
                speed = Math.min(needSpeedPlr, 0.035 * Math.pow(mouseDist, impImitz));
                if (speed < 0.06 && speed > -0.06) { speed = 0 }
                checkingPlayer.vx = speed * Math.cos(mouseAngle);
                checkingPlayer.vy = speed * Math.sin(mouseAngle);
                checkingPlayer.x += checkingPlayer.vx;
                checkingPlayer.y += checkingPlayer.vy;
                if (checkingPlayer.x <= srconfig.leftBorder || checkingPlayer.x-checkingPlayer.size <= srconfig.leftBorder) {
                    checkingPlayer.x = srconfig.rightBorder - checkingPlayer.size - 25;
                } else if (checkingPlayer.x >= srconfig.rightBorder || checkingPlayer.x+checkingPlayer.size >= srconfig.rightBorder - 25) {
                    checkingPlayer.x = srconfig.leftBorder + checkingPlayer.size + 25;
                }
                if (checkingPlayer.y <= srconfig.upBorder || checkingPlayer.y-checkingPlayer.size <= srconfig.upBorder) {
                    checkingPlayer.y = srconfig.downBorder - checkingPlayer.size - 25;
                } else if (checkingPlayer.y >= srconfig.downBorder || checkingPlayer.y+checkingPlayer.size >= srconfig.downBorder - 25) {
                    checkingPlayer.y = srconfig.upBorder + checkingPlayer.size + 25;
                }
                for (var objid in objects) {
                    objectZ = objects[objid];
                    if (checkingPlayer.y+(checkingPlayer.size-5) + 2 * Math.PI > objectZ.y && checkingPlayer.y-(checkingPlayer.size-5) - 2 * Math.PI < objectZ.y && checkingPlayer.x+(checkingPlayer.size-5) + 2 * Math.PI > objectZ.x && checkingPlayer.x-(checkingPlayer.size-5) - 2 * Math.PI < objectZ.x) {
                        if (objectZ.type == "asteroid") {
                            foodCount--;
                            checkingPlayer.mass += Number(objectZ.mass / 2);
                            checkingPlayer.hydrogen += Number(objectZ.mass / 2);
                            delete objects[objid];
                        }
                    }
                }
                if (checkingPlayer.mass > checkingPlayer.xpToUpgrade) {
                    checkingPlayer.size = checkingPlayer.xpToUpgrade / 5;
                } else {
                    checkingPlayer.size = checkingPlayer.mass / 5;
                }
                if (checkingPlayer.size < 25) {
                    checkingPlayer.size = 15;
                } 
                checkingPlayer.camzoom = 1;
            }
        }
    }
}

function spawnObject(type, count, mass, movz, x, y, owner, time, alpha, speedz, speed, mx, my) {
    count = count == null ? 1 : count;
    for (var i = 0; i < count; i++) {
        var randxx = Math.floor(Math.random() * srconfig.rightBorder - 50) + 50;
        var randyy = Math.floor(Math.random() * srconfig.downBorder - 50) + 50;
        var randz = Math.floor(Math.random() * 3 - 1) + 1;
        var moveornot = Math.random() < 0.7;
        if (moveornot && type == "asteroid" && movz == undefined) {
            var movz = 1;
        } else {
            var movz = 0;
        }
        colorrandx = '#' + (Math.random() * Math.pow(256, 3) | 0).toString(16).padStart(6, '0');
        if (objects[objectsCount + ''] == null) {
            if (typeof mass != "number") {
                return
            }
            objects[objectsCount + ''] = {
                x: x || randxx,
                y: y || randyy,
                mass: Number(mass),
                angle: 0,
                anglez: 0,
                mX: mx || 0,
                mY: my || 0,
                vx: 0,
                canmove: movz || 0,
                vy: 0,
                speed: speed || 0,
                size: Math.floor(mass / 2),
                type: type,
                speedZ: speedz || Number(randz),
                alpha: alpha || 1,
                id: objectsCount + '',
                owner: owner || null,
                time: time || 0
            };
            objectsCount++;
            if (type == "asteroid") {
                foodCount++;
            }
            if (isNaN(mass)) {
                console.log("[i] Object " + objectsCount + " spawned with " + mass + " xp!");
                objectsCount--;
                delete objects[objectsCount + ''];
            }
        } else {
            console.log("[i] Object ID already exist!");
        }
    }
}

function deleteMessage(playerID) {
    if (players[playerID]) {
        players[playerID].message = "";
    }
}

function deleteSysMessage(playerID) {
    if (players[playerID]) {
        players[playerID].sysmsgvis -= 0.01;
        if (players[playerID].sysmsgvis <= 0) {
            players[playerID].sysmsgvis = 0;
            players[playerID].sysmsg = "";
            clearInterval(systimer[playerID]);
        }
    }
}

function delSys(id) {
    systimer[id] = setInterval(deleteSysMessage, 25, id)
}

function setSysmsg(msg, id) {
    if (!players[id]) {
        console.log("No player")
        return
    }
    if (systimer[id]) {
        clearInterval(systimer[id]);
    }
    players[id].sysmsgvis = 1;
    players[id].sysmsg = msg;
    console.log("[SYS] New sys msg to '" + players[id].name + "' is '" + msg + "'.")
    setTimeout(delSys, 6000, id);
}

gameServer.prototype.setMessage = function(id, msg, color) {
    if (!color) {
        color = "white";
    }
    if (!players[id]) {
        console.log(players)
        return
    }
    if (deletetimer[id]) {
        clearTimeout(deletetimer[id]);
    }
    players[id].message = msg;
    players[id].messageColor = color;
    console.log("[CHAT] New message by '" + players[id].name + "' is '" + msg + "'. Color '" + color + "'.")
    deletetimer[id] = setTimeout(deleteMessage, 7000, id);
}

gameServer.prototype.start = function(zip, zport) {
    var myIP = zip;
    var myPort = zport;
    PORT = myPort;
    app.get("/", (req,res)=> res.sendFile(__dirname + "/index.html"))
    app.listen(PORT+1, ()=>console.log("[i] Index started on port " + (PORT+1) + "!"))

    httpServer.listen(PORT, () => console.log("[i] Starting on port " + PORT + "!"))
    wsServer.on("request", request => {
        var connection = request.accept(null, request.origin);
        var clientID = guid();
        if (playersCount + 1 > srconfig.maxConnections) {
            var data = {
                "method": "disconnect",
                "reason": "Server is full."
            }
            connection.send(JSON.stringify(data))
            connection.close();
            console.log("[i] Rejected connect from " + connection.remoteAddress + ". Reason: Server is full.")
            return;
        }
        connection.on("close", e => {
            console.log("[i] Disconnected - '" + connection.remoteAddress + "'. Nick - " + players[clientID].name + ".");
            playersCount -= 1;
            clearInterval(sendTimer[clientID]);
            clearInterval(animTimer[clientID]);
            clearInterval(hydroloss[clientID]);
            clearInterval(reviveHP[clientID]);
            delete players[clientID];
            delete setMov[clientID];
            delete mouseMid[clientID];
            delete sendTimer[clientID];
            delete animTimer[clientID];
            delete hydroloss[clientID];
            delete reviveHP[clientID];
        })
        connection.on("message", message => {
            var response = JSON.parse(message.utf8Data);
            if (response.method === "nick"){
                if (players[response.clientID]) {
                    if (response.nick == "") {
                        players[clientID].name = srconfig.uName;
                    } else {
                        players[clientID].name = response.nick;
                    }
                    console.log("[i] Set nick \"" + response.nick + "\" for " + clientID)
                }
            } else if (response.method === "movement"){
                if (players[response.clientID]) {
                    gameData[response.clientID] = {
                        "method": "gameData",
                        "players": players,
                        "downBorder": srconfig.downBorder,
                        "rightBorder": srconfig.rightBorder,
                        "objects": objects,
                        "playersCount": playersCount,
                        "top": topx
                    }
                    connection.send(JSON.stringify(gameData))
                    mouseMid[response.clientID].x = response.cWidth / 2;
                    mouseMid[response.clientID].y = response.cHeight / 2;
                    gameMouse[response.clientID].x = (response.curx - (mouseMid[response.clientID].x - 2 * players[response.clientID].camzoom)) / players[response.clientID].camzoom;
                    gameMouse[response.clientID].y = (response.cury - (mouseMid[response.clientID].y - 2 * players[response.clientID].camzoom)) / players[response.clientID].camzoom;
                }
            } else if (response.method === "enter") {
                if (players[response.clientID]) {
                    players[response.clientID].spectator = 0;
                    var randxx = Math.floor(Math.random() * srconfig.rightBorder - 50) + 50;
                    var randyy = Math.floor(Math.random() * srconfig.downBorder - 50) + 50;
                    players[response.clientID].x = randxx;
                    players[response.clientID].y = randyy;
                    setSysmsg(stars[players[response.clientID].type].desc, response.clientID)
                }
            } else if (response.method === "clicked") {
                if (!players[response.clientID]) {
                    return
                }
            } else if (response.method === "chat") {
                if (players[response.clientID]) {
                    //chat
                }
            } else {
                console.log("[i] Unknown packet received: \n-------------\n" + response.method + "\n-------------")
            }
        })

        console.log("[i] Connected - '" + connection.remoteAddress + "', ID - " + clientID);

        var payLoad = {
            "method": "connect",
            "clientID": clientID
        }

        mouseMid[clientID] = {
            x: 0,
            y: 0
        }

        gameMouse[clientID] = {
            x: 0,
            y: 0
        }

        var gameData = {
            "method": "gameData",
            "players": players,
            "downBorder": srconfig.downBorder,
            "rightBorder": srconfig.rightBorder,
            "objects": objects,
            "playersCount": playersCount,
            "top": topx
        }

        connection.send(JSON.stringify(payLoad))

        var specChance = Math.floor(Math.random() * 1 - 0) + 0;
        var specx = specChance == 0 ? -3 : 3;
        var specy = specChance == 0 ? -3 : 3;
        colorrandx = '#' + (Math.random() * Math.pow(256, 3) | 0).toString(16).padStart(6, '0');
        playersCount++;
        players[clientID] = {
            x: Math.floor(Math.random() * srconfig.rightBorder - 50) + 50,
            y: Math.floor(Math.random() * srconfig.rightBorder - 50) + 50,
            mass: srconfig.startPlayerMass,
            size: 1,
            color: colorrandx,
            type: "type",
            abilitycooldown: 400,
            numtype: 1,
            name: srconfig.uName,
            clientid: clientID,
            id: playersCount + '',
            xpToUpgrade: 30,
            angle: 0,
            sysmsg: "",
            trace: 0,
            camzoom: 1,
            hp: 40,
            maxhp: 40,
            speed: 0,
            mX: 0,
            animsize: 5,
            animspeed: 1,
            sysmsgvis: 0.5,
            mY: 0,
            hydrogen: 100,
            maxhydrogen: 100,
            vx: 0,
            vy: 0,
            message: "",
            camview: 750,
            spectator: 1,
            specspeedx: specx,
            specspeedy: specy,
            messageColor: "white"
        };

        function changeAnim() {
            if (!players[clientID] || players[clientID].spectator == 1) {
                return
            }
            if (players[clientID].animsize > 10) {
                players[clientID].animspeed = -0.5;
            } else if (players[clientID].animsize < 1) {
                players[clientID].animspeed = 0.5;
            }
            players[clientID].animsize += players[clientID].animspeed;
        }

        function lossHydro() {
            if (!players[clientID] || players[clientID].spectator == 1) {
                return
            }
            if (players[clientID].hydrogen < 0) {
                players[clientID].hp -= 5;
                return
            }
            players[clientID].hydrogen -= 2.5;
        }

        function HPRevive() {
            if (!players[clientID] || players[clientID].spectator == 1) {
                return
            }
            if (players[clientID].hp > players[clientID].maxhp) {
                return
            }
            if (players[clientID].hydrogen > 0) {
                players[clientID].hp += 5;
                return
            }
        }

        function drawz() {
            if (!players[clientID].spectator) {
                return
            }
            connection.send(JSON.stringify(gameData))
        }

        animTimer[clientID] = setInterval(changeAnim, 100);
        sendTimer[clientID] = setInterval(drawz, 1e3 / 60);
        hydroloss[clientID] = setInterval(lossHydro, 3000);
        reviveHP[clientID] = setInterval(HPRevive, 1500);
    })

    for (var i = 0; i < srconfig.foodMCount; i++) {
        var randXP = Number(Math.random() * (5 - 2) + 2);
        spawnObject('asteroid', 1, Number(randXP));
    }

    updateTimer = setInterval(updateGame, 13);

    spawnFood = setInterval(function () {
        if (foodCount < srconfig.foodMCount) {
            spawnFoodCount = srconfig.foodMCount - foodCount;
            spawnObject('asteroid', spawnFoodCount, srconfig.asteroidMass);
        }
    }, 10000)
}

Ответы (0 шт):