I seem to be having some unexpected results with a framerate counter in javascript. Up until recently the counter has been fine and I have been running my little js app at 30fps.
It uses setTimeout() (with a time adjustment to counter the system 'falling behind').
window.requestAnimFrame = (function()
{
return function (callback) {
time += FPS;
Heartbeat._eTime = (new Date().getTime() - Heartbeat._start);
var diff = Heartbeat._eTime - time;
Heartbeat._delta = FPS - diff;
Heartbeat._deltaS = Heartbeat._delta / 1000;
window.setTimeout(callback, FPS - diff);
};
})();
Heartbeat is merely an object that contains the frame rate info.
*Here is my problem: *
_MainLoopHandler: function () {
timer = new Date().getTime();
counter = timer;
while (this._messages.length > 0 && (counter - timer) < 5)
{
// process messages from _messages array
}
counter = new Date().getTime();
// THE ABOVE IS HAPPY AT 30 FPS
while ((counter - timer) < 6) {
1 + 1;
}
// THE ABOVE WHILE IS VERY UNHAPPY :(
}
So the above code block is the function that is called from setTimeout every 33.33 milliseconds (30 fps). if I take the bottom while loop out, the FPS counter will sit happily at 30fps. However, if I leave it in, the FPS counter goes crazy. it goes up to the 200FPS 300FPS then suddenly goes -200FPS -10FPS 0.01FPS. Its completely off the wall. The while loop will only run maybe 10 times per "frame".
Note also, the hard-coded values 5 and 6 are simply a check to see if 5 or 6 milliseconds have passed while processing the loops (for load balance).
Is this simply javascript being unable to handle the amount of info or has anyone else had a similar problem.
Thanks!
I don't really know what's going on, but I think you should use local variables to control your time, constantly reassess counter and process 1 message at a time. Also, I don't really understand that last loop (I've also renamed the variables):
_MainLoopHandler: function () {
var start = new Date().getTime();
var current;
do {
if (this._messages.length === 0) break;
// process 1 message
current = new Date().getTime();
} while (current - start < 5);
}
You can also encapsulate the timing concern in an object (not shown) to streamline the code:
_MainLoopHandler: function () {
var timing = new Timing();
do {
if (this._messages.length === 0) break;
// process 1 message
} while (timing.elapsed() < 5);
}
Related
My app is a game where a user has 30 mins to finish....node backend
Each time a user starts a game then a setInterval function is triggered server side....once 30mins is counted down then I clearInterval.
How do I make sure that each setInterval is unique to the particular user and the setInterval variable is not overwritten each time a new user starts a game? (or all setInterval's are cleared each time I clear).
Seems like I might need to create a unique "interval" variable for each new user that starts game??
Below code is triggered each time a new user starts a game
let secondsLeft = 300000;
let interval = setInterval(() => {
secondsLeft -= 1000;
if (secondsLeft === 0) {
console.log("now exit");
clearInterval(interval);
}
}, 10000);
Thanks!!
We used agenda for a pretty big strategy game backend which offers the benefit of persistence if the node app crashes etc.
We incorporated the user id into the job name and would then schedule the job, along with data to process, to run at a determined time specifying a handler to execute.
The handler would then run the job and perform the relevant tasks.
// create a unique jobname
const jobName = `${player.id}|${constants.events.game.createBuilding}`;
// define a job to run with handler
services.scheduler.define(jobName, checkCreateBuildingComplete);
// schedule it to run and pass the data
services.scheduler.schedule(at.toISOString(), jobName, {
id: id,
instance: instance,
started: when
});
Worked pretty well and offered decent protection against crashes. Maybe worth considering.
First: Concurrent Intervals and Timers are not the best design approach in JS, it is better to use one global timer and a list of objects storing the start, end, userid etc and update these in a loop.
Anyway. To have your interval id bound to a certain scope, you can use a Promise like so:
const createTimer = (duration, userid) => new Promise(res => {
const start = new Date().getTime();
let iid;
(function loop () {
const
now = new Date().getTime(),
delta = now - start
;
//elapsed
if (delta >= duration) {
clearTimeout(iid);
res(userid);
//try again later
} else {
iid = setTimeout(loop, 100)
}
})();
});
This way each timer will run »on its own«. I used setTimeout here since that wont requeue loop before it did everything it had to. It should work with setInterval as well and look like that:
const runTimer = (duration, userid, ontick) => new Promise(res => {
const
start = new Date().getTime(),
iid = setInterval(
() => {
const delta = new Date().getTime() - start;
if (delta < duration) {
//if you want to trigger something each time
ontick(delta, userid);
} else {
clearInterval(iid);
res(userid);
}
}, 500)
;
});
You do not even need a promise, a simple function will do as well, but then you have to build some solution for triggering stuff when the timer is elapsed.
Thanks #Chev and #philipp these are both good answers.
I was also made aware of a technique where you use an array for the setInterval variable.....this would make my code as follows;
let intervals = []
let secondsLeft = 300000;
intervals['i'+userId] = setInterval(() => {
secondsLeft -= 1000;
if (secondsLeft === 0) {
console.log("now exit");
clearInterval(interval);
}
}, 10000);
Does anyone else foresee this working?.
UPDATE 6.56pm PST.....it works!!
In an agenda/calendar app I'm working on, I display a line to indicate the current time. I want to update the position of this line every minute.
If I start a setInterval function when the calendar component did mount or will mount, there is a change that it starts at the 59th second (or just not the 1st second) and the time will always be different from the time that the device is showing (computer, smartphone, ...).
But I would like that both times are matching. So I was wondering if it is possible to start the interval when a new minute starts of if there is another way to get a time update.
EDIT: Current code
componentDidMount() {
setInterval(() => {
this.setState({ currentTime: new Date() })
}, 60 * 1000);
}
You can get fairly close, by calculating the number of seconds until the next minute, and performing a timeout on the difference.
(function showTime(){
console.log('update time');
var time = document.getElementById('time');
var now = new Date();
time.innerHTML = `${now.getHours()}:${now.getMinutes()}`;
setTimeout(showTime, (60 - now.getSeconds()) * 1000);
})();
<div id="time"></div>
Use SetTimeout to call a function that repairs any deviation for each iteration and relaunches with a new call to setTimout.
function repairAndRelaunch (cnt) {
// capture current secs
var secs = (new Date()).getSeconds();
// relaunch with corrected seconds - limit iterations for testing
if (5 > cnt++) setTimeout('repairAndRelaunch(' + cnt + ')', (60-secs)*1000);
// log to observe discrepencies
console.log('for cnt = ' + cnt + ', secs = ' + secs);
};
// test control flow
window.onload = function() {repairAndRelaunch(0);};
I am making an AJAX call in my code. What i want is to hit the AJAX call at 20th second of every minute. This is the AJAX request that i am making.
setInterval(function(){
$.ajax({
url: url,
headers: { 'x-cyclops-ajax': 'yes' },
method: 'POST',
dataType: 'json',
success: function(data) {
var chart = $('#container').highcharts();
var keys = Object.keys(data["histData"]);
$( "#main-div" ).empty();
for( var i=0; i< keys.length; i++) {
chart.series[i].setData(data["histData"][keys[i]]["histFailure"], true);
$('#main-div').append( '<div class="homepage-availability-inner-div"><h1 class="homepage-availability-text"> ' + keys[i] + ': <span class="dashboard-success">' + data["availData"][keys[i]] + ' </span> </h1></div>');
}
chart.xAxis[0].setCategories(data["histKeys"]);
console.log("Data:" + JSON.stringify(data["availData"]));
},
error: function(jqXHR, textStatus, errorThrown) {
console.log("Did not hit the AJAX call");
}
});
}, 5000);
Any help would be appreciated.
If you mean only on the 20th second as in 13:00:20, 13:01:20 , 13:02:20, ...
you would have to do something like this:
// the interval can be set lower depending on the use case, to be more accurate
// Warning a too low interval setting might kill the performance of the browser/client,
// and execute the ajax multiple times, if the milliseconds are not considerate
let interval = 1000;
// the function is called (about) every second,
// so approximately 60 times per minute and executes the ajax call only once.
setInterval(
function(){
let now = new Date();
// should only fire, if it is the 20th Second in the current minute
if(now.getSeconds() === 20){
//ajax call
console.info(now);
}
}, interval
);
The Code check every Second, if it is the 20th Second. The performance might be a bit heavy for the client, doing some many calls, but it works.
Just to think about:
It could be optimized with changing the inertval, after a hit or higher interval length, or using setTimeout instead, and calculating, the next time to call it self.
btw.:
If you want to get the milliseconds also, you would have to put the interval lower and also query the getMilliseconds() function of the now Variable, but this would probably kill the performance of the client.
here is the link to the relevant Reference to the Date function getSeconds
here is a explanation on how/why the timeout/interval is not accurate, but there are also other reasons.
Optional (just4fun):
If you want do less setInterval calls, you could use setTimeout and call the function recursively, the "problem" being, how to tweak the time setting to get close to the 20th seconds without missing it.
Here is a small basic example, to start from:
(Yes the code isn't very optimized, and could be better structured, but I hope it gives a rough idea)
// the 20th Second, when the ajax call should execute
const selectedSecond = 20;
// can be tweaked to hit closer to 20th Second (ms)
let shortInterval = 400;
// depence on the size less calls are made
let safetyBuffer = 2;
// helper Variable, 60 Seconds
let sixtySeconds = 60;
// timeout value which is set dynamic, first time will execute "immediately"
let currentTimeout = 0;
function timeoutHandler(){
// gets current Time
let now = new Date();
let seconds = now.getSeconds();
if(seconds === selectedSecond){
// **** here the ajax call should go ****
console.info("ajax Called!!");
// sets the next timeout 58s later, not to miss the 20th Second
currentTimeout = (sixtySeconds - safetyBuffer) * 1000;
}else if(seconds > selectedSecond){
// sets the next timeout to 2s beforethe 20th Second
currentTimeout = (sixtySeconds - safetyBuffer - seconds + selectedSecond) * 1000;
} else if(seconds < selectedSecond - safetyBuffer) {
// sets the next timeout to 2s beforethe 20th Second
currentTimeout = (selectedSecond - safetyBuffer - seconds) * 1000;
} else {
// sets the next timeout to shortInterval(=400ms),
// for the last 2s, it will be more often, to not miss the 20th second
currentTimeout = shortInterval;
}
// calls the function with the new optimized timeout
setTimeout(timeoutHandler, currentTimeout);
}
// initial call
setTimeout(timeoutHandler, currentTimeout);
You can use setInterval method for continuous loop and when current second is 20 you can make ajax call. Please see the code snippet:
setInterval(function() {
if(new Date().getSeconds() === 20) {
// Your ajax call
}
}, 1000);
I am building a game server using Nodejs. But I have problems running setTimeout maybe because I don't quite understand the concept of the game loop.
Here is my code:
let tickLengthMS = 1000 / 60;
let previousTick = Date.now();
export function gameLoop() {
const now = Date.now();
if (previousTick + tickLengthMS <= now) {
const delta = (now - previousTick) / 1000;
previousTick = now;
update(delta);
}
if (Date.now() - previousTick < tickLengthMS - 16) {
setTimeout(gameLoop);
} else {
setImmediate(gameLoop);
}
}
So this is the game loop that updates my game. And I need to run another loop to send messages to the clients, in a different frequency / timestep.
export function updateNetwork() {
pack = ...
socket.emit('update', pack);
setTimeout(updateNetwork, 1000 / 20);
}
Then I just run:
gameLoop();
updateNetwork();
But it never gets to run the second setTimeout function. I wonder if the first gameLoop function is blocking the second setTimeout. But isn't setTimeout a non-blocking method? Is the gameLoop function equal to while(true) {} in other languages such as java and C++?
Updated the question to make two loops.
It would be nice if the computer's 'wake up' event was propagated to the browser and available in the JavaScript API. Does anyone know if anything like this is implemented?
I don't know of any direct method to do this, but one way you could get a good idea of when it happens is to set up a setInterval task that runs, say every 2 seconds, and stores the time it last ran. Then check to see if the last time it ran is very much older than 2 seconds.
var lastTime = (new Date()).getTime();
setInterval(function() {
var currentTime = (new Date()).getTime();
if (currentTime > (lastTime + 2000*2)) { // ignore small delays
// Probably just woke up!
}
lastTime = currentTime;
}, 2000);
One of the problems you might encounter with methods above is that alert boxes or other modal type windows will pause JS execution possibly causing a false wake up indication. One way to solve this problem is to use web workers (supported on newer browsers)....
DetectWakeup.js (must be its own file)
var lastTime = (new Date()).getTime();
var checkInterval = 10000;
setInterval(function () {
var currentTime = (new Date()).getTime();
if (currentTime > (lastTime + checkInterval * 2)) { // ignore small delays
postMessage("wakeup");
}
lastTime = currentTime;
}, checkInterval);
then in your application, use it like this:
var myWorker = new Worker("DetectWakeup.js");
myWorker.onmessage = function (ev) {
if (ev && ev.data === 'wakeup') {
// wakeup here
}
}
This is a little outdated, but based on the answer by Andrew Mu I've created a simple JQuery plugin to do that:
https://github.com/paulokopny/jquery.wakeup-plugin
Usage is simple:
$.wakeUp(function(sleep_time) {
alert("I have slept for " + sleep_time/1000 + " seconds")
});
Hope this will help someone in the future.
Apart from very good answers and explanations by others, you can also depend on online, offline events. Keeping aside whether online is really online or not, usually, this event ALSO gets triggered when user's machine is back from sleep apart from having real internet disconnection.
So, the ideal solution would be having timer check combined with the online and offline events.
var lastTime = (new Date()).getTime();
setInterval(function() {
var currentTime = (new Date()).getTime();
if (currentTime > (lastTime + 2000*2)) { // ignore small delays
setTimeout(function() {
//enter code here, it will run after wake up
}, 2000);
}
lastTime = currentTime;
}, 2000);
Another way, using the session time
setInterval(function(){
let last = parseInt(localStorage.getItem('sessionTime'));
let now = (new Date()).getTime();
const diffTime = Math.abs(now - last + 2000 * 2);
if (diffTime > 480000) {
_this.authService.logout(1);
}
}, 3000);