requestAnimationFrame seems to update variables after cancelAnimationFrame is called - javascript

I'm trying to animate a camera rotation using MapBoxGL, while providing the option to pause the rotation and restart the rotation with a checkbox callback. The 'pause'/'stop' rotation works fine, but the 'restart' seems to pick up the animation where the it should have been if it was never paused, as opposed to picking up where the animation stopped.
var animation;
function rotateCamera(timestamp) {
map.rotateTo((timestamp / 600) % 360, {duration: 0});
animation = requestAnimationFrame(rotateCamera);
}
When the map loads, the animation is called with:
animation = rotateCamera(0);
The callback looks like this:
d3.selectAll("input[name='camerarotation-selection']").on("change", function() {
if (d3.select("input[name='selection']").property("checked")) {
rotateCamera(map.getBearing());
} else {
cancelAnimationFrame(animation);
}
});
If I console.log the timestamp var inside the rotateCamera function, I can see that despite the cancelAnimationFrame call, it continues to be incremented. I have tried declaring animation to be undefined upon a restart, and that doesn't seem to work either. I'm stumped! Thanks for your help.

The timestamp passed to the callback of requestAnimationFrame is an DOMHighResTimestamp, similar to the one returned by performance.now().
This timestamp indicates the number of milliseconds that elapsed since the beginning of the current document's lifetime (well it can be a bit more complicated) when the callbacks execution started.
So even when no requestAnimationFrame loop is running, this timestamp indeed increments, just like Date.now() also does.
let animation = 0;
inp.onchange = e => {
if (inp.checked) start();
else {
cancelAnimationFrame(animation);
}
};
function start(timestamp) {
_log.textContent = timestamp;
animation = requestAnimationFrame(start);
}
<input type="checkbox" id="inp">
<pre id="_log"></pre>
In your code, you will probably want to only keep the time that elapsed since last frame. To do so, you can simply save timestamp in a variable that is globally available, and then in your callback do
var elapsed = timestamp - last_frame;
last_frame = timestamp;
And remember to also take care of the resume case, where timestamp will be undefined and elapsed should be reset.
Now, I'd like to point out that your description of the problem could also indicate an other problem entirely: you could have more than a single loop running simultaneously.
Since you are using a single animation variable to hold the frame_id (used by cancelAnimationFrame), if you do call rotateCamera while a loop is already running, the first frame_ids will get lost, and their rAF loop will indeed continue.
let common_id = 0; // OP's `animation` variable
let first_id = 0;
let second_id = 0;
function loop1(timestamp) {
common_id = first_id = requestAnimationFrame(loop1);
log_1.textContent = "loop 1: " + timestamp;
}
function loop2(timestamp) {
common_id = second_id = requestAnimationFrame(loop2);
log_2.textContent = "loop 2: " + timestamp;
}
btn.onclick = e => {
console.log("first loop's id", first_id);
console.log("second loop's id", second_id);
console.log('clearing common_id', common_id);
cancelAnimationFrame(common_id);
}
loop1();
loop2();
<button id="btn">stop the loop</button>
<pre id="log_1"></pre>
<pre id="log_2"></pre>
I think it is possible in your code, since input[name='camerarotation-selection'] could change multiple times, when input[name='selection'] had not chnaged, or even since input[name='camerarotation-selection'] could be multiple elements.
To avoid that, you could keep a semaphore variable allowing you to know if the loop is running or not, and to only start it when it's not.
Or you could even get rid entirely of cancelAnimationFrame by using only one semaphore, and exiting early in the rAF callback:
let stopped = true;
function loop(timestamp) {
if (stopped) return; // exit early
requestAnimationFrame(loop);
log.textContent = timestamp;
}
// you can click it several times
btn_start.onclick = e => {
if (stopped === true) { // only if not running yet
stopped = false;
requestAnimationFrame(loop);
}
}
btn_stop.onclick = e => {
stopped = true; // deal only with the semaphore
};
btn_switch.onclick = e => {
stopped = !stopped;
if (stopped === false) { // we were paused
// start again
requestAnimationFrame(loop);
}
}
<button id="btn_start">start the loop</button>
<button id="btn_stop">stop the loop</button>
<button id="btn_switch">switch the loop</button>
<pre id="log"></pre>

Related

Javascript simple game loop - how to interval?

I have a simple game loop that goes like this:
function update(progress){
//do something each second
//do something else twice per second
//do something else each two seconds
}
function draw() {
//code here...
}
function gameLoop(timestamp) {
update(progress)
draw()
var progress = (timestamp - lastRender)
lastRender = timestamp
window.requestAnimationFrame(gameLoop)
}
var lastRender = 0
window.requestAnimationFrame(gameLoop)
How can I make sure to execute some actions into the update function each helf second, second, or two seconds?
Thank you
If you want to define the interval, you'll need to use setInterval. requestAnimationFrame will only update based on the refresh rate of the screen so you cannot define your own interval with this. setInterval has lots of downsides though, so it is recommended to sync up the interval with the refresh rate using something like this:
let doUpdate = false
setInterval(() => doUpdate = true), 1000)
const render = () => {
if (doUpdate) {
// your code
doUpdate = false
}
}
window.requestAnimationFrame(render)
If you want to perform an action periodically when using game loop based on delta time, the basic idea is for you to keep a counter of the elapsed time. For each iteration, you then add the time difference until it reaches the intended period.
Applied to your code, it would look something like this:
let oneSecondCounter = 0
let twoSecondCounter = 0
function update(progress){
oneSecondCounter += progress
if (oneSecondCounter >= 1000) {
// Code here will be executed every 1000ms
oneSecondCounter = 0
}
twoSecondCounter += progress
if (twoSecondCounter >= 2000) {
// Code here will be executed every 2000ms
twoSecondCounter = 0
}
}
function draw() {}
function gameLoop(timestamp) {
var progress = (timestamp - lastRender)
update(progress)
draw()
lastRender = timestamp
window.requestAnimationFrame(gameLoop)
}
var lastRender = performance.now()
window.requestAnimationFrame(gameLoop)
However, this means you have to make a counter variable for every periodic action you want to perform. Instead of creating a separate variable, we can bundle the counter together with the function using a closure. The closure lets each function to have their own independent counter.
The closure that we are going to use looks like this:
function initPeriodicFunction(fn, runEvery) {
let counter = 0
return function (deltaTime) {
counter += deltaTime
if (counter >= runEvery) {
fn()
counter = 0
}
}
}
Now, instead of having to create a counter variable for each action, we can just pass a function to the initPeriodicFunction and get a new function which will run only periodically.
// You can use an arrow function
const runEverySecond = initPeriodicFunction(() => console.log('One second'), 1000)
// Or you can pass a function
function runThis() {
console.log('Two seconds')
}
const runEveryTwoSeconds = initPeriodicFunction(runThis, 2000)
function update(progress){
runEverySecond(progress)
runEveryTwoSeconds(progress)
}
Depending on your use case, the method above might be enough. If you're going to perform a more accurate tasks (e.g. physics engine), it would be better to separate the frame rate from the update rate. This is similar to how Unity's FixedUpdate works.
Imagine you want to perform a physics update every 100ms. If somehow the update call was delayed, for example 600ms after the last update, instead of performing a single update, we perform 6 updates, each with 100ms chunk. This results in more precise step-by-step calculation.
To perform this kind of fixed update, the initialization function need to be modified as follows:
function initPeriodicFunction(fn, runEvery) {
let counter = 0
return function (deltaTime) {
counter += deltaTime
while (counter >= runEvery) {
fn()
counter -= runEvery
}
}
}
Now, the function will be run either once or multiple times depending on how long has elapsed since the last update call.
setInterval(function {
//code that happends every second goes here
}, 1000);
use setInterval().
This creates a timer and will call the function every x seconds.

Javascript start stopwatch after keyevent

I'am working on a game with the canvas element. The goal is that first time you press a key it start a stopwatch. It should end as soon as the gameoverscreen/winscreen appears.
After the gameoverscreen/winscreen it should work like befor(if press key than start stopwatch)
The problem is that that the function only once called can be.
The Code(the most important part):
function startTime(){
startTime = function(){};
var count = 0;
function stopwatch(){
if(winScreen || gameOver){
count = 0;
} else{
console.log(count++);
}
}
setInterval(stopwatch, 1000);
}
document.addEventListener('keydown', function(event){
startTime();
});
Is there a way to solve that problem?
The cause of your problem is that you are overwriting startTime with an empty function on the second line. The second time you call startTime(), it runs the empty function.
To keep your code clean, your stopwatch shouldn't really check for the win or game over conditions - it should only keep track of the count. The rest of your game code can start and reset the stopwatch whenever those conditions occur. You could have a stopwatch object like this:
var stopwatch = {
count: 0,
intervalId: null,
start: function() {
stopwatch.intervalId = setInterval(function() {
stopwatch.count++;
}, 1000)
},
reset: function() {
if (stopwatch.intervalId) {
clearInterval(stopwatch.intervalId);
stopwatch.intervalId = null;
}
stopwatch.count = 0;
}
}
Then your game can call stopwatch.start() when it starts and stopwatch.reset() when it ends.
Note that it also clears the interval when it resets. Without this, the function inside setInterval would get duplicated every time, causing potential bugs and a memory leak.

How to make the refresh frequency of setTimeout a variable?

I want a function I am writing to call itself automatically. I want to be able to parse the frequency at which it calls itself via the first time I parse it. It would then use that same value internally with the JS setTimeout() function to call itself repeatedly again at the same frequency.
So you can see what I have in the sample below:
function testFunction(refreshFrequ){
setTimeout(function() {
console.log("frequency: "+refreshFrequ);
testFunction(refreshFrequ);
}, refreshFrequ);
}
// run the 1st time
testFunction(5000);
The problem is that this doesn't work as from the second time it runs onwards the parsed timeout isn't evaluated. The console output gives a clue to what's going on here:
frequency: undefined
How would I get this working, nothing so far has helped.
Try Window setInterval() Method instead. Also see this answer and this answer for more information.
var autoInterval;
var elapsed = 0;
function myStartFunction(refreshFrequ) {
if (!autoInterval) {
autoInterval = setInterval(function() {
elapsed++;
document.getElementById("txt").innerHTML = refreshFrequ * elapsed + " elapsed.";
console.log("frequency interval: " + refreshFrequ + " x " + elapsed);
}, refreshFrequ);
}
}
function myStopFunction() {
if (autoInterval) {
clearInterval(autoInterval);
autoInterval = null;
elapsed = 0;
document.getElementById("txt").innerHTML = "Interval was reset.";
console.log("interval stopped");
}
}
myStartFunction(5000);
<p>The setInterval() method has started automatically.</p>
<button onclick="myStartFunction(1000)" title="Start with 1000 ms interval. Clicking this button while the event is active should not create a new interval instance.">Start</button> <button onclick="myStopFunction()" title="Click to stop and clear the interval instance.">Stop</button>
<p id="txt">0 elapsed.</p>
Edit: Although there was no mention of the potential duplicate function calls, the other answer should be taken into consideration, especially if the event can arbitrarily be executed. The if statement was imposed in order to prevent duplicate events from being stacked up against the original instance; otherwise, each additionally executed function would result in a unique instance, which could then further create unstoppable multiple events, so I must give credit where credit is due. Kudos to Tymek!
You might want to use setInterval instead.
var testFunction = (function () { // This will "build"/"enclose" our function
var handle = null; // ID of the interval
return function (freq) {
if (handle !== null) clearInterval(handle);
handle = setInterval(function() {
console.log("frequency: " + freq);
}, freq);
};
})();
With this if you re-initialize interval, you will not create another instance of it (having 2 functions ticking).
You can learn more about setInterval at: https://www.w3schools.com/jsref/met_win_setinterval.asp
and more about how JavaScript functions works at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Define a timer in JavaScript

I have a function in JavaScript. I use setInterval in order to control my function. I also have another logic for controlling my function. I have a counter which is increased once one condition happens and is decreased when another condition happens. Now, sometimes the second condition does not happen and hence my function won't be resume anymore. (I pause my function when my first condition happen). Therefore, I want to wait at most 30 seconds for the second condition. If it does not happen, then I want to resume my function anyway. I have following code, but it does not work as I expect it. What happens is that it resume my function every 30 seconds. Then, it may be resumed while it should wait. Can someone let me know what is the problem with my code?
Please note that, the value for the counter may increase to more than 20. I mean the first and second condition may occur more than once.
function main()
{
// body
}
function increaseCounter()
{
counter += 1;
clearInterval(controller);
controlSecond = setInterval(function(){
counterSeconds += 1;
if (counterSeconds == 30)
{
counterSeconds = 0;
controller = setInterval(main, 100);
clearInterval(controlSecond);
}
}, 1000);
}
function decreaseCounter()
{
counter -= 1;
if (counter == 0)
{
counterSeconds = 0;
clearInterval(controlSecond);
controller = setInterval(main, 100);
}
}
Consider what happens if you call increaseCounter twice in a row.
On the first execution it will create interval A and assign it to controlSecond.
On the second execution it will create interval B and assign it to controlSecond, while interval A continues to fire off indefinitely. You won't stop it with clearInterval(controlSecond) because controlSecond no longer references interval A.
The problem is that you continue to set controlSecond and controller to a new interval without clearing them first. That results in the intervals being leaked with no way of clearing them. It's sort of like a memory leak where you have dynamically allocated memory but nothing pointed at it, but instead of renegade memory you have renegade intervals.
One way to prevent this is to make sure you always clear your interval before setting it.
I would also recommend that you implement controlSecond with a setTimeout because that is designed for tasks which only happen once.
Why not
var counter = 0
var timeout = null
function main () {
clearTimeout(timeout);
timeout = null;
}
function increaseCounter () {
counter++;
if (!timeout)
timeout = setTimeout(main, 30*1000);
}
function decreaseCounter() {
counter--;
if (counter === 0)
main();
}

Timing in JS - multiple setIntervals running at once and starting at the same time?

Let's say I have a function:
myFunc = function(number) {
console.log("Booyah! "+number);
}
And I want it to run on a set interval. Sounds like I should use setInterval, huh!
But what if I want to run multiple intervals of the same function, all starting at the exact same time?
setInterval(function(){
myFunc(1);
}, 500);
setInterval(function(){
myFunc(2);
}, 1000);
setInterval(function(){
myFunc(3);
}, 2000);
So that the first runs exactly twice in the time it takes the second to run once, and the same between the second and third.
How do you make sure that they all start at the same time so that they are in sync?
Good question, but in JS you can't. To have multiple functions in the same program execute at the same time you need multi-threading and some deep timing and thread handling skills. JS is single threaded. setInterval doesn't acutally run the function after the delay, rather after the delay it adds the function to the event stack to be run as soon as the processor can get to it. If the proc is busy with another operation, it will take longer than the delay period to actually run. Multiple intervals/timeouts are all adding calls to the same event stack, so they run in turn as the proc is available.
function Timer(funct, delayMs, times)
{
if(times==undefined)
{
times=-1;
}
if(delayMs==undefined)
{
delayMs=10;
}
this.funct=funct;
var times=times;
var timesCount=0;
var ticks = (delayMs/10)|0;
var count=0;
Timer.instances.push(this);
this.tick = function()
{
if(count>=ticks)
{
this.funct();
count=0;
if(times>-1)
{
timesCount++;
if(timesCount>=times)
{
this.stop();
}
}
}
count++;
};
this.stop=function()
{
var index = Timer.instances.indexOf(this);
Timer.instances.splice(index, 1);
};
}
Timer.instances=[];
Timer.ontick=function()
{
for(var i in Timer.instances)
{
Timer.instances[i].tick();
}
};
window.setInterval(Timer.ontick, 10);
And to use it:
function onTick()
{
window.alert('test');
}
function onTick2()
{
window.alert('test2');
}
var timer = new Timer(onTick, 2000,-1);
var timer = new Timer(onTick2, 16000,-1);
For a finite number of ticks, change the last parameter to a positive integer for number. I used -1 to indicate continuous running.
Ignore anyone who tells you that you can't. You can make it do just about any thing you like!
You can make something like this.
arr = Array();
arr[0] = "hi";
arr[1] = "bye";
setTimer0 = setInterval(function(id){
console.log(arr[id])
},1000,(0));
setTimer1 = setInterval(function(id){
console.log(arr[id]);
},500,(1));
Hope it helps!
JavaScript is single threaded. You can use html5 web worker or try using setTimeout recursively. Create multiple functions following this example:
var interval = setTimeout(appendDateToBody, 5000);
function appendDateToBody() {
document.body.appendChild(
document.createTextNode(new Date() + " "));
interval = setTimeout(appendDateToBody, 5000);
}
Read this article:
http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx
You can use multiples of ticks inside functions, in the example below you can run one function every 0.1 sec, and another every 1 sec.
Obviously, the timing will go wrong if functions require longer times than the intervals you set. You might need to experiment with the values to make them work or tolerate the incorrect timing.
Set a variable to handle tick multiples
let tickDivider = -1
Increase the value of tick variable inside the faster function
const fastFunc = ()=> {
tickDivider += 1
console.log('fastFunciton')
}
Use a condition to on running the slower function
const slowFunc = ()=> {
if (!(tickDivider % 10)){
console.log('slowFunction')
}
}
Call both functions in a single one. The order is not important unless you set tickDivider to 0 (of any multiple of 10)
const updateAllFuncs = () => {
fastFunc()
slowFunc()
}
Set the interval to the frequency of the faster function
setInterval(updateAllFuncs, 100)
What I'm doing here is adding a speed attribute to the HTML elements. These speeds are passed as a parameter to setCounter(). I did this mainly to make the code easier to test and play with.
The function setCounter() is invoked inside a loop for every HTML element with class counter. This function sets a new setInterval in every execution.
The intervals seem to be working in sync.
const elements = document.querySelectorAll('.counter')
elements.forEach((el, i) => {
let speed = Number(elements[i].getAttribute('speed'))
setCounter(el, speed, 5000)
})
function setCounter(element, speed, elapse){
let count = 0
setInterval(() => {
count = (count >= elapse) ? elapse : count + speed
if(count === elapse) clearInterval()
element.innerHTML = count
}, 1)
}
Same Speeds
<p class="counter" speed='10'></p>
<p class="counter" speed='10'></p>
Different Speeds
<p class="counter" speed='3'></p>
<p class="counter" speed='5'></p>

Categories

Resources