icecreamyou / mainloop.js Goto Github PK
View Code? Open in Web Editor NEWProvides a well-constructed main loop useful for JavaScript games and other animated or time-dependent applications.
License: MIT License
Provides a well-constructed main loop useful for JavaScript games and other animated or time-dependent applications.
License: MIT License
Basically, MainLoop.stop()
stops the loop by canceling the frame referenced by the rafHandle
variable. However, inside of animate()
(which calls the begin/draw/update/end functions), rafHandle
points to the current frame, so canceling it doesn't do anything. Afterwards, requestAnimationFrame()
still gets called again at the end of animate()
, setting a new rafHandle
for the next frame.
The solution to this is to simply move the requestAnimationFrame()
call from the bottom of animate()
to the top. As far as I can tell, this has no negative effects. It has one additional positive effect, which is that if we have to fall back to the setTimeout()
-based polyfill for requestAnimationFrame()
, the frames will be called slightly closer to on-time.
This library only supports one instance per page due to the way it is written with global library state. A more idiomatic approach would be to expose a constructor that makes a new MainLoop. For instance,
var mainLoop = new MainLoop(options);
mainLoop.setEnd(cb);
// You can easily make another one
var mainLoopTwo = new MainLoop(options);
Given how well-organized the code is, I think it'd be relatively simple to refactor it to use this system instead.
If this is something you're interested in, let me know and I'd be happy to put together a PR for you to review.
Just trying to understand a bit, here's a simple demo that just writes values:
https://codepen.io/dakom/pen/GVomge?editors=0010
The interpolation amount passed to draw seems to sort of cycle, but not within one second, rather, resets after a while.
Can someone please explain a bit more why this is and what's happening under the hood?
Thanks!
For example I need to perform a specific action every game second:
function update(dt) {
if (/* what to check here? */) {
console.log('this message is logged every second')
}
}
Just trying to understand the approach a bit better before porting it-
Would it work to call update(simulationTimestep)
and updating all the related variables inside setInterval/setTimeout - even if render()
is scheduled with rAF? Talking about browsers only and assuming that the callback can call performance.now()
In other words, I'm trying to understand why physics updates are also done in rAF which, as you've mentioned is tied to the display rate and can be delayed when switching tabs etc.
Thanks!
Hello !
I'm having a problem with the Update function.
Let's say I have a class named "Game" which has a method named "update"
Normally, I could do this :
let game = new Game();
game.update();
and the method could be like this :
update() {
this.objects.doStuff();
}
But when I try to call the method with the Update call, I can't access "this"
I get the following error : this is undefined
Is it due to the plugin implementation or is this simple javascript behaviour that I'm not aware of ?
Thank you very much.
I put together a minimal example: https://github.com/dakom/mainloop-test
and it's suuper janky in firefox at least. any idea why that is?
Hey Isaac!
Thanks a lot for this awesome repository. How would you suggest going about a Node.js implementation? I can't quite figure out the best way to do this.
I'm currently running an interval on the server-side, which streams out data to the client-side, which will then render the game (as for now). But as you probably know, this is not the best solution.
Basically I need to figure out a way to stream updates from server-side to client-side, and then render it in the most efficient way.
Any input is much appreciated!
Thank you for open sourcing it and for writing so many comments! ❤️ ❤️ ❤️ ❤️ ❤️ ❤️ ❤️
Hello, the documentation says that it can be used in Node.js with require('mainloop')
But when I tested, it simply isn't possible because of the use of "window', which obviously isn't available server-side...
I haven't tested this yet personally but I noticed in stop() that you call cancelAnimationFrame(rafHandle). If the RAF polyfill falls back to setTimeout, wouldn't stop() fail to actually stop the loop from continuing since you would need to use clearTimeout() instead?
Since last commit was 2 years ago is a nice idea update the library to fully work with the node module system (a.k.a. export default
/import
), once this feature is more mature now.
would love to have this for rust/wasm projects...
If I go for it, how should I handle the license? (happy for it to be MIT too)
of course it would mostly be copy/paste and I want to give you full credit for all the hard work you put into it - but Rust/WASM has its own set of problems that need some original solutions too.
There might be some minor issues with the FPS calculation:
FPS < 1
is currently not possible, but this might happen in test environments.n
times in each loop with the value frameCount / n
, where n
is the number of seconds passed since the last update.timestamp > lastFpsUpdate + 1000
.frames / second
frames / (second + time until next frame starts)
.while (lastFpsUpdate < timestamp - 1000) lastFpsUpdate += 1000;
lastFpsUpdate = timestamp;
This library currently uses window.requestAnimationFrame
if available and falls back to setTimeout
otherwise. The upcoming WebVR API will use display.requestAnimationFrame
to schedule frames on attached VR displays. This library should support such loops.
When bundled via Browserify, this
(and thus root
) is a reference to module.exports
instead of window
, so the module falls back to setTimeout
.
Related: browserify/browserify#372
Thanks :D
I would like to only execute "setEnd" 20 times per second, because updating updateGamestate 60 times per second over the network would be too much expensive.
This code is kind of working for me but it looks like it gives performance issues.
const nus = 20 // Number of updates per second - 1 min, 60 max
let emit = true
setInterval(() => (emit = true), 1000 / nus)
mainloop.setEnd(() => {
if (emit) {
io.emit('gameState', updateGamestate(bodies))
emit = false
}
})
There is a proper way to limit the number of calls to setEnd?
This runs great, but the recursive call to requestAnimation causes the call stack to become pretty deep. I'm only using the loop for update(), so maybe the issue is with how I integrated it?
I've integrated this into my N64 javascript emu at http://1964js.com.
Hi,
I've been trying to force the loop to execute more quickly so that I can test my game code at a faster rate (game runs twice as fast for example). I've tried updating the simulated timestep and/or max FPS but I think there's a throttle built into MainLoop which is actively preventing me from running too quickly.
Do you have any tips on what I can do?
Many thanks!
Andy
Hi!
I see you added "main" in package.json about 2 months ago, but you have not published a new package to the npm registry.
So after doing "npm install mainloop.js" I still had to add main to use your lib.
Could you please publish a newer version of this package to the npm registry?
Thanks.
Hello!
Your article on game time step helped me so much when I was making a small game. Thank you for the article and this awesome library!
I am trying to make a multiplayer game with Websocket and I'm not sure where would be the ideal place to put the socket.send() calls. The game updates at fixed time step, and draws at 60fps. I would like to send the player inputs to the authoritative server at 30hz instead of 60hz. Is this possible to do with this library?
Thank you for your help!
This may be a stupid question...
If update takes an appreciable amount of time (but still less than simulationTimeStep), wont looping through update 240 times if you're just going to bail and resync anyway just make it take longer to panic?
I assume I'm missing something.
Minor nitpick, but "bower install MainLoop" doesn't actually work (at least, not in git bash) but..
"bower install mainloop" DOES work. Is this an issue affecting everyone or just git bash users?
After a couple seconds when i am using this library chrome says: "Aw, Snap!".
I'm using pixijs. I'm just making some circles to move on the screen. The fps also lowers to under 18 fps.
Currently the update function is called multiple times in a row:
numUpdateSteps = 0;
while (frameDelta >= simulationTimestep) {
update(simulationTimestep);
frameDelta -= simulationTimestep;
if (++numUpdateSteps >= 240) {
panic = true;
break;
}
}
Since it uses time delta anyway, why can't it be condensed into the single update
call based on how much time has passed? It seems that this way would require only one update
call for the loop to catch up instead of several at times.
if ((simulationTimestep / frameDelta) >= 240) {
panic = true;
}
update(frameDelta);
draw(frameDelta / simulationTimestep);
Hi @IceCreamYou ! I know it's been a couple of years since you've worked on this library, so no worries at all if you've moved on and don't have the time and/or interest to triage issues.
I love the example that ships with MainLoop. It really shows the value of using this library over many other solutions out there. I have a question for ya': do you happen to have another example handy that uses setBegin to process user input? It could be cool to demonstrate to folks best practices for how to use that method.
Thanks for reading!
Currently, when MainLoop.start()
is called, draw()
is called to render the initial state before any updates occur. This behavior could lead to users writing buggy code based on the reasonable expectation that begin()
and end()
are called before and after draw()
, respectively.
I think it would be fine to call begin()
and end()
before and after draw()
when MainLoop.start()
is called, but this might be considered a backwards-incompatible change. The alternative would be to document this behavior so that people know to manually call begin()
and end()
if needed.
Hello,
Im new to this topic and read your mainLoop article at:
https://isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing#fps-control
I really found it helpful and want to try things out with this.
However I was wondering why the update(simulationTimestep) function always gets called with the same value.
Earlier in your article we passed delta as parameter, where it sent the elapsed time since the last update.
Later in the article you said it has to be called with the same delta since we would get wierd rounding errors, which leads to moving through objects.
So thinking about it is there any use for this "delta"? The calculated velocity/movement will always be the same amount anyways.
Thanks in advance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.