Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

2019.3468 - Timer fixes are terrible and unnecessary.
Started by aexol Mar 05 2019 01:37 AM

31 replies to this topic
[TOPIC CONTROLS]
Page 1 of 2 1 2
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

aexol

[GLOBAL: userInfoPane.html]
aexol
  • Observer

  • 15 posts
  • Corona SDK

Before this daily build firing this code:

timer.performWithDelay(
    5000,
    function()
        print('done')
    end,
    1
)
local i = 0
timer.performWithDelay(
    10,
    function()
        i = i + 1
        if i == 500 then
            print('done2')
        end
    end,
    500
)

Would result in printing both 'done' and 'done2' at the same time.

In 2019.3468  because timer is now bound with framerate 'done2' is printed few SECONDS!!!! later. That denies the purpose of the timer. Now synchronizing any data based on timer is impossible without desynchronization. Also there are problems with using timers and physical bodies because of being bound to frames.

 

Other issue with having timers bound to frame rate is desynchronization if framerate is unstable/framedrops occur.

 

Please take a look at this issue and bring back old timer implementation.



[TOPIC: post.html]
#2

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

Sorry, we discovered that firing timer more than once per frame broke a lot of actual existing games. Inserting timers with periods more than one frame would accurately work. We could add a flag or a setting, that timer would work as you suggest, but it should not break existing games.



[TOPIC: post.html]
#3

aexol

[GLOBAL: userInfoPane.html]
aexol
  • Observer

  • 15 posts
  • Corona SDK

Having additional flag or a setting seems like a reasonable solution.



[TOPIC: post.html]
#4

Michael Flad

[GLOBAL: userInfoPane.html]
Michael Flad
  • Contributor

  • 225 posts
  • Corona SDK

Sorry, we discovered that firing timer more than once per frame broke a lot of actual existing games. Inserting timers with periods more than one frame would accurately work. We could add a flag or a setting, that timer would work as you suggest, but it should not break existing games.

 
Is this really true? I.e. wasn't the previous way timers were fired, the one for a long time, so isn't there a big chance to break much more existing apps, once they get rebuilt with a new version of the SDK and they may break in very subtle ways not noticed immediately or dependent on the devices or levels or whatever will be tested?
 
If anything, such a fundamental change which is actually a workaround for badly written code, should be the one to opt in by a flag. If anyone creates a repeating timer with frequency higher than the actual framerate, this code should handle multiple triggers per frame as it's what it actually requested. If one wants a trigger per frame, that's what enterFrame is for.


[TOPIC: post.html]
#5

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Vlad, is there a place where there is a bit more information on what exactly has changed than what is mentioned in the daily build notes?

 

It seems like this is a substantial change and deserves or needs more attention than this.

 

I for one am wondering how timers used to work, and how they work now.



[TOPIC: post.html]
#6

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

Hello, everyone. You are right, expanded explanation is due. Sorry for not providing it in full. Here's what happened:
1. On September 6 I accepted pull request which made recurring timer not trim time to the beginning of the frame. 
2. As unintended consequence, it enabled behaviour that timers with interval less than a frame would fire multiple times per frame.
3. We got several complaints, because apparently many people was relying for timer working similar to frame count.
4. In commit at Feb 26 I changed behaviour of timer so it: didn't fire timers more than once per frame and did not crop time: issue which was supposed to be fixed in (1).
5. Today, I made a commit which would introduce "timer.allowIterationsWithinFrame". Flag, if set to true, timers created after it would have behaviour similar to that which we had between (1) and (4).
 
I will wait for comments until tonight, and if there's no objections I'll push changes to daily build. Please, share your thoughts on this solution.
 
 
EDIT: Come demo

 
timer.performWithDelay(
    5000,
    function()
        print('done')
    end,
    1
)
 
timer.allowIterationsWithinFrame = true
local i = 0
timer.performWithDelay(
    10,
    function()
        i = i + 1
        if i == 500 then
            print('done2')
        end
    end,
    500
)
timer.allowIterationsWithinFrame = false
 
 
local j = 0
timer.performWithDelay(
    10,
    function()
        j = j + 1
        i f j == 500 then
            print('done3')
        end
    end,
    500
)
 

This code would have done and done2 printed about at the same time. and done3 some time later

[TOPIC: post.html]
#7

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

I'll start with your point 1. Was this a request done by many people? Because if not, I would say:

 

" Things were working just fine and nobody was confused. Can't we just go back to the way it was working before? ".

 

If not, I would say that the following should be the absolute golden rule:

A timer works according to the time you specify. Not more, not less.

 

If you let a timer fire more than once per frame, it should count as multiple times. If you don't want this, either work using the enterFrame event, or set your timer to more than "1000 milliseconds divided by 60, or 30". People relying on timers set shorter than this time are asking for trouble either way, as game logic will not follow faster.

 

I would really not like Corona to take a sloppy approach to coding just because some people use the wrong coding approach. The right coding approach in 99% of use cases, to be blunt, is using enterFrame events and deltaTime to calculate increments. Relying on a timer with a 60fps frequency and thinking this will work better is poor understanding of the Corona runtime, in my opinion.

 

Am I making any sense here?



[TOPIC: post.html]
#8

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Sheesh. This brings back nightmares from my MacroMedia Director days, when my game logic ran at a different framerate from my "display" logic.

 

To be honest, this timer issue really brings up a lot of questions... For example:

 

1) What happens when you call a function five times per frame, updating a sprites position?

2) How granular can you go with the timer function, approximately? I know this will depend on device etc..., but still I'm curious.

3) Also, why would anyone want to go down to a higher frequency than 30 or 60fps, if your display can't keep up? Do people think things will go faster or smoother using a really short timer?

4) In a similar vein, but even stronger: why the hell would you want to use a timer similar to the frame rate? If it's similar, just use the frame rate!

 

It really boggles the mind to me. Can't think of any good reason to use supershort timers, that can't be better supported with a game loop tied to the frame rate.



[TOPIC: post.html]
#9

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

We try not to break backwards compatibility. We didn't have a public release in a while, and it was behaviour for previous dozens of public releases. Many people use only public releases, and even without that we got several complaints that behaviour changed. Breaking existing games because something is "more right" is very bad approach to making public APIs, we try to help our developers support existing games, not make them search for changes we introduced.

 

I, personally, do not understand point in timers recurring more than once per frame, I don't think this is a good pattern to follow due to performance impact. Calculations which happen more than once per frame are inherently wasteful, since their result can not be displayed to user. Hence, default behaviour is not to allow timers to work like that. But, if you really want timer which fire more than once per frame, you can now opt-in.

 

I don't really see right/wrong division as you show it. There's code which was working for literally a decade, that makes it right im my view, and change which allowed timers to fire more than once per frame was a breaking change.



[TOPIC: post.html]
#10

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Okay, I found a situation where a supershort timer would be used: a bonus score counter. But that's not a situation where the accuracy or trimming to start of frame would be an issue.

 

So I'll rephrase: I can't think of any good reason to use supershort timers, where the trimming to beginning of frame would be an issue. If you supershort timers are really used for syncing things, you're doing it the wrong way in my opinion.



[TOPIC: post.html]
#11

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

thomas6, what are your suggestions? I don't see value in short recurring timers too, but it doesn't meant other people don't see it as well. We're not all coding superstars here, and some problems may be easier solved with short timers, than lots of coding. It may be suboptimal from performance point of view, but easier to make and less error-prone. For example situation when your timer period is not hardcoded, but result of something else. Like you get a coin every N, and N can change, like player can upgrade something, etc. It is not best way to implement it with timer, but it is certainly easiest, and easiest often meas less bugs.

 

Also, please, let's keep civil tone. About your questions - you can get answers to all of them trying previous daily build. Answers are pretty straightforward. Timer is not a magic box, they are called consecutively, and really simple Lua code I linked before.

 

I don't see harm in making short timers behaviour opt-in. What do you suggest?



[TOPIC: post.html]
#12

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

I'm getting even more confused, Vlad.

 

Didn't you / Corona just break backwards compatibility to make things work "more right", with the latest changes to how timers work?



[TOPIC: post.html]
#13

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Hi Vlad,

 

I'll let other chime in here, since I have to be honest: I had not noticed any difference in my own projects.

 

That being said, my suggestion is twofold:

 

1) In general, things should happen in the most logical, intuitive and predictable way. That means, if you set a timer to run a specific function 100 times per second, that function should run a 100 times per second. I would go for temporal accuracy, since the thing is called a timer, after all, meaning that it is not trimmed to the start of a frame. I take it that when you trim to start of frame, and a function was called 5 times since the last frame update, this function would then run 5 times at the start of the frame? This would mean a larger single performance hit each frame, instead of 5 smaller ones during the duration of the frame, which does not seem positive for performance reasons, and seems to be an argument against trimming to frame start.

 

2) Whenever possible, making changes in how the Runtime works, allow for backwards compatibility in some way, like your opt-in suggestion.



[TOPIC: post.html]
#14

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

For the record, and after that I'll really leave this thread to others, I do code using the enterFrame event for practically everything I do out of personal preference, and then evaluate the event.time parameter to decide what should happen in the new frame.

 

Your example of "Like you get a coin every N, and N can change" would then be implemented by checking each frame, if the time passed between the last coin and the new frame time is bigger then N, and if so, spawn a new coin.

 

I have a very strong feeling you know exactly what I'm trying to explain, Vlad, so I'm not posting this to lecture or prove myself right, but rather as a pointer for people looking for how to code stuff like this. Peace out! :)



[TOPIC: post.html]
#15

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

I see there's some confusion. Let me summarize:

 

Default behaviour of previous Public Release 3326 and next daily build with suggested changes would be same, except timers would be a little bit more precise *. Timers would not fire more than once per frame as before.

 

There would be new ability to use timer.allowIterationsWithinFrame = true, and get behaviour when timers would fire more than once per frame.

 

*) we had a "rounding error" in timers, and all of this is really a consequence of trying to fix it.



[TOPIC: post.html]
#16

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Sounds good! Thanks!



[TOPIC: post.html]
#17

Michael Flad

[GLOBAL: userInfoPane.html]
Michael Flad
  • Contributor

  • 225 posts
  • Corona SDK

@vlads
Thanks for the additional details about the history of the changes.
 
Using the flag solves the problem in the best possible way. I actually think that the correct way to handle timers is the September change. Missing timers due to longer frametimes is just the same as tunneling in collisions, it's a bug and cropping time is the same just in the other direction.
Firing just once a frame as well as cropping the time after the timer fired was an arbitrary decision no different from firing only ever 3 frames or at least once a frame and nothing a framework should do/decide.
 
But given it was the behaviour for years there's also no way to expect all devs to update their code and data. Knowing what happend, I'm actually surprised there wasn't a raging mob around the forums back then :)


[TOPIC: post.html]
#18

davebollinger

[GLOBAL: userInfoPane.html]
davebollinger
  • Corona Geek

  • 1,317 posts
  • Enterprise

5. Today, I made a commit which would introduce "timer.allowIterationsWithinFrame". Flag, if set to true, timers created after it would have behaviour similar to that which we had between (1) and (4).
 

 

So, just for clarification:  if set to true, you'd still get the buggy version's ~16 callbacks next frame?  (assuming 1ms interval at 60fps)

 

I can't see any reasonable use for that.  Perhaps an alternate way of addressing it might be to add an additional property of the event indicating how many "implied" cycles were skipped.  So if someone thought they needed 16 callbacks just to add 16 to a counter, they could instead do:

myCounter = myCounter + event.impliedCyclesSkipped -- or whatever the property name, who cares

If chose to do that, then just make sure it's clear whether the cycle count is of only those truly skipped (ie, does NOT include the current actual callback) or of how many cycles elapsed since last (ie, DOES include the current actual callback), just a potential "off-by-one" issue.  (my pref would be a count of only those that were truly skipped, so that it'd be normally zero for all intervals > frame time)

 

fwiw



[TOPIC: post.html]
#19

Michael Flad

[GLOBAL: userInfoPane.html]
Michael Flad
  • Contributor

  • 225 posts
  • Corona SDK

So, just for clarification:  if set to true, you'd still get the buggy version's ~16 callbacks next frame?  (assuming 1ms interval at 60fps)

 

I can't see any reasonable use for that.  Perhaps an alternate way of addressing it might be to add an additional property of the event indicating how many "implied" cycles were skipped.  So if someone thought they needed 16 callbacks just to add 16 to a counter, they could instead do:

 

This seems to be a pretty interesting topic :)

 

Adding up a simple counter is obviously a stupid usage, roughly as stupid as setting up a 1ms timer expecting it to fire exactly once every frame, relying on an implementation detail/sideeffect, when there's an enterFrame event existing just (and only) for this exact use case.

 

I honestly can't see any reasonable reason to not fire 16 timers if I setup a timer to fire every 1ms and since last frame 16ms have passed because that's just exactly what I requested. When I create 100 rects I also expect to get 100 rects and not 17 because for whatever reason someone thought 17 would be enough.

 

The counter of course is a decent option and optimization.



[TOPIC: post.html]
#20

vlads

[GLOBAL: userInfoPane.html]
vlads
  • Contributor

  • 654 posts
  • Corona Staff

davebollinger - getting multiple events in same frame is opt-in behaviour. By default you get one call per frame, as I wrote in summary above.

 

There was also hypothetical use for such construction.

 

And we will not skip iterations without reducing counter. That's just bonkers. If you asked for 20 iterations, you'll get 20 iterations. Flat would only change if they would be in same frame or 20 frames.



[TOPIC: post.html]
#21

davebollinger

[GLOBAL: userInfoPane.html]
davebollinger
  • Corona Geek

  • 1,317 posts
  • Enterprise

@Michael Flad - don't shoot the messenger.  (besides I don't use timers, so I don't have any personal "stake" in it)

 

The routine has always "lied" to you, in the sense that if you asked for a 1ms timer, but it can only fire per-frame, then something HAS to give -- so, whether you get one callback per frame or 16.67 queued up and fired sequentially, both are technically "wrong".

 

>>because that's just exactly what I requested

 

Ah, but it isn't what you requested, not exactly - you requested that it fire every 1ms, and clearly it cannot since it's frame-based.  The only remaining question is what it should do on that next frame to attempt to "apologize" for that quantization of time:

  - callback once and just ignore the missed intervals? (historical behavior)

  - callback 16 times "right now" to make-up for missed intervals? (temporary regression behavior, new opt-in behavior)

  - callback once and just "indicate" the number of missed intervals?

  - other?

 

On several occasions I've remarked in the forum that "timer.performWithDelay" is better understood if imagined to be named "timer.performOnNextFrameAfterAtLeastThisMuchDelay" to better match its actual behavior (or, at least, it matched at the time of writing, recent changes notwithstanding).  Except that no-one wants to type all that, of course!

 

IMO, the long-standing pre-3326 behavior of timer seemed fine if you just "understood" what it was actually doing, and didn't expect it to act like a real-time asynchronous thread.



[TOPIC: post.html]
#22

Michael Flad

[GLOBAL: userInfoPane.html]
Michael Flad
  • Contributor

  • 225 posts
  • Corona SDK

Hehe wasn't meant to be a shot :)
 
And I don't use timers for anything controlling the logic of my games either as I prefer it to be as deterministic as possible and get the same results in each run (which also means, I tend to not use dt based logic if I don't need to), IMO the only way to seriously be able to debug code - so no stake on my side either. I just noticed the posting and thought, especially before @vlads post about the history of changes, it's a pretty serious change that might break lots lo existing logic as many users seem to use timers a lot, including to control their game logic.
 
Of course, no timer will ever be able to satisfy a generic request as it would need to support an infinite small frametime.
 
You also have a point with the way you see how the timer should work, there's probably no way to make it perfect for everyone.
 
What I don't agree with is the pre September version throwing away the remaining time after a repeating timer fired. It means that a repeating timer defined to trigger every 1.1x frames triggers ever second frame when it actually should only skip ever 10th - that's a very different result.
 
In the end I think the very best option would be to add some flags to the options table when requesting a timer, one to either get a count of or actually send missed events and one to throw away or keep the remaining time. Using defaults to match the pre September version as I guess it's still what most existing code is relying on.


[TOPIC: post.html]
#23

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Can someone give a definitive answer to the following?

 

Do timers in Corona have the ability to run their functions "sort of" when they are called, independent of the display frame rate? (so, multiple times, inbetween frames, at intervals).

 

Or is all timer and other logic (like touch events) handled per frame, each time a new frame starts?

 

It might be a silly question but I don't know if anyone knows this all for sure.



[TOPIC: post.html]
#24

Michael Flad

[GLOBAL: userInfoPane.html]
Michael Flad
  • Contributor

  • 225 posts
  • Corona SDK

It all happens on a per frame basis, everything else is really hard and for actually no real benefit - you'd see any change after the frame anyway and if you want your simulation to be finer grained than a single frame you're better in handling this on your on at a very defined point in time rather than code that might interrupt your own code at will.

 

If anything, my suggestion above with repeated timer events getting a field with the actual time they were meant to fire in a perfect environment (with infinite short frametimes), would help to f.i. calculate inbetween frame progress/movement etc. related to your ideal timer.

But once you really need this, in general, it's much better to handle all of it on your own, it's very simple if you need timers for a very specific reason rather than writing a general purpose solution meant to support everything us developer may come up with.



[TOPIC: post.html]
#25

thomas6

[GLOBAL: userInfoPane.html]
thomas6
  • Contributor

  • 950 posts
  • Corona SDK

Hi Michael, thanks for the clear info!

 

And thanks for the tips, although I have to say I was already in full agreement. I am a 100% enterFrame gameLoop and deltaTime coder, so I have zero need for accurate timers. Woohoo!




[topic_controls]
Page 1 of 2 1 2
 
[/topic_controls]