🍂
Posts tagged with :fallen_leaf:
setInterval(() => {
your players move forward, ➡️
your enemies decide who to badger next, :threat:
some of your projectiles are just barely dodged, :flug:
and others smash satisfyingly into their targets :bonk:
}, 1000 / 60);
at each of these intervals, the server decides which of these occur, and which ... do not.
the difference is quite literally life and death for the denizens of your online realm:
if a player's input makes it to the server just after this update has occurred, they might just not move out of the way of the oncoming missile in time.
after one of these updates when the server has just finished making these vital decisions, it blasts relevant portions of them out to everyone who's connected to your server. (there's no point in updating you about something if it's happening on the other side of the map!)
meanwhile, in the player's web browser, these regular updates from the server are gradually applied, so the server's periodic contemplation is rendered as a seamless sequence of fluid movements...
unless those updates aren't really so regular at all ...
the contract that the server has with the client -- that the server will give the client evenly spaced out updates at regular-enough intervals so that the client can smoothly move between them -- isn't actually the same as the contract that setInterval
provides.
all setInterval(update, 1000)
means is that there will be ABOUT one thousand milliseconds between each of your updates, usually more. so if the actual interval ends up being 1009ms one frame, the next update won't be about 991ms later so that updates happen at regular one second intervals, it will be more like 1007ms later, so that now you're a total of 16 ms away from the contract you had with the client. this is known as drift.
at worst, this passes on all of the problems of using setInterval
for animation onto your game. but what if you're doing something fancier than just moving between the updates you get from the server as you get them? there's always going to be a random delay between what the server sends and what you get, anyway. it might make more sense to push the updates you get from the server onto a stack, and exactly as often as the server updates, pop the next set of positions off of that stack.
... except that if you're popping using one setInterval
on the client, and the server is pushing out those simulation ticks using a setInterval
of its own ... and each has a different drift ... they're quickly going to fall out of sync. if the server is drifting a lot, your stack might gradually accumulate thousands of positions that you just can't keep up with ... and if the server is drifting less than you are, you might run out of positions to show the player!
suffice it to say, it's super important to have a setInterval
that updates without drift. I threw together an implementation of one this morning. I call it a `tick`, since it works well for those simulation ticks I was talking about before. To test it, I went ahead and also made a drift visualizer. Here's a screenshot of it! The grey lines are spaced out at 0.5s intervals, and the first row of blue marks is made by setInterval
, while the other row of blue marks are made by my own tick
function. as you can see, they stick to the "wall clock time" a lot better than Interval
does, even when you leave it running for several minutes!
here's a screenshot! I'll post the source in the thread :)
fun fact: everything that moves is a CSS animation 😂
I didn't want to pollute the JS event loop, although now that I think about it, it might've been better to so that I can see how well my ticker keeps itself together in the presence of some event loop pressure.