Android Tutorial Running simultaneous tasks at different speeds

Introduction:

I came across this challenge about one year ago, when I was trying to integrate sprites that were designed to be displayed at 24 FPS on a 60 FPS game loop.
For the sake of simplicity, the subject of sprite animation won't be covered at all in this tutorial.

Although this tutorial may seem videogame focused, remember you may apply this concept in any kind of project.


The Main Cycle:

The Main Cycle is usually a Timer but it can been anything kind of loop, for example, the LibGDX Render event.
60 cycles per second can described either as 60Hz (Hertz) or 60FPS (Frames per Second).
It's pretty much the same, nothing more than two different terms describing the cycle frequency.
The term FPS is usually applied in graphical focused cycles, such as the ones found in game engines or video players.

Setting a timer to run at this speed it's a pretty simple thing to do.
Since Timers are set in milliseconds, the code will be: myTimer.Initialize("myTimer", 1000 / 60).
The reason I'm using the this value (60) is pretty much arbitrary.
60Hz is the usual screen refresh rate, unless you have a 3D-TV (120Hz or more).
If you want your cycle to run "as fast as possible", set the Timer interval to 1 millisecond.
myTimer.Initialize("myTimer", 1)

Now that we have our main cycle set-up, let's give it something to do.
B4X:
Sub myTimer_Tick
    Move_Player
    Move_Enemy
    Collision_Detection
    Animate
    Display_Current_Frame
    Upload_Score_To_Online_Database
    Retrive_Current_Highscores_From_Online_Database
End Sub

Imagine your game sprites were designed to be displayed at 24FPS.
Can you already spot the problems we could encounter when running this code?
Not only the sprite animation would seem to be played in "fast-forward", accessing your online database dozens of times per second would most likely overload your server and/or game with requests.

To solve this, we need to establish some rules.
1. The animation function shall only be called 24 times each second.
2. The current score shall only be uploaded (in background) every 10 seconds.
3. The current highscores shall only be retrieved (in background) every 60 seconds.

Time-stamping:

To enforce the rules we established above, we'll use time-stamping.
Time-stamping is the act of saving into a variable the exact time (unix time) something was done.
Your timestamp variable should always be global (unless you're using C/C++ and pointers) and its type must be Long.
B4X:
Sub Process_Globals
    Dim animationTimestamp  As Long
    Dim dbUploadTimestamp   As Long
    Dim dbDownloadTimestamp As Long
End Sub

Let's start with the Animate function.
In order to time-stamp it, all we have to do is add the following line of code:
B4X:
Sub Animate
    animationTimestamp = DateTime.Now
End Sub
Alright! Now, every time the functions is called, its time-stamp will be overwritten with the current value of DateTime.Now.
From this point on we are prepared to establish a way of enforcing our rules.​


Enforcing the rules:

Before we proceed, let's create a custom Type (struct) in Process_Globals to keep things more organized.
B4X:
Type AccessControl(Timestamp as Long, Delay as Int)
Dim acAnimate as AccessControl
B4X:
acAnimate.Delay = 1000 / 24 ' = 41 milliseconds
We're now ready to add our secret sauce to our Animate function.
B4X:
Sub Animate
    If (DateTime.Now - acAnimate.Timestamp) >= acAnimate.Delay Then
        acAnimate.Timestamp = DateTime.Now
        'The animation code goes below
        ...
        ...
        ...
    End If
End Sub
An alternative way to achieve the same result would be using a Return instruction:
B4X:
Sub Animate
    If (DateTime.Now - acAnimate.Timestamp) < acAnimate.Delay Then Return
    acAnimate.Timestamp = DateTime.Now
    'The animation code goes below
    ...
    ...
    ...
End Sub

For the Database access functions, well use exactly the same code, we only need to change their delay values:
B4X:
acDBUpload.Delay   = 10000 '= 10 seconds
acDBDownload.Delay = 60000 '= 1 minute

The Main Cycle code will stay the same, but it would be a good practice to add some comments.
B4X:
...
myTimer.Initialize("myTimer", 1000 / 60)             'Timer Speed = 60 Hz, Timer Interval = 16
...

Sub myTimer_Tick
    Move_Player                                      'Speed:     60 Hz, Delay = 16 ms    - Called every cycle
    Move_Enemy                                       'Speed:     60 Hz, Delay = 16 ms    - Called every cycle
    Collision_Detection                              'Speed:     60 Hz, Delay = 16 ms    - Called every cycle
    Animate                                          'Speed:     24 Hz, Delay = 41 ms    - Called 24 times every second
    Display_Current_Frame                            'Speed:     60 Hz, Delay = 16 ms    - Called every cycle
    Upload_Score_To_Online_Database                  'Speed:    0.1 Hz, Delay = 10000 ms - Called every 10 seconds
    Retrive_Current_Highscores_From_Online_Database  'Speed: 0.0166 Hz, Delay = 60000 ms - Called one time per minute
End Sub


Conclusion:

I hope you enjoyed reading this tutorial and that it proves useful for you in the futures.
Please don't hesitate in correcting me if I got anything wrong.
Thank for reading. :)

@Erel, if you see fit, please move this thread into the Teaching section.
 
Last edited:

inakigarm

Well-Known Member
Licensed User
Longtime User
Good job wonder !!

One question from you code, how many ticks are 41 ms ? 41 tickS ??

B4X:
(DateTime.Now - acAnimate.Timestamp) >= acAnimate.Delay
 

wonder

Expert
Licensed User
Longtime User
Good job wonder !!
Thank you!! :)
One question from you code, how many ticks are 41 ms ? 41 tickS ??
It depends on your timer interval. Let's see:
B4X:
---------------------------------------
Ticks = Time (ms) / Timer Interval (ms)
---------------------------------------
Timer Interval       Time        Ticks
 1 ms (max speed)    41 ms       41
16 ms (60 FPS)       41 ms       2.56
41 ms (24 FPS)       41 ms       1
 1 s  ( 1 FPS)       41 ms       0.041
---------------------------------------
Only remember that there is no such thing as half-a-tick.
A tick is a function call, in this case, to your timer_tick code.
Your code is either called (true) or not called (false).

Using this method will always produce the same results.
Your functions minimal delay (acAnimate.Delay) will be respected, regardless of your timer interval.
 
Top