I'm developing a game where my backend is written in NodeJS and I use socket.io for realtime communication.
One of the features of my game is matchmaking. Basically, if more than two players are on matchmaking period, my application will create a game room (special socket.io room) for them and start the game.
There are two ways to do this:
Create a setInterval on NodeJS. In the interval, check playersInMatchmaking array. If there are more than 2 players in the array, create the game room. I will loop as long as the server is online.
Instead of relying on setInterval, check playersInMatchmaking array each time I receive a call to socket.on("matchmaking start") or socket.on("matchmaking stop")
Is there any benefits of using certain approach? setInterval sounds easier as I can decouple matchmaking algorithm from socket logic, however it will be less performant as matchmaking algorithm will run in a loop as opposed to socket events.
What do you think? Do you have any other ideas that would work better?
You will maximize the utilisation of socket.io and real-time communication by using events. If there is no other reason other than decoupling the algorithm, you should use events. There are no reason to run the loop over and over again if there are currently no users/players participating or searching for the match.
If the interval is not small enough (for example 5 seconds), I would also consider what happens when Player1 is put into queue, Player2 also comes in the queue, and in between 2 intervals Player1 cancels his search for opponent since he's tired of waiting. So main benefit of event triggered systems other than the one you mentioned (performance) is that there is no real delay (except the network, code looping, jitter, ..) and things happen immediately
Related
I'm trying to make a server for a role-based browser game, so what I did so far:
Once 2 players join the server, a room is created, then it starts its own timeout of let's say 10 seconds, once the timeout is done, it changes its state and reruns the timeout.
I'm afraid if I have many rooms that may impact the performance, so an idea came to my mind which is, create a setInterval that tick every second, and loop through all rooms to call an update, inside the room once update called, it will check last time it updated its state vs current time, if 10 seconds passed, it updates to the new start.
I'm afraid that with setInterval every second may be worse than the first idea, or the opposite?
It would be far more efficient to let each room have its own timer than to have one frequent setInterval() that has to loop through all the rooms.
Timers in nodejs are super efficient and can easily scale to zillions of timers. They are stored in a sorted linked list and only the head of the linked list is compared vs the current time in the event loop. All the rest of the timers in the linked list have no regular cost - they just sit in the linked list until they are finally at the head of the list. There is slightly more cost to adding a new sorted timer when the linked list is long, but that's probably better than having a single interval that spends a lot of time looping through rooms that haven't reached their timeout. The nodejs timer system is just a more efficient way to do that.
I am working on a multiplayer game. Each client have a character that moves in a shared environment.
I use socket.io to create rooms and peer.js to create a peer-to-peer connection between clients.
What I am trying to do is to enable each client to update the positions of the characters of the other players in his map.
For that, each client should have the state of the keyboard cursors (arrow keys) of the other players so that he can move their corresponding characters with a walking animation.
P2P: I am thinking of creating duplex streams between the clients so that each client will have the state of the keyboard cursors of the other players, so that he can move their characters with the appropriate animation...
SOCKETS: I can also pass the information via the server using sockets, but i will have to send an update of the cursors state 60 times per second since the game is on 60 fps, which makes a lot of socket messages. I am not sure this is the most efficient way to handle it
What is the most efficient way to keep everybody updated about the state of the other players ? Any suggestion will be appreciated. Thanks.
Actually your game is likely a little demo of a MMORPG game or something like CS/CSGO.
For such a game, we always have a loop in main process (work process) with a frequency like several frames a second(say 20 frame). In every frame, the client will process the packets received from others and the options form the pleyer.
If here is 20 frames, that means every fram can't be more than 50ms, so it will cause some delay if it's in WLAN and some packets drop happend.
If you want to use P2P to synchronized players action, here is a problem: when player number booms, the comlicated of the connection boos too. What's more, you need a reliable connection protocol, that means you need to know how to use something like QUIC or write a reliable UDP by yourself.
So I think the most effcient way may keep use C/S model insetead of P2P only if your game will use in LAN and there is quite a few players.
For example, let's say I have a random game in which I have 500 independent objects and 10 players.
Independent object is an object that moves in a specific direction per update regardless of what players do (there is no need for players to come into contact with these objects).
Now if a player is shooting (lets say) a bullet, it is easier because it belongs to a specific player therefore it's easier to avoid in game lag. Lets look at something simpler, though, for example a player try to update their position. The typical thing I would do on client & server side would be this :
client side : update the coords of the player + send a message to the server as socket X
server side : receives the message from socket X, updates the coords of the player on the server side +
sends a message with the coords of that same player to all other sockets
When you do the communication like this, everyone will receive the new coords of the player and there will be little to no lag. (It is also sufficient for objects like bullets, because they are created upon firing a player event)
How do you handle 500+ independent objects that move in random directions with random speed all across the map and update them for all players efficiently? (Be aware that their velocity and speed can be changed upon contact with a player). What I've tried so far:
1) Put all of the movement + collission logic on the server side &
notifying all clients with a setTimeout loop & io.emit -
Result : causes massive lag even when you have only 500+ objects and 4 connected players. All of the players receive the server's response way too slow
2) Put all of the movement + collission logic on the client side & notifying the server about every object' position-
Result : To be honest, couldn't encounter much lag, but I am not sure if this is the correct idea as every time an object moves, I am literally sending a message to the server from each client to update that same object (server is getting notified N[number of connected clients] amount of times about that same object). Handling this entirely on the client side is also a bad idea because when a player randomly switches tabs [goes inactive], no more javascript will be executed in that players' browser and this whole logic will break
I've also noticed that games like agar.io, slither.io, diep.io, etc, all of them do not really have hundreds of objects that move in various directions. In agar.io and slither you mainly have static objects (food) and players, in diep.io there are dynamical objects, but none of them move at very high speeds. How do people achieve this? Is there any smart way to achieve this with minimal lag?
Thanks in advance
Convert your user interactions to enumerated actions and forward those. Player A presses the left arrow which is interpreted by the client as "MOVE_LEFT" with possible additional attributes (how much, angle, whatever) as well as a timestamp indicating when this action took place from Player A's perspective.
The server receives this and validates it as a possible action and forwards it to all the clients.
Each client then interprets the action themselves and updates their own simulation with respect to Player A's action.
Don't send the entire game state to every client every tick, that's too bloated. The other side is to be able to handle late or missing actions. One way of doing that is rollback where you keep multiple sets of state and then keep the game simulation going until a missinterpretation (late/missing packet) is found. Revert to the "right" state and replay all the messages since in order to get state to correct. This is the idea behind GGPO.
I suggest also reading every article related to networking that Gaffer on Games goes into, especially What Every Programmer Needs To Know About Game Networking. They're very good articles.
I'm developing an multiplayer turn based game (e.g chess), should support a lot of players (that's the idea). My question is about a service i'm developing, it's the pairing system, the responsible of pairing 2 players to start a room and start playing.
So, this is the pairing service:
matchPlayers() {
if (this.players.length >= 2) {
let player1 = this.players.shift();
let player2 = this.players.shift();
if (player1 !== undefined && player2 !== undefined) {
player1.getSocket().emit('opponent_found');
player2.getSocket().emit('opponent_found');
return this.createMatchInDataBaseApiRequest(player1, player2)
.then(function (data) {
let room = new RoomClass(data.room_id, player1, player2);
player1.setRoom(room);
player2.setRoom(room);
return room;
});
}
}
return false;
}
At the entrypoint of the server, each new socket connection I push it to an array "PlayersPool" this array is for players waiting to get matched up.
Right now my approach is to pair users when there are available, (FIFO - first in first out).
The problems (and question) I see with this pairing system is:
This depends on new users, this gets executed each time a new user is connected, The flow is: A user connects, get's added to the pool, and check if there are users waiting for being paired, if yes a room is created and they can play, if not he gets added to the waiting pool; Until a new user connects and the code get's executed and so on...
What would happen if in some weird case (not sure if this could happen) 2 players gets added to the waiting pool at the same exact time, this service would find the pool empty and would not create a room: To solve this maybe having another service running always and checking the pool? what would be the best approach? Could this even happen? in which scenario?
Thanks for the help.
I'm guessing this particular code snippet is on the server? If so, assuming there is only one server, then there is no "race condition": node.js is single-threaded, as IceMetalPunk mentioned, so if you're running this function every time you add a player to this.players, you should be fine.
There are other reasons to be examining the player pool periodically, though: players you've added to the pool may have gotten disconnected (due to timeout or closing the browser), so you should remove them; you also might want to handle situations where players have been waiting a long time - after X seconds, should you be updating the player on progress, calculating an estimated wait time for them, perhaps spawning an AI player for them to interact with while they wait, etc.
You can run into a "race condition", it's explained here in this package which provides you a Locking mechanism.
https://www.npmjs.com/package/async-lock
That package will be useful, only if you run node.js in a single process, meaning you are not having multiple servers, or having node cluster running multiple processes.
In that case, you will have to implement a distributed locking mechanism which is one of the most complex things in distributed computing, but today you can use the npm package for Redlock algorithm, set 3 redis servers and go.
Too much overhead for a game without players.
Node.js is not single threaded, here is the explanation of one of the creators.
Morning Keynote- Everything You Need to Know About Node.js Event Loop - Bert Belder, IBM
https://www.youtube.com/watch?v=PNa9OMajw9w
Conclusion, keep it simple, run it in a single node process and use the "async-lock" package.
If your server grows to become a MMO, you will need to read about distributed computing:
How to do distributed locking:
https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
Book on data intensive apps
http://dataintensive.net/
I have a multiplayer game lobby up where users can create private chatrooms and start private games. Each user has a health bar in the game that is suppose to slowly regenerate x points per second.
I suppose I would need to start server side game loop at the beginning of each game, which is something like that:
setInterval(update('gameID'),1000);
Where update('gameID') increment the health variables for all players in a particular game where 1000 ms = 1 second.
Question: Am I right to assume this is asynchronous? I might have 50 separate games going on, and 50 of these running. The main process is not going to be blocked right?
It's asynchronous, but you don't need 50 timers in the case you describe.
You can use a single timer to regenerate players in active games. If you're also pushing health data this is going to be pretty inefficient.
You can do something like player.attackedTime = (new Date).getTime() and calculate regeneration on each attack like player.health += x_points * ((new Date).getTime() - player.attackedTime) / 1000, but you will have to do predictive regeneration on the client.
It is asynchronous. But doing this that way may kill your server.
I advice making these intervals passive, i.e. hold the start of the game in memory and make client ping for data. When client pings server checks current date and compares it to the stored one (and updates the stored one at the end of request). It can evaluate current health from that.
This solution should scale better.
setInterval is certainly asynchronous. Most functions that take a callback are asynchronous. If you're ever in doubt, you can check the documentation or the source code.