I am building an online browser game where I used Node.js as the server backend and socket.io to handle all the networking connections. However, for the game backend itself, I actually write it in C++ and used node-addon-api to wrap around that backend. I do this because:
- I think that the C++ game backend will be a little bit faster than a JS game backend. Plus, this is also the language I used in my work.
- I want to leave the possibility of building the server in C++ in the future. Having the backend code in C++ would make the transition more seemless in the future.
The server.js
code currently looks like the following.
// Server code
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000;
app.use(express.static('public'));
const http = require('http').Server(app);
const io = require("socket.io")(http, {
cors: {
origin: "*",
}
});
app.get('/', (req, res) => res.sendFile(__dirname + '/index.html'));
http.listen(PORT, function() {
console.log(`Listening on ${PORT}`)
})
io.on('connection', connected);
// Move data
class MoveData {
constructor(playerId, x, y) {
this.playerId = playerId;
this.x = x;
this.y = y;
}
}
// Backend initialization
const game_wrapper = require('./build/Release/game_wrapper.node')
var gameInstance = new game_wrapper.GameWrapper(4.3);
gameInstance.AddNewPlayer("aloha");
gameInstance.RemovePlayer("aloha");
var playerCreations = [];
var playerDestructions = [];
var playerMoves = []
var objectPositions = {};
function connected(socket) {
socket.on('newPlayer', data => {
console.log("New player coming at id " + socket.id);
playerCreations.push(socket.id);
});
socket.on('disconnect', () => {
playerDestructions.push(socket.id);
delete objectPositions[socket.id];
io.emit('updatePlayers', objectPositions);
});
socket.on('move', moveData => {
playerMoves.push(
new MoveData(socket.id, moveData.x, moveData.y)
);
});
}
// Server loop
var lastTime = Date.now();
function serverLoop() {
// Player update
for (let playerId in playerCreations) {
gameInstance.AddNewPlayer(playerId);
}
playerCreations = [];
for (let playerId in playerDestructions) {
gameInstance.RemovePlayer(playerId);
}
playerDestructions = [];
for (let moveData in playerMoves) {
gameInstance.Move(moveData.playerId, moveData.x, moveData.y);
}
playerMoves = [];
// Game update
currTime = Date.now();
timeDelta = (currTime - lastTime) / 1000;
gameInstance.Update(timeDelta);
for (let playerId in objectPositions) {
let point_array = gameInstance.GetPlayerPosition(playerId);
objectPositions[playerId] = { x: point_array[0], y: point_array[1]};
}
console.log("Before");
io.emit('updatePlayers', objectPositions);
console.log("After");
lastTime = currTime;
}
setInterval(serverLoop, 1000/60);
Here, the game_wrapper
comes from the Node C++ addon. I created a gameInstance
from the library, which creates an instance of the game. New player can join in by connecting to the server, which triggers a creation of a new player in the game backend, identified by the socket ID.
When I called AddNewPlayer
and RemovePlayer
outside, they seem to work fine. However, when I called AddNewPlayer
inside the serverLoop
, the server either crashed immediately, or eventually crashed.
Does anyone have a suggestion of what is going on? I am suspecting that the serverLoop
is actually a callback function that spawns a new thread. This creates some memory management issue if gameInstance
does not handle memory properly, causing crashes. However, I would love to hear from node.js
expert or socket.io
expert here. Happy to provide further code to help debug this issue. Thanks!
I tried several things such as constructing the function the different way, or changing the way the function works on the backend. But I still have the same issues. It seems that Node Addon C++ doesn't like any object where memory might be dynamically allocated.