Making a countdown problems

176 views
Skip to first unread message

Jorge Ramirez

unread,
Aug 26, 2014, 10:50:07 AM8/26/14
to androi...@googlegroups.com
When setting a timer both with setInterval() and setTimeout() the execution seems to be halted when I click on a button that updates some texts to the point of not advancing if I repeatedly touch the button.

The timed function updates a text with the seconds left for the turn to end, and as I said is completely halted when touching the other buttons.

Why is this happening? I'm guessing that for a more exact time function I better read the current time on execution but that would end on the timer text being not updated until the function is run and then it would skip numbers.

For my current game is not very important, but anything more real time would be impossible.

Thanks!

Dave Smart

unread,
Aug 27, 2014, 4:05:27 AM8/27/14
to androi...@googlegroups.com
Hi Jorge,

JavaScript is single threaded and all your code is running in the user interface thread ('GUI Thread').  That means when you use setInterval, setTimeout and when you click on buttons, they all have to share that single thread.  If you click on buttons very fast or do lots of operations inside your setInterval callbacks then you are going to get a slow down of the user interface and firing of timer callbacks as these things all fight for use of the GUI thread.  

You cannot rely of setInterval to give you exactly the interval you requested...it will simply do it's best. This means you need to use the Date() fnction to get the current time (in milliseconds if necessary) and do your calculations based on that.  

Note: Multi-threading does happen in AndroidScript but that is done underneath in Java when you call methods such as 'Play' on the media player object which then kicks off an internal thread and notifies you back in the GUI thread using callbacks.  You can also use the built in XMLHttpRequest object to do http requests on a background thread and have the results sent to you via callbacks.

Hope that helps

Regards
David

Steve Garman

unread,
Aug 27, 2014, 3:08:34 PM8/27/14
to androi...@googlegroups.com
If it's any help, the countdown timer I usually use looks something like:

var startTime;
var secsMax = 10
var txt;

function OnStart(){
    var lay = app.CreateLayout( "linear", "VCenter,FillXY" );    
    txt = app.CreateText( secsMax );
    txt.SetTextSize( 32 );
    lay.AddChild( txt );
    app.AddLayout( lay );
    StartTimer();
}
function StartTimer(){
  startTime = Math.floor(new Date().getTime()/1000);
  showTimer();
}
function showTimer(){
   var now = Math.floor(new Date().getTime()/1000);
   var secs = (now - startTime)
   if (secs >= secsMax){
      txt.SetText("0")
   }
   else{
     secs = ( Math.round(secsMax-secs));
     if (secs != txt.GetText()){
       txt.SetText(secs);
     }
     setTimeout(showTimer,300);
   }
}







Steve Garman

unread,
Aug 27, 2014, 3:22:37 PM8/27/14
to androi...@googlegroups.com
Sorry, that was from a test app I had never cleaned up.

There was a completely unnecessary Math.round in the else block.

Jorge Ramirez

unread,
Aug 27, 2014, 3:50:37 PM8/27/14
to androi...@googlegroups.com
Thanks!

For future references I found this blog post about the single threaded nature of JS http://ejohn.org/blog/how-javascript-timers-work/

Jorge Ramirez

unread,
Sep 23, 2014, 4:39:33 PM9/23/14
to androi...@googlegroups.com
I made a Timer object that takes into account the single threaded nature of javascript.

You create a new timer object with:

var myTimer =  new Timer( 5000, onUpdate, 100);

This means that I created a new timer that last 5 seconds (5000 milliseconds), that every time that it updates it calls the function onUpdate and that it (tries to) update the function once every 100 milliseconds. (if it fails to run in 100ms it will take into account the time passed).

Next you can start(), pause() or reset( newTime ) the timer:

myTimer.start(); //begins the execution of the timer

myTimer
.pause(); //pause the timer

myTimer
.start(); //restart the timer where it left

myTimer
.reset(); //pause the timer and reset into it's original time

myTimer
.reset( 5000 ); //resets the timer to 5000 milliseconds

With this I hope to squash all the bugs that where inundating my word game (it kept ticking when resetting a game) and to make a(nother of the many) multiple timer app :)

This is the full code and a sample to use the object:


//Called when application is started.
function OnStart()
{
//Create a layout with objects vertically centered.
lay = app.CreateLayout( "linear", "VCenter,FillXY" );
timer = new Timer(7000,function( time ){
        txt.SetText("Time: " + (time/1000).toFixed(2));
    },1000/20);

//Create a text label and add it to layout.
txt = app.CreateText( "Hello" );
txt.SetTextSize( 32 );
lay.AddChild( txt );
    btn = app.CreateButton( "Start Timer" );
    btn.SetOnTouch( startTimer );
    lay.AddChild( btn );
    
    btn = app.CreateButton( "Pause Timer" );
    btn.SetOnTouch( pauseTimer );
    lay.AddChild( btn );
    
    btn = app.CreateButton( "Reset Timer" );
    btn.SetOnTouch( resetTimer );
    lay.AddChild( btn );
    
    btn = app.CreateButton( "Calc Something Big" );
    btn.SetOnTouch( bigCalc );
    lay.AddChild( btn );

//Add layout to app.
app.AddLayout( lay );
}

function bigCalc(){
    for( var i = 0; i < 10000000; i++){
        Math.random()*Math.random();
    }
    
}



function startTimer(){
    timer.start();
}

function pauseTimer(){
    timer.pause();
}

function resetTimer(){
    timer.reset();
}

//timer object
function Timer(time, updateCallback, updateInterval){
    //recieve time in milliseconds, updateCallback updateInterval in milliseconds
    var self = this;
    this.running = false;
    this.lastTime = false;
    this.time = time || 10000;
    this.originalTime = this.time;
    this.updateCallback = updateCallback || function(){};
    this.updateInterval = updateInterval || 500;
    
    this.start = function (){
        if (self.running) return;
        self.running = true;
        app.ShowDebug("Timer running!: " + self.time);
        self.lastTime = Date.now();
        setTimeout(self.count(),0);
    };
    
    this.pause = function(){
        self.count();
        self.running = false;
    };
    
    this.reset = function( time ){
        self.running = false;
        self.time = time || self.originalTime;
        self.updateCallback( self.time );
    };
    
    this.count = function (){
        //countdown by the time that passed    
        //calc how much time passed since last execution
        if (!self.running) return;
        
        var newTime = Date.now();
        var timePassed = newTime - self.lastTime;
        self.lastTime = newTime;
        
        //update time counter
        self.time -= timePassed;
        
        if ( self.time <= 0 ){
            self.time = 0;
            self.updateCallback( 0 );
            self.running = false;
            return;
        }
        self.updateCallback( self.time );
        setTimeout(self.count, self.updateInterval);
    };
}


As I'm coding in javascript since just a couple month I would love to hear any correcting or better use of the code. Thanks!


Steve Garman

unread,
Sep 23, 2014, 7:05:04 PM9/23/14
to androi...@googlegroups.com
I think that's really excellent, Jorge.

Not only have you used the ideal way to update your screen without danger of blocking your code, you have also produced a very nice sample object that would make an excellent teaching aid.

I personally learned a couple of things from your code. I have also only been coding in JavaScript for a couple of months myself but I have used similar environments in other languages.

One thing I learned was that when I created multiple buttons on a layout and did not need to refer to them again, I do not need to push the original btn references into an array, presumably the layout is keeping a reference to them, stopping them being garbage-collected.
That should have been obvious to me as I was allowing the array to go out of scope anyway.

The other thing I gleaned from your code was a better understanding of callbacks. I have been struggling a bit with them. 
In fact I created a copy of your demo where the only difference was that I  removed the anonymous callback function and replaced it wit a named function 
    timer = new Timer(7000,timer_OnCallBack,1000/20);
just to convince myself that it was as straightforward as it appears

  function timer_OnCallBack( time ){
    txt.SetText("Time: " + (time/1000).toFixed(2));
  }



Jorge Ramirez

unread,
Sep 23, 2014, 7:20:27 PM9/23/14
to androi...@googlegroups.com
Not only have you used the ideal way to update your screen without danger of blocking your code

Thanks, it took time for the concepts to sink, but I finally understood "single thread". 

you have also produced a very nice sample object that would make an excellent teaching aid.

I don't think it's too simple, I would encourage everyone to make the codeacademy javascript course to learn javascript, there were not simple concept to grasp. 
 
One thing I learned was that when I created multiple buttons on a layout and did not need to refer to them again, I do not need to push the original btn references into an array

Oh yeah, that's me just being lazy :/ 

but actually yes, AndroidScript keep all objects in an internal array that's currently undocumented, you can notice on the debug screen on the WebIDE every object is referenced not as it variable name, but as G0, G1, G2 and so on. (I think that's the same).

Cheers!

Steve Garman

unread,
Sep 23, 2014, 7:33:49 PM9/23/14
to androi...@googlegroups.com
I did write sample object, not simple object. 
If I was using an adjective, it certainly wouldn't be simple, it would be elegant.

And to be fair, I also knew about the _map[ ] array. It just hadn't clicked as a concept.

Jorge Ramirez

unread,
Sep 24, 2014, 9:27:07 AM9/24/14
to androi...@googlegroups.com
hahaha thanks!
Reply all
Reply to author
Forward
0 new messages