Home » Nodejs » How do I emulate “sleep” in NodeJS?

How do I emulate “sleep” in NodeJS?

Posted by: admin November 30, 2017 Leave a comment

Questions:

I am building a browser game with a mini map of the surroundings of the player. I need to track where other players are and update this mini map whenever someone moves. I am implementing this in NodeJS and CouchDB. My design goes as follows:

I have a database for all changing game data. In this database, I have a document that contains the basic map data in a two-dimensional array, with each square on the grid being represented by an element in this array. Since I could have tons of different users all moving about the map simultaneously, I needed some way to ensure that I don’t get a read as someone else is writing to it (I could get faulty information if a ton of users are reading and writing to a single document). I decided to have separate documents in this database which represent the individual squares, and each document has the players that are “on” that square and some other data associated with that square. Essentially, the map document is used only as a lookup table for the square document. This enables me to change a single document without having to rewrite the entire map document, solving the problem of simultaneous reads and writes.

My problem, though, is that I need to get a mini map for the user to use as a reference. This mini map will have the documents of the surrounding squares. Since I need all of this at the same time, I thought I would just grab all 9 squares from the database and return it in a single ajax response. My problem though is with reducing the amount of blocking IO that I do. Right now, I have a nested loop that requests the squares I need from the database. Here’s a look at my code (position and map are passed in):

var miniMap = new Array();
var keyMap = new Object();
var numDone = 0;
for(i = position.y - 1, y = 0; i < position.y + 1 && i < map.length; i++, y++){
    miniMap[i] = new Array();
    for(v = position.x - 1, x = 0; v < position.x + 1 && v < map.length; v++, x++){
        keyMap[map[i][v].id] = {'x': x, 'y': y};
        gameDB.getDoc(map[i][v].id, function(er, doc){
            var tPos = keyMap[doc._id];
            miniMap[tPos.y][tPos.x] = doc;
            numDone++;
        });
    }
}

My problem, though, is that getDoc is non-blocking, so I don’t know when it will set the square data to miniMap. I thought about doing something like this:

while(numDone < 9){
    sleep(10);
}
callback(miniMap);

This will let me wait until CouchDB is finished getting all of my data, but JavaScript doesn’t have a sleep function. The closest I found was setTimeout, but that is also non-blocking and I’ll never be 100% sure that the time I chose for the timeout will be sufficient to allow CouchDB to finish getting my data.

So basically I want to have a condition, test it, then return it to the event stack again if the condition is false. The only solution I thought of was to have a recursive setTimeout callback that would do something like this:

function testCallback(data, condition, callback){
    if(data < condition){
        setTimeout(function(){
            testCallback(data, condition, callback);
        }, 10);
    }else{
        callback(data);
    }
}

This seems pretty terrible… Is there a better way that I could be doing this? Should I give up this approach and force there to be multiple ajax calls to get updated data? Should I go for a blocking approach?

Answers:

Just use another callback:

function getMiniMap(....., callback) { // supply the callback that sends the data
    var miniMap = new Array();
    var keyMap = new Object();
    var numDone = 0;
    ...
                numDone++;
                if (numDone === 9) { // as soon as everything has been collected...
                    callback(miniMap); // ... call the send callback and supply it the miniMap
                }
            });
        }
    }
}

Oh, and your Database Model is really bad, I don’t know much about your game, but unless this needs to run on multiple Node processes, it would be better to keep the Map etc. in a JS Array and only write to the DB when the server needs to save state.

Oh and you can also replace your keyMap with an anonymous function call:

    (function(x, y) {
        gameDB.getDoc(map[i][v].id, function(er, doc){
            var tPos = keyMap[doc._id];
            miniMap[tPos.y][tPos.x] = doc;
            numDone++;
        });
    })(x, y); // pass in a copy of x and y

Questions:
Answers:

Given we are talking about event-ed system, I think it would be cleaner if you can just emit a load done event when numDone==9 and catch it from your main and proceed from there.

You can put the entire map in redis even the game needs to be run across multiple nodes.