Time based javascript functions with setInterval - javascript

I'm using node.js but this question is strictly javascript related. I'm interfacing with an i2c board to fade lights and I'd like to fade them at a specific rate, say 1 second. Now with setInterval, in theory it should work like... if I wanted to fade them 100 steps in 1 second I could just do something like...
var fader = setInterval(function(){
//will fade light 100 steps in 1 second
doFade(something,something);
},10)
But depending on the code inside of the Interval loop, it may take longer than 1 second (I tested and for my application was about 2.5 seconds). I'm sure the fact that function doFade is taking a set amount of time to happen is causing this issue but I'm just curious if there is any real way to make this actually all happen within 1 second.

The closest you'll ever get, AFAIK, relying entirely on JS would be to do something like this:
var fader = (function(now)
{
var timeLeft, end = now + 1000,//aim for 1000 ms
stepsRemaining = 100,
callBack = function()
{//define here, avoid redefining function objects
doSomething(foo, bar);
setter();
},
setter = function()
{//recompute interval, set anew
if (stepsRemaining <= 0)
{//avoid infinite timeouts
return;
}
timeLeft = (end - (+(new Date)));
timeLeft= timeLeft > 0 ? timeLeft : 0;//ensure positive timeleft, if not, 0 intervals ==> ASAP
fader = setInterval(
callback,
Math.floor(
timeLeft/stepsRemaining--
)
);
};
setter();
return fader;
}(+(new Date)));//get ms now
This code, though untested creates all function objects beforehand. Then, using setter, everytime the interval finishes, as long as you haven't set 100 intervals, the new interval will be computed. After the work is done, by the callBack function, setter is called again. Here, the number of remaining steps is checked, then the timeLeft is computed again and based on the remaining steps, which are decremented by 1 on each call to setter.
To avoid setting the intervals too long, or using float-numbers, I'm calling Math.floor, and to avoid setting negative timeout values, I'm checking the value of timeLeft, too, obviously

Related

Making a clock that takes 2560ms to complete a revolution in JavaScript

I'm trying to make a clock for a Libet task - a cognitive task used by psychologists. By convention these clocks take 2560 ms to complete a revolution. Mine seems to be running quite a lot slower and I can figure out why.
I have made an example of the issue here: https://jsfiddle.net/Sumoza/re4v7Lcj/8/
On each iteration of a setInterval with a 1ms delay, I increase the angle of rotation of the hand by (Math.PI*2)/2560, i.e. 1/2560th of a circle in radians. So, incrementing one of these each ms should complete the circle in that number of ms. As you can see from the clock, this runs a fair bit slower. Can anyone shed any light as to why. Interestingly, *10 seems to look a lot closer to what I want - but that doesn't make much sense to me so I'm wary of the fix. Relevant JS from the fiddle below. Thanks for any help.
var time = 0;
var rad_tick = (Math.PI*2)/2560; //radians per tick: last number is ms per revolution
// Draw Clock
const clock = document.getElementById('clock')
const clock_ctx = clock.getContext('2d')
clock_ctx.translate(clock.width/2, clock.height/2);
radius = (clock.height/2) * 0.7;
function drawClock() {
clock_ctx.clearRect(-clock.width/2, -clock.height/2, clock.width, clock.height);
clock.style.backgroundColor = 'transparent';
clock_ctx.beginPath();
clock_ctx.arc(0, 0, radius, 0 , 2 * Math.PI);
clock_ctx.stroke();
}
drawClock();
// Draw Hand
const hand = document.getElementById('hand')
const hand_ctx = hand.getContext('2d')
hand_ctx.translate(hand.width/2, hand.height/2);
function drawHand(time){
hand_ctx.clearRect(-hand.width/2, -hand.height/2, hand.width, hand.height);
hand_ctx.beginPath();
hand_ctx.moveTo(0,0);
hand_ctx.rotate(time);
hand_ctx.lineTo(0, -radius*0.9);
hand_ctx.stroke();
hand_ctx.rotate(-time);
}
function drawTime(){
time+=rad_tick;//*10
drawHand(time)
}
var intervalId = setInterval(drawTime, 1);
Try this
var starttime = new Date().getTime();
function drawTime() {
var elapsed = new Date().getTime() - starttime;
var time = elapsed % 2560;
time = time * rad_tick;
console.log(elapsed + "->" + time);
drawHand(time)
}
Base things on the current time, and don't depend on setInterval to fire at the exact time you set it to fire. If there are things in-queue at the time that setInterval is supposed to fire, it will do those things in-queue first and only then run the function you've wired to setIterval (causing the delays you see --which build up more and more over time). Learn about the event loop and requestAnimationFrame.
Instead, get the time (freshly) prior to doing something, and calculate how much time has passed since the start time. Base your animations on these calculations. This way, even if there are slight delays, your last action will always sync things to the way they're supposed to look right now.
When you do it this way, you might lose some frames (skipping things the engine didn't have time to do), but it will likely complete much closer to the expected completion time.
The bottom line is, if you tell the browser to do more than it can do in a particular amount of time, there will be delays. However, you can skip frames of an animation to make things complete much closer to the expected completion time.
You ask setInterval to call your callback every 1ms, but does it actually call it every 1ms?
Try this code:
let cnt = 0;
setInterval(() => cnt++, 1);
setTimeout(() => { console.log(cnt); }, 1000);
For me, it prints something between 230 and 235. So looks like the actual (average) minimum interval for me is a bit over 4ms.
Adding on to Stig, since javascript isn't super fast when it comes to these things, it won't be super accurate. Using something like getTime() will allow proper timing. How about you call getTime in your drawTime function, so that around every millisecond, it will check the actual time and use that value.

HTML/Javascript Ascending Number

I'm trying to make a simple javascript game. Basically, you get pancakes at a certain amount per second. In the code, I call this value (the rate at which you get pancakes) pps. I want to make it so that as the HTML span tag that shows the total amount of pancakes gets more pancakes, (at the rate of pps), it ascends so it looks nicer.
For example, if I get pancakes at 5 pps, right now it just goes 0, 5, 10, etc... every second. I want it to go 0,1,2,3,4,5(1 second), next second 6,7,8,9,10, etc...
Here is the code that I have so far, for the pancake counter:
pps = 100;
tp = 0;
window.setInterval(function(){
tp += parseInt(pps);
document.getElementById("test").innerHTML = tp;
}, 1000);
Anyone know how to do this?
This is a problem common to all games, and one that you need to solve correctly for your game to work outside of your own computer.
The correct solution is that you need to measure the elasped time between each iteration of your game loop, or between each frame render. This is, in practice, going to be a very small number; you can think of this number as a "scaling factor".
If your game was about moving a space ship, and you wanted it to move 5 screen units per second, your game loop would:
Find the time elapsed since the last interval, in seconds. In a game rate-limited to 60 frames-per-second, this would be around 1/60th of a second
Multiply the ship's speed (5 units per second) by 1/60; the ship would move 0.8333... units this tick
move the ship by that amount.
By the time 1 full second has passed, the ship will have moved 5 units.
The exact same principal applies to your PPS.
The important part is that, in the real world, it will not be exactly 1/60th of a second between frames. If you're not computing the "scaling factor" each iteration of your loop, your game will slowly accrue error. setInterval is particularly bad for this, and not at all suitable as a source for time in a game.
The implementation in JavaScript is simple: Each game loop, record the current time from whatever source of time is available to you; in your case, you can use get Date().getTime(), which returns the time since the UNIX epoch in milliseconds. Record this value.
In the subsequent redraw, you will again call get Date().getTime(), and then subtract the previous value to the the elapsed time. That is your scaling factor, in milliseconds. You can multiply pps by that value to determine how many pancakes to add.
It's important that you still follow this approach, even if you're using setInterval. You might think you can simply setInterval(..., 1000 / 60) to invoke your callback 60 times per second, but setInterval (and setTimeout) are not accurate - they invoke your callback at least that far in the future, but potentially much further. You still need to scale pps by the elapsed times since the last redraw.
Here's a simple implementation:
var PPS = 5;
var lastTime = new Date().getTime();
var cakes = 0;
setInterval(function () {
var currentTime = new Date().getTime()
var elapsedTime = currentTime - lastTime;
lastTime = currentTime;
cakes += (PPS * (elapsedTime / 1000)) // elapsedTime is in milliseconds, divide by 1000 to get fractional seconds
document.getElementById('pps').innerText = cakes;
}, 10);
<div id="pps"></div>
As an aside, the incorrect solution is one you find in a lot of old games: Increment things as fast as you can. On old computers this was a viable solution; the game redrew slowly enough that the game would advance smoothly. As computers got faster, the game would run faster, until it became unplayable.
A simple interval timer would do the trick. Something like this:
function incrementToNumber(tag, currentNumber, targetNumber) {
var numTicks = targetNumber - currentNumber;
var interval = setInterval(function() {
currentNumber++;
tag.innerText = currentNumber;
if(currentNumber == targetNumber) {
clearInterval(interval);
}
}, 1000 / numTicks);
}
That particular function increments over the course of one second. To change the time it takes to increment, swap out the 1000 with whatever milliseconds you want it to take.
For a version that increases forever:
function inrementForever(tag, currentPancakes, pancakesPerSecond) {
setInterval(function() {
currentPancakes++;
tag.innerText = currentPancakes;
}, 1000 / pancakesPerSecond);
}

Javascript - gameloop using delta time

I am trying to implement a gameloop that uses delta time. I got the following code from this article, however I feel it does not explain this type of gameloop well. I have researched on requestAnimationFrame and none of the explenations seem to be useful. Can someone simply break down how this loop works?
function timestamp() {
return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
},
var now,
dt = 0,
last = timestamp(),
step = 1/60;
function frame() {
now = timestamp();
dt = dt + Math.min(1, (now - last) / 1000);
while(dt > step) {
dt = dt - step;
update(step);
}
render(dt);
last = now;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
requestAnimationFrame is a special timer. Unlike setInterval which executes the callback repeatedly after a given minimum of milliseconds, requestAnimationFrame executes variably to achieve smooth framerates.
The problem is how requestAnimationFrame achieves that. Depending on the situation, it may run faster or slower. What this means is that if your update logic was tied to requestAnimationFrame directly, a character running at "one step per update" would travel 60 steps in one second when requestAnimationFrame is running at 60fps, but then would only do 40 when it throttles down to 40fps.
To counteract this sudden speed-up/slow-down of the timer, we use "delta time". Instead of depending on each iteration of requestAnimationFrame to call an update, you check the time between frames to see if it is the right time to call an update.
So lets say your character should do a step every 100ms. If the game ran at 60fps, 100ms is roughly every 6 frames. This means that for each iteration, your code checks to see if 100ms has elapsed. Around the 6th frame it does, and calls update. Now if the timer ran at 40fps, 100ms is around 4 frames. So same logic, on each iteration it checks if 100ms elapsed. At the 4th frame it does and calls update. With this, you are insured that update is consistently being called regardless of the fluctuations.

call function on the minute every minute

The code I wrote to call a function on the minute every minute, I think is flawed, as It's good for a while, but tends to lag behind by about 15 seconds for every hour since the page was loaded. To be honest I can't figure out what's causing the lagging, maybe it's the time it takes the functions to execute, small lapses all adding up and accumulating. Is there a way to auto-correct the lapses within the function as it's called. Or maybe someone knows a better method of achieving on the minute function calls. Any help or ideas much appreciated. Thanks.
var now = new Date();
var delay = 60 * 1000; // 1 min in msec
var start = delay - (now.getSeconds()) * 1000 + now.getMilliseconds();
setTimeout(function setTimer() {
onTheMinFunc();
setTimeout(setTimer, delay);
}, start);
First of all, the DOM Timers API does not guarantee accuracy. I quote:
This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected.
Second, you have a lag on each round caused by the time onTheMinFunc() is executed (you only set the timeout when it's done).
So, let's say onTheMinFunc takes half a second to execute - you get half a second delay at each minute and it accumulates - after only 10 minutes it'll lag quite a bit. (Note, functions should usually not take more than 15ms to execute anyway to avoid noticeable lag)
Try:
setInterval(onTheMinFunc, delay);
It still won't be very accurate. You can poll on much shorter intervals and keep track of a date variable - but again - no guarantees.
What you probably want is setInterval:
setInterval(onTheMinFunc, delay);
As is, your code using setTimeout means that the time it takes to execute your onTheMinFunc is being added into your delay before the next one is started, so over time, this extra delay will add up.
Using setInterval will be more accurate, since the delay is between calls to execute the function, rather than starting the timer only after the function is finished.
Timers and javascript times aren't very accurate, and I would think the only way to make sure a function is executed every whole minute over time, is to check the seconds every second
setInterval(function() {
if ( new Date().getSeconds() === 0 ) onTheMinFunc();
},1000);
FIDDLE
Here is a slight modification to your code:
function everyMinute(fn) {
arguments[1] && fn();
var now = new Date();
var delay = 60 * 1000 - (now.getSeconds()) * 1000 + now.getMilliseconds();
setTimeout(function(){
everyMinute(fn, true);
}, start);
}
everyMinute(onTheMinFunc);
It recalculates the number of milliseconds to wait till the next minute every time so it is as accurate as possible to the top of the minute.
I think you want something closer to this:
function setNextMinute() {
// figure out how much time remains before the end of the current minute
var d = new Date().getTime()%60000;
//set a timeout to occur when that expires.
setTimeout(function () {
// recalculate a new timeout so that your timer doesn't lag over time.
doWhateverYouWantToHere();
// note that calling doWhateverYouWantToHere() will
// not offset the next minute, since it is recalculated in setNextMinute()
setNextMinute();
},60000-d);
}
setNextMinute();
caveat: I did not thoroughly test this for timing. But it appeared to work for 1 sec intervals and 1 min intervals well enough.
This has the advantage of not recalculating every second, and also not just starting a 60 second timer from whatever the current time is.
The current accepted answer may overkill
Executing if ( new Date().getSeconds() === 0 ) onTheMinFunc(); on each second (and forever) seems to not be a good idea.
I will not benchmark it against the following propositions, it's not necessary.
Clues
Use whatever logic is necessary to calculate the start moment.
On the start moment
Use setInterval for remaning executions
Execute the first call
Note setInterval is called ASAP to avoid that time lapses.
If you want that new Date().getSeconds() === 0:
var id = setInterval(function() {
if ( new Date().getSeconds() === 0 ) {
setInterval(onTheMinFunc, delay);
onTheMinFunc();
clearInterval(id);
}
},1000);
Alternatively, you could use your own logic:
var now = new Date();
var delay = 60 * 1000; // 1 min in msec
var start = delay - (now.getSeconds()) * 1000 + now.getMilliseconds();
setTimeout(function() {
setInterval(onTheMinFunc, delay);
onTheMinFunc();
}, start);
Please check both examples working on jsfiddle
The second (Example B) seems more accurate.

JavaScript Duration of animation isn't exact

First of all I want to mention two things,
One: My code isn't perfect (esspechially the eval parts) - but I wanted to try something for my self, and see if I could duplicate the jQuery Animation function, so please forgive my "bad" practices, and please don't suggest that I'll use jQuery, I wanted to experiment.
Two: This code isn't done yet, and I just wanted to figure out what makes it work badly.
So the animation runs for about 12 seconds while the duration parameter I entered was 15 seconds, What am I doing wrong?
function animate(elem, attr, duration){
if(attr.constructor === Object){//check for object literal
var i = 0;
var cssProp = [];
var cssValue = [];
for(key in attr) {
cssProp[i] = key;
cssValue[i] = attr[key];
}
var fps = (1000 / 60);
var t = setInterval(function(){
for(var j=0;j<cssProp.length;j++){
if(document.getElementById(elem).style[cssProp[j]].length == 0){
//asign basic value in css if the object dosn't have one.
document.getElementById(elem).style[cssProp[j]]= 0;
}
var c = document.getElementById(elem).style[cssProp[j]];
//console.log(str +" | "+c+"|"+cssValue[j]);
if(c > cssValue[j]){
document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));
}else if(c < cssValue[j]){
document.getElementById(elem).style[cssProp[j]] += 1/((duration/fps)*(c-cssValue[j]));
}else if(c == cssValue[j]){
window.clearInterval(t);
}
}
},fps);
}
}
animate('hello',{opacity:0},15000);
html:
<p id="hello" style="opacity:1;">Hello World</p>
Note: I guess there is a problem with the
(duration/fps)*(c-cssValue[j])
Part or/and the interval of the setInterval (fps variable).
Thanks in advance.
I'm not gonna try and refactor that and figure it out, cause it's pretty wonky. That said... a few things.
Don't rely on the value you are animating to let you know animation progress
In general your approach is unsound. You are better off keeping track of progress yourself. Also, as a result of your approach your math seems like it's trying too hard, and should be much simpler.
Think of it like this: your animation is complete when the time has elapsed, not when the animated value seems to indicate that it's at the final position.
Don't increment, set
Floating point math is inexact, and repeated addition cumulation like this is going accumulate floating point errors as well. And it's far more readable to make some variables to keep track of progress for you, which you can use in calculations.
animatedValue += changeOnThisFrame // BAD!
animatedValue = valueOnThisFrame // GOOD!
Don't do the positive/negative conditional dance
It turns out that 10 + 10 and 10 - (-10) is really the same thing. Which means you can always add the values, but the rate of change can be negative or positive, and the value will animate in the appropriate direction.
timeouts and intervals aren't exact
Turns out setTimeout(fn, 50) actually means to schedule the fn to be call at least 50ms later. The next JS run loop to execute after those 50ms will run the function, so you can't rely on it to be perfectly accurate.
That said it's usually within a few milliseconds. But 60fps is about 16ms for frame, and that timer may actually fire in a variable amount of time from 16-22ms. So when you do calculations based on frame rate, it's not matching the actual time elapsed closely at all.
Refactor complex math
Deconstructing this line here is gonna be hard.
document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));
Why for more complex break it up so you can easily understand what's going on here. refactoring this line alone, I might do this:
var style = document.getElementById(elem).style;
var changeThisFrame = duration/fps;
var someOddCalculatedValue = c-cssValue[j];
style[cssProp[j]] -= 1 / (changeThisFrame * someOddCalculatedValue);
Doing this makes it clearer what each expression in your math means and what it's for. And because you didn't do it here, I had a very hard time wondering why c-cssValue[j] was in there and what it represents.
Simple Example
This is less capable than what you have, but it shows the approach you should be taking. It uses the animation start time to create the perfect value, depending on how complete the animation should be, where it started, and where it's going. It doesn't use the current animated value to determine anything, and is guaranteed to run the full length of the animation.
var anim = function(elem, duration) {
// save when we started for calculating progress
var startedAt = Date.now();
// set animation bounds
var startValue = 10;
var endValue = 200;
// figure out how much change we have over the whole animation
var delta = endValue - startValue;
// Animation function, to run at 60 fps.
var t = setInterval(function(){
// How far are we into the animation, on a scale of 0 to 1.
var progress = (Date.now() - startedAt) / duration;
// If we passed 1, the animation is over so clean up.
if (progress > 1) {
alert('DONE! Elapsed: ' + (Date.now() - startedAt) + 'ms');
clearInterval(t);
}
// Set the real value.
elem.style.top = startValue + (progress * delta) + "px";
}, 1000 / 60);
};
anim(document.getElementById('foo'), 5000);
​
JSFiddle: http://jsfiddle.net/DSRst/
You cannot use setInterval for accurate total timing. Because JS is single threaded and multiple things compete for cycles on the one thread, there is no guarantee that the next interval call will be exactly on time or that N intervals will consume the exact duration of time.
Instead, pretty much all animation routines get the current time and use the system clock to measure time for the total duration. The general algorithm is to get the start time, calculate a desired finish time (starttime + duration). Then, as you've done, calculate the expected step value and number of iterations. Then, upon each step, you recalculate the remaining time left and the remaining step value. In this way, you ensure that the animation always finishes exactly (or nearly exactly) on time and that you always get exactly to the final position. If the animation gets behind the ideal trajectory, then it will self correct and move slightly more for the remaining steps. If it gets ahead for any reason (rounding errors, etc...), it will dial back the step size and likewise arrive at the final position on time.
You may also need to know that browsers don't always support very small timing amounts. Each browser has some sort of minimum time that they will allow for a timer operation. Here's an article on minimum timer levels.
Here's an article on tweening (the process of continually recalculating the step to fit the duration exactly).
I'd also suggest that you look at the code for doing animation in some libraries (jQuery, YUI or any other one you find) as they can all show you how this is done in a very general purpose way, including tweening, easing functions, etc...

Categories