So I want to send monster damage the same for all users that connects to the game. The problem at the moment I believe that my code is in io.on('connection', function (socket) { and if there are two users joined at different times the damage will be send two times for both users and it will be different like 53, 24. I need to achieve that no matter when player joins the game the damage should be sent only one time and it has to be the same for all players. Right now I'm sending it every 5 seconds from the server. What I'm thinking to do is storing timestamp with damage in database and send it to all users. Or maybe I can achieve that with the code that I have at the moment ?
io.on('connection', function (socket) {
setInterval(() => {
monsterDamage = Math.floor(Math.random() * monsters[defeats.defeats].damageB) + monsters[defeats.defeats].damageA;
User.findOne({username: username, ingame: true}).then((user) => {
if(!user) {
return console.log('User not found');
}
user.hp -= monsterDamage;
if(user.hp < 0 || user.hp === 0) {
user.hp = 0;
user.ingame = false;
console.log('You LOST');
}
io.emit('monster-attack', {
damage: monsterDamage
});
console.log(monsterDamage);
user.save();
});
}, 5000);
});
Keep in mind that this code is in io.on('connection', function (socket) {. So I believe if theres two players, two connections two damages has been sent or I'm wrong ?
This is a design problem. You have some users and they may connect to your server and you want to inform connected users in something happens. First take setInterval out of io.on('connection'.
In io.on('connection' keep track of users for example in an array (remember to remove them when the connection is closed). Now you have an updated list of all connected users inside your global array.
In setInterval part send message to members of that array.
Code sample:
const users = []
// io defined
setInterval(() => {
// iterate users array and notify them of event
})
io.on('connection', (socket) {
// update users array for example: users.push(socket)
})
io.on('disconnect', (socket) {
// update users array. Remove disconnected user from array.
})
Related
I want to create a live order page where clients can see the status of their order.
For that reason I want to run a function every 10 seconds that checks the SQL database if the order is ready.
function checkOrder(socket, userid, checkinterval) {
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * FROM orders WHERE user = ' + userid + ' ORDER BY timestamp DESC', function(err, rows) {
var alldone = false;
for (var i = 0; i < rows.length; i++) {
if (rows[i]['status'] == 'completed') {
alldone = true;
} else {
alldone = false;
break;
}
}
socket.emit('order-update', rows);
connection.release();
if (alldone) {
console.log('all done');
socket.emit('execute', '$("#orderstatus").html(\'Done\');');
clearInterval(checkinterval);
}
});
});
}
var express = require('express');
var app = express();
var app = express();
var options = {
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('cert.pem'),
ca: fs.readFileSync("chain.pem")
};
var server = require('https').createServer(options, app);
var io = require('socket.io')(server);
var port = 443;
server.listen(port, function() {
console.log('Server listening at port %d', port);
});
io.on('connection', function(socket) {
socket.on('trackorder', function(userid) {
var checkinterval = setInterval(function() {
checkOrder(socket, userid, checkinterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
Now I'm having issues on stopping the function if either the job is completed or the client disconnects.
How could I achieve that? I suppose the clearInterval() would work inside the function since it is passed but there is an issue with the on disconnect event handler. Either checkinterval is undefined or if I define it globally it stops the wrong function.
How can this be done properly?
Your checkInterval variable is out of scope when the disconnect event comes. You need to move its definition up a level.
io.on('connection', function(socket) {
// checkInterval variable is declared at this scope so all event handlers can access it
var checkInterval;
socket.on('trackorder', function(userid) {
// make sure we never overwrite a checkInterval that is running
clearInterval(checkInterval);
checkInterval = setInterval(function() {
checkOrder(socket, userid, checkInterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
In addition:
I added a guard against overwriting the checkInterval variable if you ever get the trackorder event more than once for the same client.
You mispelled checkinterval in one place.
As others have said, polling your database on behalf of every single client is a BAD design and will not scale. You need to either use database triggers (so it will tell you when something interesting changed) or have your own code that makes relevant changes to the database trigger a change. Do not poll on behalf of every single client.
You have no error handling in either pool.getConnection() or connection.query().
Instead of that complicated setInterval stuff, just add a small IIFE that calls itself if the result isnt there yet. Some pseudocode:
function checkOrder(socket, userid){
//a variable pointing to the running timer
var timer;
//on error clear
socket.on("disconnect", ()=>clearTimout(timer));
//a small IIFE
(function retry(){
pool.getConnection(function(err, connection) {
//parse & notice socket
if (!alldone) //retry
timer = setTimeout(retry, 1000);
});
})();
}
I would say you're using a bad approach. You should go for push rather than pull.
What I mean is, emit the event when status of order changes. Don't put the burden on your database to hit it frequently for no reason.
On successful change of status, emit the event order_status_update with order id and what is the new status
socket.emit('order_status_update', {order_id: 57, status: 'In Process'});
This way you don't need any kind of loop or setinterval etc. No worries even if client is connected or not, its sockat.io business to take care of it. You will just raise the event.
I am trying to find a way to stop setInterval(test,5000) function from running if no user is connected to the socket stop setInterval function as it causes lot of waste of resources.
I found the method but I dont know how to put it
io.engine.clientsCount //this will tell number of users connected but only inside socket.on function.
below is my code:
var connectCounter = 0;
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
function test()
{
httpk.get("api-url", function(res) {
var body = '';
res.on('data', function(data){
body += data;
});
res.on('end', function() {
var parsed = JSON.parse(body);
console.log(parsed.johndoe.example1);
nsp.emit('live-quote', parseFloat(parsed.johndoe.example1);
});
});
}
setInterval(test,5000);
nsp.on('connection', function(socket){
//Make a http call
connectCounter++;
nsp.emit('live-users',connectCounter);
console.log('1 user connected, Total Joined: '+connectCounter);
socket.on('disconnect', function(){
connectCounter--;
nsp.emit('live-users',connectCounter);
console.log('1 user disconnected, Total Left: '+connectCounter);
});
console.log("total clients: "+io.engine.clientsCount);
if(io.engine.clientsCount >= 1)
{
//do something
//if I put setInterval here it will cause problems, that is for each connection it will run setInterval causing lot of http get request
// meaning, if 100 users then 100 get request in 5 seconds (depending on setInterval time).
}
});
How do I best stop execution of SetInterval(test,5000) if no users connected?
To stop setInterval, use clearInterval
Refer to
Stop setInterval call in JavaScript
Do you also need help to stop/resume setInterval if you have no/1+ connected user?
If so, you can try managing a reference to your setInterval at the same time as your counter:
var connectCounter = 0;
var interval = undefined;
then:
connectCounter++;
if (interval === undefined) interval = setInterval(test,5000);
then:
connectCounter--;
if (connectCounter <= 0 && interval !== undefined) interval = clearInterval(interval);
So I'm working on a text based game where users send post requests to the server written in node.js. However, I need to match 2 players together by letting the first player to wait for the 2nd player before submitting a response. The problem right now is that after I call the post request once, I cannot send another post request for any response. It seems like the first thread is blocking any more future requests. What should in terms of using callbacks and async. I do not want to use sockets if possible as that will limit the languages that players can code in.
var newGame=0;
//joins new game (username,password)
app.post('/game/join', function(req, res) {
var username = req.body.username;
var password = req.body.password;
if(newGame==1){
res.send('Game Started');
newGame=0;
}
else{
newGame=1;
wait(username, function(){
res.send('Game Started'+username);
});
}
});
function wait(username){
while(newGame==1){
console.log(username + newGame);
}
}
So you may have found that once your wait functions starts, nothing else happens. This is because an infinite while loop with a blocking call (console.log) prevents the event loop from doing anything else until it is 'done'. You can include waiting with setTimeout, setInterval, and clearInterval to run a function at some point in the future, run a function on an interval, and stop running a function on an interval, respectively.
var newGame = 0;
app.post('/game/join', function(req, res) {
var username = req.body.username;
var password = req.body.password;
if (newGame === 1) {
res.send('Game started' + username);
} else {
var wait = setInterval(function() {
if (newGame === 1){
clearInterval(wait);
res.send('Game Started');
newGame = 0;
}
}, 5000); // retry every 5 seconds
}
}
});
Node.js is single threaded so your code is preventing your server from processing additional requests. Blocking the event loop is def something you don't want to be doing.
I was going to request using something like Socket.IO or WebRTC but I saw your constraint. I'm not really sure what you meant by "limit the language players can code in"
For me, its not clear that how you pick 2 users to start a match. If it is like, one player is connected and another one is joining and then the match is started between them, I would do something like this.
var waitingResponses = [];
//joins new game (username,password)
app.post('/game/join', function(req, res) {
var username = req.body.username;
var password = req.body.password;
if(pendingJoinRequests.length) {
var waitingPlayerResponse = waitingResponses.shift();
waitingPlayerResponse.send('Game Started'+username);
res.send('Game Started');
}
else {
waitingResponses.push(res);
}
});
I am looking at some node.js code that does push notification on a MySQL database.
http://www.gianlucaguarini.com/blog/push-notification-server-streaming-on-a-mysql-database/
There is a polling function.
var pollingLoop = function() {
// Doing the database query
var query = connection.query('SELECT * FROM users'),
users = []; // this array will contain the result of our db query
// setting the query listeners
query
.on('error', function(err) {
// Handle error, and 'end' event will be emitted after this as well
console.log(err);
updateSockets(err);
})
.on('result', function(user) {
// it fills our array looping on each user row inside the db
users.push(user);
})
.on('end', function() {
// loop on itself only if there are sockets still connected
if (connectionsArray.length) {
pollingTimer = setTimeout(pollingLoop, POLLING_INTERVAL);
updateSockets({
users: users
});
} else {
console.log('The server timer was stopped because there are no more socket connections on the app')
}
});
};
The particular code segment above that puzzles me is this;
updateSockets({
users: users
});
Why is the argument users: users?
The code for updateSockets() is here;
var updateSockets = function(data) {
// adding the time of the last update
data.time = new Date();
console.log('Pushing new data to the clients connected ( connections amount = %s ) - %s', connectionsArray.length , data.time);
// sending new data to all the sockets connected
connectionsArray.forEach(function(tmpSocket) {
tmpSocket.volatile.emit('notification', data);
});
};
{
users : users
}
This code is just a plain objet. The first users is the name of the object property and the second users is just a variable.
You can write like this if you want :
var myUsers = users;
updateSockets({
users: myUsers
});
It's an additional information stored in data
When this code performs emit(data), it sends the packet with parameters user and time (added in updateSockets)
It's the message you want to send
I'm using setTimeout in Node.js and it seems to behave differently from client-side setTimeout in that it returns an object instead of a number. I want to store this in redis, but since redis only stores strings, I need to convert the object to a string. However, using JSON.stringify throws a circular reference error. How can I store this object in redis if I want to be able to fetch it from redis and call clearTimeout on it?
You cannot store the object in Redis. The setTimeout method returns a Handler (object reference).
One idea would be to create your own associative array in memory, and store the index in Redis. For example:
var nextTimerIndex = 0;
var timerMap = {};
var timer = setTimeout(function(timerIndex) {
console.log('Ding!');
// Free timer reference!
delete timerMap[timerIndex];
}, 5 * 1000, nextTimerIndex);
// Store index in Redis...
// Then, store the timer object for later reference
timerMap[nextTimerIndex++] = timer;
// ...
// To clear the timeout
clearTimeout(timerMap[myTimerIndex]);
I was attempting to do the same thing as the OP. My solution was to set the timeout with a conditional check on a new key inside the timeout in my disconnect handler:
redis.hset("userDisconnecting:" + userId, "disconnect", 1);
setTimeout(function() {
redis.hget("userDisconnecting:" + userId, "disconnect",
function(err, result) {
if (result.toString() === "1") {
//do stuff, like notify other clients of the disconnect.
}
});
}, 10000);
Then, when the client connects again, I set that key to 0, so the stuff that needs to fire on true disconnect doesn't happen:
redis.hset("userDisconnecting:" + userId, "disconnect", 0);
The timeouts themselves aren't persistent across server restarts, but you could solve that by kicking off a sweeper method on startup. Connected clients would come back "online" pretty quickly.
In the newer versions of node, you can use the Id of the Timeout object instead of the object itself to end the loop.
redisClient.set('time', JSON.stringify(10))
let timeoutObject = setInterval(async function(){
let time = await JSON.parse(redisClient.get('time'))
if(time === 0){
let intervalId = await JSON.parse(redisClient.get('intervalId'))
clearInterval(intervalId)
}
time -= 1
redisClient.set('time', JSON.stringify(time))
}, 1000)
let intervalId = timeoutObject[Symbol.toPrimitive]()
redisClient.set('intervalId', JSON.stringify(intervalId))
This is just an example of a timer built with setInterval and redis combined. As you can see, you can grab the Id of the Timeout Object and store that to end setInterval's execution instead of trying to store the whole object.
Here is the link to the node docs: https://nodejs.org/api/timers.html#timers_timeout_symbol_toprimitive
This code is used when the timeouts need not be persistent across server restarts
var timeouts = {};
app.get('/', function (req, res) {
var index = timeouts.length;
timeouts[index] = setTimeout(console.log, 1000000, req.user.name);
redis.set('timeout:' + req.user.name, index, function (err, reply) {
res.end();
});
});
app.get('/clear', function (req, res) {
redis.get('timeout:' + req.user.name, function (err, index) {
clearTimeout(timeouts[index]);
delete timeouts[index];
redis.delete('timeout:' + req.user.name);
res.end();
});
});
If you need timeouts to be persistent across server restarts, then you might need to store _idleStart and _idleTimeout values for every timer in the redis, and load them up everytime you server restarts
app.get('/', function (req, res) {
var timeout = setTimeout(console.log, 1000000, req.user.name);
var time = timeout._idleStart.getTime() + timeout._idleTimeout;
redis.set('timeout:' + req.user.name, time, function (err, reply) {
res.end();
});
});
app.get('/clear', function (req, res) {
redis.delete('timeout:' + req.user.name);
res.end();
});
// Load timeouts on server start
// *I know this is not the correct redis command*
// *It's not accurate, only approx*
redis.get('timeout:*', function (err, vals) {
vals.forEach(function (val) {
var time = val - new Date().getTime();
setTimeout(console.log, time, username)
});
});