Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

native timer callback #83

Closed
zwetan opened this issue Mar 24, 2016 · 3 comments
Closed

native timer callback #83

zwetan opened this issue Mar 24, 2016 · 3 comments
Labels
Milestone

Comments

@zwetan
Copy link
Member

zwetan commented Mar 24, 2016

based on Platform.h interface

    /**
    * function pointer for the timer callback function
    * @see setTimer()
    */
    typedef void (*AvmTimerCallback)(void*);

    class Platform
    {
    public:
        /**
        * Method to set a platform-specific timer for a callback after specific interval of time
        * This method should implement a one-shot timer
        * @param seconds number of seconds indicating the timer expiration
        * @param callback function that will be invoked when the timer fires
        * @param callbackData context data that will be passed as an argument during "callback" invocation
        * @return none
        */
        virtual void setTimer(int seconds, AvmTimerCallback callback, void* callbackData) = 0;
    };

implement a _setTimerListener that works like _setExitListener

eg.

private native static function _setTimerListener( seconds:int, f:Function ):void;

AVM2 static function setTimerListener( seconds:int, f:Function ):void
{
    _setTimerListener( seconds, f );
}
@zwetan zwetan mentioned this issue Mar 24, 2016
13 tasks
@zwetan
Copy link
Member Author

zwetan commented Mar 24, 2016

this setTimerListener need to deal with only one listener function

if passed null it should destroy the timer listener

if passed another function (while one is already set) should override the current function

if passed only a seconds argument

  • if function already exists, override the seconds with the new seconds arg
    eg. should allow to change the "timing" at runtime
  • if function does not exists, do nothing

example:

function myLoop():void
{
    // some loop
}

Program. setTimerListener( 10, myLoop ); //loop every 10sec

Program. setTimerListener( 30 ); //override, now function loop every 30sec

@zwetan zwetan changed the title natime timer callback native timer callback Mar 24, 2016
@zwetan zwetan added the runtime label Mar 24, 2016
@zwetan zwetan mentioned this issue May 28, 2016
@zwetan zwetan added this to the 0.5.0 milestone Jun 23, 2016
@zwetan zwetan modified the milestones: 0.4.2, 0.5.0 Jun 1, 2017
@zwetan
Copy link
Member Author

zwetan commented Jun 1, 2017

moved to earlier release but change in API

those native timers are extremely interesting
on POSIX mac/linux they use SIGALRM
on WIN32 windows they use timeSetEvent

they have seconds precision (could be improved to milliseconds precision)
but more importantly they are non-blocking

yep they do not block nor pause the program flow
which make them a good contender to implement an event loop

For now if we were implementing a loop based on a forever loop
eg. while( true ) { // do stuff }associated withsleep()` we are forced to call that at the end of the program

but with the native timers, they can be called on the first line of a program
and the following lines will continue to execute

@zwetan
Copy link
Member Author

zwetan commented Jun 16, 2017

change in design
no native timers but still "native" timers

problem with SIGALRM
is that you can define only one callback for the whole life of the process
other problem is that setTimer() (which alos use SIGALRM)
is used internally by the runtime to deal with code timeout
so we can not use it

problem with timeSetEvent
is that under Windows it creates another thread
and if we associate a callback to it we get the error
"GC called from different thread"

so what do we do? pretty much stuck there are we?

not exactly :)

a native timer is interesting because it could allow us to broadcast a tick()
at regular interval and so become our heartbeat for a main loop
but it happens that we will have to sleep() at the end of the code flow
anyway to be able to receive those ticks.

So, the change in design, is to simply use a "busy wait" loop
which will register those "native" timers and call their tick() function.

An obvious problem with that is as soon as you enter a "busy wait" loop
all the code flow after this loop is stuck and does not get executed

for example:

trace( "hello world" ); // will show
-> start "busy wait" loop // block forever
trace( "end of program" ); // will never show

Our solution to that is to add a native hook which will execute
this "busy wait" loop at the very end of our program,
even if the it is requested earlier in the code flow.

which gives:

trace( "hello world" ); // will show
-> start "busy wait" loop // does not block anymore
trace( "end of program" ); // will show too
-> native call actually run the loop // block forever

And, in fact, we already had this behaviour in the runtime with
the Program.atExit() call.

So, now we added 2 more hooks: atStart(), atLoop()
and at the end of the code flow those callbacks are executed
in the following order:

  • atStart()
    this hook allow us to execute code compiled by MXMLC
    where the abc ordering is slightly different
  • atLoop()
    this hook run the "buys wait" loop
  • atExit()
    this hook run all the defined callbacks for atExit()

and now here how we define our goAsync()

    Runtime.goAsync = function():void
    {
        /* Note:
           Only the primordial worker can execute the main loop
        */
        if( !Worker.current.isPrimordial )
        {
            return;
        }

        if( !Runtime.isAsync() )
        {
            Runtime._async = true;

            if( Runtime.loop )
            {
                Program.onLoop = function():void
                {
                    Runtime.loop.start();
                }
                Program.setLoopListener( Program.onLoop );
            }
        }

    }

But then how do we work with "native" timers ?

when Runtime.loop is running
it check for "native" timers and handle them like this

        private function _handleTimers():void
        {
            var current:uint = Program.getTimer();
            var i:uint;
            var timer:TimerClient;

            for( i = 0; i < _timers.length; i++ )
            {
                timer = _timers[i];

                if( timer.running )
                {
                    var elapsed:uint = current - timer._started;

                    if( elapsed >= (timer.delay * (timer.currentCount + 1)) )
                    {
                        timer.tick();
                    }
                }
            }
        }

the property _started and the method tick()
are both defined under the AVM2 namespace

eg.

    public class TimerClient
    {
        AVM2 var _started:uint;

        AVM2 function tick():void
        {
            //...
        }
    }

the namespace allow to "hide" those from public consumption
but allow us to access them internally by opening the namespace
use namespace AVM2;

The class TimerClient is like the flash.utils.Timer class
but instead of using events, use function callbacks.

End result are:

  • we have "native" timers in the sense that they will work without an event loop
  • those "native" timers are managed/synchronised by the "main loop"
    which is one level above the "event loop"
  • those timers will only work if the runtime is async
  • those timers can be used/defined anywhere in the code
    before or after you call Runtime.goAsync()

For now, we keep things "simple" and we let the user be responsible for managing the main loop

If you don't want your program to "run forever" you could define your own timeout

import flash.utils.*;

function runtime_timeout():void
{
    clearTimeout( timeout_id );
    Program.exit(0);
}

// a 15 seconds timeout
var timeout_id:uint = setTimeout( runtime_timeout, 15 * 1000 );

Another use case would be to automatically run the main loop only if timers are detected

function runtime_autoloop():void
{
    if( Runtime.loop.timerPending )
    {
        Runtime.loop.start();
    }
}

// check if you need to run the loop before he program exit
Program.atExit( runtime_autoloop );

@zwetan zwetan closed this as completed Jun 16, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant