2019.3468 - Timer fixes are terrible and unnecessary.

@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.

Hehe wasn’t meant to be a shot :slight_smile:

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.

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.

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.

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!

fwiw, sample “diagnostics” if you really want to know what’s going on at a “pseudo-subframe” level w new opt-in:

timer.allowIterationsWithinFrame = true print("BEFORE FIRST TIMER FRAME, current real time :", system.getTimer()) timer.performWithDelay(1, function(event) local now = system.getTimer() print("TIMER CALLBACK:") print(" event time (aka timer's start-of-frame time) :", event.time) print(" callback execution real time (aka 'right now'):", now) print(" real-time vs event-time delta (aka 'overhead'):", now-event.time) print(" event.source time (aka scheduled time) :", event.source.\_time) print(" real-time vs event-source-time (aka 'overdue'):", now-event.source.\_time) end, 10)

Hey guys. So, latest build contains my fixes. Now there’s timer timer.allowIterationsWithinFrame, defaulted to false. If put to true, you’ll get all the events as soon as possible. Default behaviour is to separate them by frame enter.

Both modes correct old behaviour of cropping timer to frame time.

While the display may only update at 60fps, I am hoping that calling audio.play will play sound sooner than the next upcoming frame.

This will finally enable accurate rhythmic triggering of sounds.

DEVS - So - if I call audio.play during a timer-triggered event, before the next upcoming frame, will audio.play truly play the sound at any desired number of milliseconds before the next enterframe event? If so, I’ll be VERY happy!

Gary

Hi Gary,

I think the topic of accurate audio timing has been beaten to death on this forum - several times I might add!

The documentation is pretty clear on this: audio works through OpenAL and works on a “best effort” basis, meaning you have very little chances of getting supertight timing.

Making timers, frames and other parts of code tighter and more accurate would not help out a lot, since all that would do is place the “play this sound”-command in a sort of buffer at an accurate time. The problem is the buffer of audio commands is read out at a “as soon as we have the processor time and resources” pace.

More in-depth information here:

https://developer.coronalabs.com/partner/audionotes

For what it’s worth: I think I have followed ALL the threads on tight audio, without success. I have a history as an audio producer/engineer of sorts, and have tried to no end to create some sort of sequencer in Corona. I’ve tried every trick in the book, including having 16 variations of each sample, each with 1,2,3 etc… milliseconds of silent spacing in front of the sample. My personal opinion: nothing works (well), and it is absolutely impossible to get timing to sound supertight.

Thanks thomas6 - tried those strategies too… had a major thread going on back in 2012…  

If there was just a way to create an ongoing audio-stream and pre-mix audio into it at the exact desired sample and/or system timer position it would be a boon.

Audio latency always seems to be the last thing anyone considers vigorously tackling with both technology and software!

Cheers!

Gary

Yup, too bad as I had a great game idea and prototype depending on tight audio…

It was called “Beat Boy”, and it was a sort of endless runner where your running speed defined the pace of the drumtrack music. If you got up to speed and got all the jumps right, you would have a nice continuous drumbreak rolling, with all of the bonus sounds in sync.

Unfortunately, as you can probably guess, the game mechanics worked, but the audio was so jittery it ruined the whole flow-experience.

These changes should really be documented at https://docs.coronalabs.com/api/library/timer/performWithDelay.html for future sake.

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.

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

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.

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.

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

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?

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.

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.