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

implement events (google code Issue 4) #46

Closed
zwetan opened this issue Dec 29, 2015 · 14 comments
Closed

implement events (google code Issue 4) #46

zwetan opened this issue Dec 29, 2015 · 14 comments

Comments

@zwetan
Copy link
Member

zwetan commented Dec 29, 2015

https://code.google.com/p/redtamarin/issues/detail?id=4


implement the full event stacks based on W3C

goal:
have the same event system than Flash/AIR

but also allow to add a little more


so we gonna do that but cheat a little ;)

more details a bit later


@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

what we currently have (alpha quality but working)

Runtime.goAsync()
which create an instance of EvenLoop
into Runtime.loop

see shell.async.EventLoop

quickly implemented only in AS3
see CoreEventLoop

we basically use an infinite loop

while( true )
{
    // loop
}

that we pause for N seconds
sleep( 1000 / frequency )

we tested it with timers and it works fine

while( running )
{
    elapsed = getTimer() - started;

    if( timerPending )
    {
        handleTimers();
    }

    sleep( 1000 / frequency );
    frame++;
    tick();
}

@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

following this logic we can add more to obtain a more complex loop

while ( sleepuntil( nextEventTime  ) OR externalEventOccured() ) {
  ...
  if ( timerPending ) { // AS2 Intervals, AS3 Timers
    handleTimers();
    nextEventTime = nextTimerTime();
  }
  if ( localConnectionPending ) {
    handleLocalConnection();
    nextEventTime = min(nextEventTime , nextLocalConnectionTime());
  }
  if ( videoFrameDue ) {
    decodeVideoFrame();
    nextEventTime = min(nextEventTime , nextVideoFrameTime());
  }
  if ( audioBufferEmpty ) {
    refillAudioBuffer();
    nextEventTime = min(nextEventTime , nextAudioRebufferTime());
  }
  if ( nextSWFFrameDue ) {
    parseSWFFrame();
    if ( actionScriptInSWFFrame ) {
      executeActionScript();
    }
    nextEventTime = min(nextEventTime , nextFrameTime());
  }
  if ( needsToUpdateScreen ) {
    updateScreen();
  }
  ...
}

have cleaner events class, and all would be working fine
speed would not be the issue
yep even in pure AS3 as we don't really render stuff on screen

but it would work only if you call Runtime.goAsync()

that means if you want to use flash.net.Socket for convenience
or reusing a library from Flash/AIR, the events will not fire
without that call to goAsync()

it would be nice to make it smarter
eg. be able to use flash.net.Socket
without the need to call goAsync()

and have the program wait
ever forever or for a timeout
that some events fire

@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

so we focus on the command-line
contrary to Flash/AIR we don't really have
to optimise the GUI rendering
but we have our own set of problems.

With Flash/AIR you would use workers
to move an heavy processing into another thread
in order to keep a smooth reactive UI,
so why do we need workers on the command-line
where there is no GUI ?

Inside the command-line we can block forever
a command-line program can block litterally for hours
if not days without spawning a warning
it can happen either

  • inside an 'infinite loop'
    eg. while( true ) { //do stuff }
  • by using a blocking C function
    for ex: recv(), accept(), etc.
  • calling another executable
    and wait for it to return

even worst, depending where you "block forever"
some span of code could end up dead (eg. never have a chance to execute

for ex:

trace( "hello world" );

var count:uint = 0;
while( true )
{
    if( count > 10 )
    {
        exit( 0 );
    }

    count++;
}

// dead code
trace( "this will never happen" );

goAsync() will react the same

// this code will execute

Runtime.goAsync();

// this code may never execute

we can document
"if you want to use events, use goAsync() in the last line of your code"
but with big programs, few hundred classes etc.
this could become trickier to explain where to put this goAsync()

@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

we could use the boot process
to automatically run goAsync() in an atExit()
if the pool of events is not empty

or at least provide some configurations for that

for ex:

function autoAsync():void
{
    if( Runtime.events.length > 0 )
    {
        Runtime.goAsync();
    }
}
Program.atExit( autoAsync );

// from there write your code
// if somewhere you use only one addEventListener()
// then onExit() your program will automatically run goAsync()

@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

use what we learned from spitfire

the source code is not open yet but we learned

  • how to build a more efficient event loop
  • how to mix event loop and workers
  • how to communicate between workers

how to build a more efficient event loop

sleep( 1000 / frequency ); is no good

we learned that an "infinite loop" can be controlled
see

- no sleep
 2014009.99 fps
 1999529.47 fps
 1992650.34 fps
 1943541.45 fps
 2004844.15 fps

- sleep( 1 ) / 1 millisecond
 799.2 fps
 801.19 fps
 802.19 fps
 806.19 fps
 802.19 fps

- sleep( 10 ) / 10 milliseconds
 88.82 fps
 87.38 fps
 87.38 fps
 86.91 fps
 88.64 fps

- sleep( 20 ) / 20 milliseconds
 44.77 fps
 43.3 fps
 43.73 fps
 42.91 fps
 42.96 fps

- sleep( 27 ) / 27 milliseconds
 33.69 fps
 32.38 fps
 33 fps
 32.22 fps
 32.94 fps

- sleep( 30 ) / 30 milliseconds
 31.46 fps
 29.94 fps
 30.12 fps
 30.36 fps
 30.42 fps

- sleep( 40 ) / 40 milliseconds
 24.29 fps
 23.12 fps
 23.41 fps
 23.18 fps
 23.03 fps

from that we could deduce that if we sleep at least 1 second
then our mac frame per second is around 800

and if during each loop you keep track of the total number of frames
and the time it took to execute the loop

you can calculate the frame per second (FPS)

        private function _calcFPS( frames:uint, time:Number ):Number
        {
            var fps:Number;
                fps = (frames/time) * 1000;
                fps = int(fps*100)/100;
            return fps;
        }

and define the minimum desired sleep time based on this FPS

        public function set minDesiredFPS( value:uint ):void
        {
            if( value > 0 )
            {
                _minDesiredFPS = value;
                _sleepTime = (maxFramePerSecond / (_minDesiredFPS - adjustFrame));
            }
        }

eg. if you want a 24 FPS, you need 800 / 24 = 33.33, about 33.33 milliseconds of sleep time
not perfect, as you may need to adjust the frame, remove a bit

how to mix event loop and workers

If each event loop can control its own FPS that means when we use workers
we can have thins running in parallels, no more need to wait to exit an event loop to run

worker1 run at 24fps and child worker2 run at 8fps etc.

but there we have another problem as we need to synchronise the threads (or workers)
yep that means we need to use mutex and lock/unlock

here what happen with a simple trace() call
without locking/unlocking Mutex

9.77 fps
sleepTime = 100
loop child 0
loop child 1
loop child 2
loop child 3
loop child 4
loop child 5
9.71 fps
sleepTime = 100
lloooopp  cchhiilldd  67

loop child 8
loop child 9
loop child 10
loop child 11
loop child 12
loop child 13
9.72 fps
sleepTime = 100

see this line lloooopp cchhiilldd 67
it's what happen when 2 different threads
try to call trace() at the same time

By locking the mutex you assure that
that a different thread will not try to do
a call at the same time

for a trace() call you would do that

_mutex.lock();
trace( message );
_mutex.unlock();

that way you are sure everything run smooth and synchronised

9.74 fps
sleepTime = 100
loop child 0
loop child 1
loop child 2
loop child 3
9.78 fps
sleepTime = 100
loop child 4
loop child 5
loop child 6
loop child 7
hello
>> background <<
Child Worker 8
state running
mutex [object Mutex]
loop child 8
9.7 fps
sleepTime = 100

how to communicate between workers

Remember with redtamarin we do not have events by default
we can not simply use addEventListener() with our workers
we have to implement that and so to communicate between workers
we need another way

To do that we defined a WorkerConnection class modelled after LocalConnection
yep old school ;)

our very dumb implementation reuse getSharedProperty() and setSharedProperty()
with a little bit of parsing

each property contains a command array
arg0 is the function name to call on the client
if there are more args, arg1, arg2, etc. are the function arguments

you use it this way

  • create a WorkerConnection that you attach to a Worker with a shared Mutex
    in general you will use the main worker (the primordial) but it could be any worker
  • when you need to call a function on the client
    WorkerConnection.send( "foobar", "arg1", "arg2" )
    • and in the event loop you call
      WorkerConnection.fetch()

it is a basic FIFO (First In, First Out)
send()

  • mutex.lock()
  • always read first the "commands" property
  • add to it
  • save it
  • mutex.unlock()

fetch()

  • mutex.lock()
  • read the "commands" property
  • if not empty, loop trough it
  • execute each index, then delete it
  • mutex.unlock()

it's basic, only simple types would be supported (eg. don't pass around a ByteArray)
later a more advanced implementation could AMF serialise/deserialise those arguments
but it allows different workers to communicate with each other

@zwetan
Copy link
Member Author

zwetan commented Dec 31, 2015

much more work is needed but so far we need to focus on

  • implementing a clean and complete event dispatcher in AS3
    similar to flash.events.EventDispatcher in old test/ performance/ canaries/ simpleflexapputil/ playershell.as
  • have different EventLoop implementations
    • a basic one in AS3
    • one using 1x worker
    • one using multiple workers
  • have a playershell that setup everything to deal with events
    and then load your program.abc or program.swf

@ajwfrost
Copy link

ajwfrost commented Mar 3, 2016

Hi
I'd added an item to your forum to talk about socket support and a better event loop and then found this issue list! So, attached are my thoughts so far about the event loop and how this could be done: I did wonder about doing this with Workers but think there are still drawbacks to this. My proposal would be to move the event loop down into C++ and implement support for the events (timers, enter "frame", sockets etc) via that.
I'm not sure whether you're actively working on this right now: if you're not then could I go ahead and implement this on my forked version of the code and you can then take a look at what I've done and see what you think?
thanks
Andrew
RedTamarin - native event loop support.docx

edit: (document inlined)

Native (C++) message loop functionality
Currently the RedTamarin “shell.async.CoreEventLoop” implements a message loop within its “_loop()” function. This has the following form:
1. Handle any timers that are due (using an internal array of Timer objects – they add themselves to this array when they are created)
2. Sleep for a fixed time (based on the ‘frame rate’ e.g. if the frequency is meant to be 60 calls per second then the sleep time will be 16.67ms)
3. Call a “tick()” function which calls a function that the user can set up using the “setCallback()” method.
Some drawbacks to this:
1. Timers cannot have a frequency of more than the frame rate i.e. if a timer should be called 100 times per second but the frequency was set up at 50Hz, then the timer will only be called half as often as it should be.
2. The timer array will only grow, it will not shrink when timers are finished with. This could start to cause memory and performance issues if the runtime has been running for a long period of time.
3. The ‘sleep’ does not take into account the time taken by the various functions (the timer event handlers and the ‘tick’ callback)
4. The ‘tick’ callback would need to be set up manually, but there is no standard Flash-type functionality such as an ‘enter frame’ callback
5. The runtime is essentially stuck within the ActionScript processing mode during all of this time. There would not be a chance for other C++ activities unless explicitly called by timer handlers or the ‘tick’ callback.


New proposal:
1. The main application’s control loop should be implemented natively in C++. Essentially this would be the equivalent of the “_loop()” function.
2. This should be entered automatically after the entry point AS3 function has been called, but should then only continue to loop while there are activities pending.
3. Pending activities may be one of:
   1. An active timer (i.e. a Timer that has an event listener that has been started)
   2. An ‘enter frame’ handler (similar to the ‘tick’ callback but I’d suggest we allow classes to register for the ‘enterFrame’ events like they would in normal Flash/AIR). This could mean that the shell.Runtime.loop object needs to derive from flash.events.EventListener.
   3. An asynchronous activity such as a socket connection request or accept/recv type functionality. Note, at this point I am not referring to C.sys.socket.accept() or other such blocking functions implemented via ActionScript, but instead adding the concept of asynchronous C++ activities such as a socket implementation that’s running in a different thread.
1. The event loop in C++ would then have a similar pattern to the AS implementation but would run more quickly, so e.g.:
   1. Process any timers that are running and due
   2. Process any asynchronous activities that need to fire events into AS (e.g. socket data received etc)
   3. Call all enter frame handlers if the requested ‘frequency’ requires it
   4. Sleep for a small amount (~5ms) and repeat until there are no active timers, enter frame event listeners or asynchronous operations in play.


Implementation plan:
To implement the above proposal I would suggest:
1. The ‘EventLoop’ interface is modified to extend from ‘EventDispatcher’
2. Either ‘CoreEventLoop’ is modified to move a lot of the functionality to C++ native functions, or a new ‘NativeEventLoop’ class is created that does the same thing
3. The ‘Timer’ objects are adjusted to include a native/C++ constructor/destructor so that they can be triggered in C++ without needing the ‘Runtime.loop.timers’ array
4. On the C++ side we create a thread-safe mechanism so that asynchronous operations can be launched that would then be able to post events that are fired back into ActionScript

@zwetan
Copy link
Member Author

zwetan commented Mar 3, 2016

Hey,

First thing is about the google forum, it gonna change, all discussion should go on the Github issues.

For the current shell.async.coreEventLoop

  • it is pre-alpha code, not the final design at all
  • drawbacks about timer frequency
    yes valid, we don't want that, timers or loops should be able to have different frequency
  • drawbacks about timer array growing up
    yes valid, that was a dumb implementation, again nothing final here
  • drawbacks about a tick callback
    yes valid, again quick implementation
  • "runtime is essentially stuck within the ActionScript processing mode during all of this time."
    not valid, we do want the ActionScript side to contain the logic

Before going into your proposal let underline some Redtamarin principle we try to follow

  • we do use C/C++ for native code
    but we try to keep the logic out
    at most you will have 10, maybe 20 lines of C++ to implement a system call
    we certainly do not want 100s of line of C++ to implement logic native side
  • we want to support both blocking and non-blocking code
    that means we are not Node.js and we will never be
  • we want to favour a cross-platform runtime for Windows, Mac OS X and Linux
    that means we will favour using Workers over fork()
  • we favour a native C API
    we don't implement a SocketServer native C++ class
    we implement socket(), bind(), listen(), etc. so dev can implement
    their own SocketServer class in AS3

and few more like

  • we are inside a VM so when we implement exit() call
    it does not exit right away if it is used in a child worker
    and many things like that, eg. we try to play well with what we have
    deal with both blocking and non-blocking code
  • same we can not implement C API calls and kill Flash API calls
    or vise versa, all API have to work well with each others
  • same as we are not Node.js
    we are not the Flash Player or AIR runtime
    supporting them is nice but optional

On top of that, last month or something I worked on the server sockets and made
some experimentations and discoveries, I can not go public with that yet
but yeah being able to use events in Redtamarin is in my TODO list
but I also need to make it work well with other things like the VM, Workers, daemons, etc.

Looking at your proposal you are under the impression that making this event loop native C++ would make everything faster by default, I don't completely agree with that.

Sure C/C++ implementations are fast, but you can perfectly keep the whole logic in AS3
and just call few specific system calls as native function, it will not slow down really and
will have the huge advantage that working with AS3 is much nicer and faster than with C/C++,
that's why such API as the C API exists with its own C package.

Let's take 2 examples:

  1. sockets, in redtamarin 0.2.5 I had a nice C++ abstract Socket class
    with an implementaion for Linux/Darwin and another one for Windows
    and it was working fine.
    But I can guarantee you that we have now in the C.sys.socket, C.netdb, etc. packages
    are 100x more powerful and allow developers to implement their socket class as they see fit.
  2. look at the implementation from Renaun Erickson with his
    ActionScriptGameServerExamples based on libuv
    it is powerful, but the logic is in the native class and so limit the usage to that native class
    if I were to implement that in Redtamarin now I would add that in a C.libuv package
    so dev can implement their own logic for their own special needs.

I understand your proposal, people like the AS3 event system, they also like Node.js
they see Redtamarin and they want Redtamarin to both act like Flash and Node.js

I get that, I want that too, but I'm not ready to sacrifice other things
like having blocking program or blocking sockets or a runtime without events by defaults, etc.

so I would say: "you don't want Redtamarin to be like Node.js"
"you want Redtamarin to provide you the tools so you can implement Node.js" yourself
and I do hope that make sense

To answer your proposal

  1. "The main application’s control loop should be implemented natively in C++."
    nope, the main application control loop should be a simple callback
    to which you can hook your own control loop logic based on
    either select(), poll(), Workers, libuv, etc. or a combination of them etc.
  2. "should be entered automatically after the entry point AS3 function has been called"
    if you're talking the control loop should start only after a call to goAsync(), I agree;
    but if you considering the loop should start as a main entry point in the runtime, I disagree.
    Really being non-blocking, async, having a loop etc. should be purely optional.
  3. "should then only continue to loop while there are activities pending."
    I do agree on that one, but "as is" a loop will block the flow of a program,
    and this part is not straightforward to solve
  4. "Pending activities may be one of [...] active timer, enter frame, async activity"
    well, it is a bit more complicated than that
    I need to write a wiki doc about the design of the loop but basically it has to deal with much more
    see Timing it right for example
    think about cases where you would have a parent worker a many child workers
    combined with a socket server that need to deal with concurrent connection all mixed together
    my point here is we need something above the classic flash runtime loop
    something that can work with Workers, that can also work with signal interrupts, etc.
  5. "The event loop in C++ would then have a similar pattern to the AS implementation but would run more quickly,"
    Not necessarily true and can also cause numerous problems
    the C++ implementation could become quite complex and if it does not deal with all cases
    then the only way to change the loop is to recompile which I think is a PITA.
    All the details you provide as a. b. c. etc. can all be implemented in AS3 without slowdown
    but true, something native would have to trigger the event in the loop.

Now here few things that have not been made available yet in the Redtamarin API

We have native timers, there are there, just not available in AS3
see setTimer() and VMPI.h for VMPI_TimerClient, VMPI_startTimer, VMPI_stopTimer, VMPI_TimerData, etc.

so you can have something like that

function _loop():void
{
    while ( sleep ( 1000/frequency ) && run )
    {
        if( timerPending )
        {
            handleTimers();
        }
    }
}

a dumb blocking timer in an "inifinite" loop

but with a native timer you could have

Program.setTimer( myloop );

function myloop():void
{
    // do whatever to process events
}

and that would not be blocking, it use microseconds (even if not guaranteed) it's not bad at all

you could even reuse Program.atExit() to either
prevent the program to exit if some particular events are still in the pool of events
or at the contrary force all the events to flush before their time and then exit

Or even create a child worker just to run your event loop on the side

For the record, I did some tests with a dumb blocking loop based on sleep()
if you sleep 1 milliseconds, you can easily get to 800 fps
and if you use workers and different sleep time per workers
you can have a main worker running at 4fps, while child worker running at 30fps etc.

Also with Workers, the implementation in Redtamarin is not connected to events
so we don't have a MessageChannel class, but you can implement a
a WorkerConnection class modelled after the LocalConnection class
and combine that with a basic event dispatcher and sync all your events
between different workers.

So, if I go into all those details is because I went trough those different road
like "let's implement a NativeEvent class and do all the events in C++"
and I don't think it can work easily without sacrificing a lot of stuff we already have working

and yeah I know I put the "challenge" maybe a bit too high
having events working nicely with blocking, non-blocking, sockets, workers, signal interrupts etc.
it is asking a lot maybe something a bit impossible

but considering your proposal, even if I appreciate a lot the proposal in itself
"as is" I don't see integrating something like that in Redtamarin
unless I misunderstood some part of it ?

but this for example
"On the C++ side we create a thread-safe mechanism so that asynchronous operations can be launched"
I mean, we already have workers using pthread and it's cross-platform, what else do you need ?
is it because you need something specialised for sockets like epoll, kqueue, other ?
is it because you just want it to work like Flash ?

I'm open to discussion but if it goes toward
"to have events like Flash we can not do blocking programs anymore"
I'm not gonna agree with that (for good reasons)

but please go ahead, maybe describe a bit more what you need ?
(and remember the impl. of coreEventLoop is not at all the final design)

@ajwfrost
Copy link

ajwfrost commented Mar 3, 2016

Hi
Thanks for taking the time to look through my document and for the detailed response and background!
In terms of the overall approach: I see where you're coming from, in terms of wanting to keep the logic in AS3 and then just providing the low-level C APIs in native code, so yes with that in mind I can see the alternatives.
To clarify, when I talked about the native event loop running faster, I actually meant that we would deliberately run it faster i.e. the 'sleep' would be shorter. The same thing could be equally accomplished in AS3 (which I've already got working having played around a bit), I wasn't really comparing execution times for AS3 vs C code.
One key thing then would be how an application actually works in terms of going asynchronous. When I did my first 'hello world' with redtamarin, the entry point function was called, I got the trace, and then the program quit. Then I added a timer and started it but my event listener never got called, so I dug into it some more and found the loop's "start" method. So with that, it's working fine but it requires the user to actually call that goAsync (which never returns, at least unless the user deliberately quits the loop). You need to set up all your timers/callbacks prior to calling goAsync, which makes sense. But I thought, why not always call the goAsync function, and only actually run the loop if/while you have listeners etc going on. I've not dug into how the actual SWF is opened and the specific entry point is called, but if you want to call an initial 'main' function and then you want to go into a loop without the SWF author doing anything else, then it needs to be done in C++ :-)
I don't really know anything about Node.js so can't make comparisons there: what I was looking for though is something where I can write some ActionScript using fairly high-level APIs such as the flash.net.Socket classes, in order to support some cloud development work. The alternative to me is using J2EE which I've not done for several years, so basically I'm looking at this as an alternative option to Tomcat + Servlet programming.
In terms of using a Worker to handle the calls that might be blocking, this should be possible as you say, so I'll explore this option more fully now. Can I check whether you recommend having lots and lots of workers (one per socket basically) or whether you'd think it's better to try to get a single Worker to handle all of the asynchronous code?
thanks
Andrew

@zwetan
Copy link
Member Author

zwetan commented Mar 3, 2016

I actually meant that we would deliberately run it faster i.e. the 'sleep' would be shorter

yep, it's possible, I did some tests on a concurrent socket server here some numbers

           - no sleep
             2014009.99 fps
             1999529.47 fps
             1992650.34 fps
             1943541.45 fps
             2004844.15 fps

           - sleep( 1 ) / 1 millisecond
             799.2 fps
             801.19 fps
             802.19 fps
             806.19 fps
             802.19 fps

           - sleep( 10 ) / 10 milliseconds
             88.82 fps
             87.38 fps
             87.38 fps
             86.91 fps
             88.64 fps

           - sleep( 20 ) / 20 milliseconds
             44.77 fps
             43.3 fps
             43.73 fps
             42.91 fps
             42.96 fps

           - sleep( 27 ) / 27 milliseconds
             33.69 fps
             32.38 fps
             33 fps
             32.22 fps
             32.94 fps

           - sleep( 30 ) / 30 milliseconds
             31.46 fps
             29.94 fps
             30.12 fps
             30.36 fps
             30.42 fps

           - sleep( 40 ) / 40 milliseconds
             24.29 fps
             23.12 fps
             23.41 fps
             23.18 fps
             23.03 fps

with that you can basically tell a loop class to either target a particular FPS
or sleep a particular time, off course the higher the FPS the less code you can run in the loop
eg. don't expect to run a lot of complex logic if you run at 400fps something
but running things in the 30 to 60fps range so far I've seen loop staying consistant

One key thing then would be how an application actually works in terms of going asynchronous. When I did my first 'hello world' with redtamarin, the entry point function was called, I got the trace, and then the program quit. Then I added a timer and started it but my event listener never got called, so I dug into it some more and found the loop's "start" method. So with that, it's working fine but it requires the user to actually call that goAsync

That's basically because we are blocking by default
and the coreEventLoop is a blocking loop

eg.

// some code here
goAync()
// code here not called as long as the loop run

So with that, it's working fine but it requires the user to actually call that goAsync (which never returns, at least unless the user deliberately quits the loop). You need to set up all your timers/callbacks prior to calling goAsync, which makes sense.

which is what I was calling the "way of doing"
eg. prepare all your code, and only at the end of the program flow
call goAsync()

But I thought, why not always call the goAsync function, and only actually run the loop if/while you have listeners etc going on.

It is possible, but there is another problem, from where in the program flow you always run goAsync
the beginning ?
the end ?

worst case scenario is having some code in the middle of the flow
needing some events to work before reaching the end

if you want to call it right from the beginning to be sure that all events will be taken into account
then you will need to structure the code in a special way with a kind of wrapper

and if you want to call it at end, you can use Program.atExit()
but then you risk missing some events
and you need a way to stop the event loop
so your program does not run forever

but if you want to call an initial 'main' function and then you want to go into a loop without the SWF author doing anything else, then it needs to be done in C++ :-)

It can be done from the boot process without C++
see boot.as

well... we would probably need to add some support in the AS3 side
but yeah the boot process could take care of that

for example:

var REDTAMARIN_LOOP:String = getenv( "REDTAMARIN_LOOP" );

if( REDTAMARIN_LOOP != "" )
{
     if( REDTAMARIN_LOOP.toLowerCase() == "true" )
    {
         // define some loop etc.
    }
}

so for now it's pretty basic, it would need to check an env var, or a file or other kind of entry
and then the boot process could self modify how its code run before any user code run.

Now, it's not really the real problem.
The real problem is that event loop is blocking and if you look at the reference
I gave earlier about the native timers, it should be possible to create a
non-blocking loop, example

boot.as

Program.onBoot:Function = null
Program.setTimer( Program.onBoot, 10 ); // execute this 10 microseconds after the user code start

user code

function checkForEvents():void
{
      if( EventManager.length > 0 )
      {
           EventManager.start();
      }
}

Program.onBoot = function()
{
      Program.setTimer( checkForEvents, 100 ); // execute this 100 microseconds from the start of the program
}

foobar = new Something();
foobar.onFoo = function( e:event ):void
{
    // react to FOO event
}
foobar.addEventListener( Event.FOO, foobar.onFoo );

or even simpler, in the EventManager have some logic
that check if events exists and kick start the event loop

My point is native timers could allow us to have a loop that is not blocking (I need to test that particular case)

now about

why not always call the goAsync function

we can not do that as it will block the program flow and so prevent us
all the blocking programs scenarios

think also about case where your program block but it's not really an event
like displaying some text in the console and waiting for some user input
with kbhit()

or testing from the start of a program if we have command line arguments
or need to read the data from stdin
see set_binary_mode()

what I was looking for though is something where I can write some ActionScript using fairly high-level APIs such as the flash.net.Socket classes, in order to support some cloud development work.

interesting

so I would say if you need that "right now"
the best way would be to have an implementation of flash.net.Socket
based on the C API, and use function callbacks instead of events

if you can wait a bit, let's say "about 2 months", supposed I can manage to have this
non-blocking event loop working, then you could have flash.net.Socket that use real events
and some configurations to do at the start of the program for how those events should work

right now one thing missing in the sockets C API is a way to go asynchronous
eg. when you use accept() the function is not blocking
you do it by using workers but it's kind of advanced to do

The alternative to me is using J2EE which I've not done for several years, so basically I'm looking at this as an alternative option to Tomcat + Servlet programming.

well you fit one of my use case ;)
I'm not super expert in Tomcat but I just know it is PITA to install and maintain server-side
and it would be so much easier to be abel to create an AS3 program as a daemon
that can deal with concurrent connections

that's about what I'm working on right now but it is in the prototype phase

In terms of using a Worker to handle the calls that might be blocking, this should be possible as you say, so I'll explore this option more fully now. Can I check whether you recommend having lots and lots of workers (one per socket basically) or whether you'd think it's better to try to get a single Worker to handle all of the asynchronous code?

So to start with workers, the simple setup for concurrent socket connections
is to use 2 type of workers: main and background
with the logic that main deal with the new clients
and instead of forking like good old linux fork()
create background worker per connection

This I tested, provided your main worker keep running
you can spread hundreds and more of background workers
in fact, it is only limited by your RAM

your main server class will look like this

        public function run():void
        {
            if( Worker.current.isPrimordial )
            {
                trace( ">> primordial <<" );
                main();
            }
            else
            {
                trace( ">> background <<" );
                background();
            }
        }

the main logic would be

        public function main():void
        {
            _mutex = new Mutex();
            var run:Boolean = true;
            var childID:uint = 0;

            while( run )
            {
                //trace( "main loop" );

               if( condition to create child )
                    var newChild:Worker = WorkerDomain.current.createWorkerFromPrimordial();
                        newChild.setSharedProperty( "childID", childID );
                        newChild.setSharedProperty( "mutex", _mutex );
                        newChild.start();
                    childID++;
                }

                sleep( sleepTime );
            }

        }

in "condition to create child" it could anything really

the child logic would be

        public function background():void
        {
            var child:Worker   = Worker.current;
            var ID:uint            = child.getSharedProperty( "childID" );
            var mutex:Mutex  = child.getSharedProperty( "mutex" );
            _mutex = mutex;

            while( true )
            {
                // do child logic here
                sleep( 1000 );
            }

            child.terminate();
        }

now you need to mix that with socket connections

        public function run():void
        {
            var client_fd:int;
            var mutex:Mutex;

            if( Worker.current.isPrimordial )
            {
                connections = [];
                clients = new Dictionary();

                mutex = new Mutex();
                var cli_addr:* = new sockaddr_in();
                var srv_addr:* = new sockaddr_in();

                var sock:int = socket( AF_INET, SOCK_STREAM, 0 );
                if( sock < 0 ) {
                    trace( "Can't open socket" );
                    throw new CError( "", errno );
                }

                setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, 1 );

                var port:int = 1234;
                srv_addr.sin_family = AF_INET;
                srv_addr.sin_addr.s_addr = INADDR_ANY;
                srv_addr.sin_port = htons( port );

                if( bind( sock, srv_addr ) == -1 ) {
                    close( sock );
                    trace( "Can't bind" );
                    throw new CError( "", errno );
                }

                connections.push( sock ); // your first socket is the server
                clients[ sock ] = {};

                trace( "shell server start" );
                listen( sock, 5 );

                var running:Boolean = true;

                while( running )
                {
                                       // yeah simple loop on an array of sockets work
                    for( var i:uint = 0; i < connections.length; i++ )
                    {
                        var selected = connections[i];

                                                // isReadable() use select() to know if the socket is readable or not
                        if( isReadable( selected ) )
                        {
                            if( selected == sock )
                            {
                                client_fd = accept( sock, cli_addr ); // this will block
                                trace( "got connection" );
                                if( client_fd == -1 )
                                {
                                    trace( "Can't accept" );
                                    throw new CError( "", errno );
                                }

                                connections.push( client_fd ); // we add a new socket
                                clients[ client_fd ] = {}; // in our array of sockets

                                                               // now we pass the socket descriptor as an ID to the child worker
                                var w:Worker = WorkerDomain.current.createWorkerFromPrimordial();
                                    w.setSharedProperty( "client_fd", client_fd );
                                    w.setSharedProperty( "mutex", mutex );
                                    w.start();
                                clients[ client_fd ].worker = w;
                                clients[ client_fd ].running = false;
                            }
                            else
                            {
                                                               // it's a socket we already know so associated to a worker
                                var client:Worker = clients[ selected ].worker;

                                if( !clients[ selected ].running &&
                                    (client.state != WorkerState.RUNNING) )
                                {
                                    clients[ selected ].running = true;
                                }

                                if( clients[ selected ].running )
                                {
                                    if( client.state == WorkerState.TERMINATED )
                                    {
                                        trace( "child terminate" );
                                        delete clients[ selected ];

                                        var j:uint;
                                        var len:uint = connections.length;
                                        var desc:int;
                                        for( j = 0; j < len; j++ )
                                        {
                                            desc = connections[j];
                                            if( desc == selected )
                                            {
                                                connections.splice( j, 1 );
                                            }
                                        }

                                        if( connections.length == 1 )
                                        {
                                            trace( "only server left" );
                                            running = false;
                                            break;
                                        }
                                    }
                                }

                            }
                        }
                    }

                    sleep(1); // yep 1ms will work
                }

                trace( "done" );

                trace( "shell server stop" );
                close(sock);
                exit( EXIT_SUCCESS );

            }

         // ...
        }

that's where it get complicated, because well all server loop are complicated

usually on accept() you get the socket descriptor of the client
unless this descriptor is the same you have saved for the server

and you just add that id into an array, and your main loop
is just about that: looping trough a serie of sockets

You don't need to use workers, you can do it all like that

but what happen if when a client connect and ¥ou need this client to do an action
like for example: registering a user name (on a chat server) ?
well then your whole loop is blocking

so an evil user could just connect and wait forever to DoS your server

that's why you want to use Workers, so instead of blocking inside your main loop
you just get the socket descriptor (this is just an integer) and pass all that to a
child worker, that way wether you need to wait 10h for the user to decide to register
a name or whatever, it does not block the main server loop

But now the evil is in the details, in fact you have 2 loop
1 main server loop to handle the connections
1 client loop inside a worker to deal with the client "actions"

and for example if you need to deal with client timeout
then your server main loop need to register the time when the client connected
and within the main loop make a time diff and disconnect that client if time bigger than N

but if you wanted to deal with a client timeout from client side
then you would have to spread another worker from the client loop
see where I m going ? a worker than create a worker that create another worker
it can get complicated very fast

now the last part is being able to communicate between all those workers
see WorkerConnection.as

it is a bit crude but it works
technically if you just share a mutex, you can share a function call
between your parent and child worker, just wrap the call in mutex.lock(), mutex.unlock()

but the problem with that is you can not share data
what about if you want to store some strings in an array hosted on the parent worker ?
well use something like _commands.send( "storeData", "hello world" )
it basically use the primordial or parent worker as "dispatcher/receiver"

but you could as well create a connection between 2 child if you needed to

so I basically pasted some prototype code, I'm working on improving all that
just got nothing ready yet for public usage and it may take some months
for a final solution

@ajwfrost
Copy link

ajwfrost commented Mar 3, 2016

Wow, thanks for all that!
Okay so I can't believe how I've missed the flash.concurrent package (and even better, it's part of avmplus rather than FP/AIR code): when I first started looking at the possibility of doing stuff via a Worker I was thinking that mutexes would have been pretty useful!
I've only skim-read your response so far, but one of your other comments that jumped out at me was about not being able to share data between different workers. Surely this is supported via the shareable bytearrays?
We've probably got a bit off-topic with all the socket talk (there's a separate issue about those ones I saw!) but just to go back to the event loop and overall program flow, I hadn't been considering some of the blocking calls like when you're asking for input from a user. I guess my thought for that (and for waiting for socket data) was that those are asynchronous activities where you'd basically set something up to receive the data and then add an event listener for when the data has been received. Then you'd drop out of your user-written code back into the underlying event loop i.e. idling, until such time as the user had provided their input or the socket data had arrived etc. So it's all a bit more asynchronous. But I can see what you mean in terms of the design principles behind redtamarin, so even though that's the sort of thing that I might prefer just from my background of how Flash/AIR works, I can appreciate that there is an elegance about keeping everything in AS as much as possible and making AS behave a bit more like C in terms of blocking program flows.
thanks
Andrew

@zwetan
Copy link
Member Author

zwetan commented Mar 3, 2016

Surely this is supported via the shareable bytearrays?

yes, and in fact because you also have AMF serialisation / deserialisation
you can actually use writeObject() / readObject() on the ByteArray
do get started easily

WorkerConnection was initially about having 2 child Workers talking to each other
directly, for transfering files for example eg. "send data on socket 13 from socket 14"

We've probably got a bit off-topic with all the socket talk

not really, a socket server loop is a very good basic argument to promote the use of events

I'm still in "R&D" for non-blocking main loop, I'll post here if I found something

also, not for big/huge changes but Redtamarin is now considered in production mode
( I use it on dozen of servers) and so update can/will happen on about a bi-monthly basis

@ajwfrost
Copy link

FYI just on the use of events/loops for sockets (client-only for now), I've got this working with a single Worker that's handling all the connections and receiving the data, meant I needed it to put all sockets non-blocking (as I didn't want a worker per socket) so there was a tweak needed to get this working (no fcntl implementation). Out of interest, where we need a higher-level API to make it platform independent, are you okay with that being added to the C.sys.socket package? Currently I'm just hacking a bit in VMPI_fcntl but I thought it would be better to have a setBlocking(Boolean) function.
So now, within my worker I'm running an infinite loop with a sleep in it to avoid spinning the cpu, and in my main Socket class I've got a timing running to poll for any updates coming from the worker. Bit more testing needed but it means I can add event listeners and basically implement the flash.net.Socket API properly.

@zwetan zwetan mentioned this issue Mar 24, 2016
13 tasks
@zwetan
Copy link
Member Author

zwetan commented Mar 24, 2016

closed in favour of the meta issue #82 flash.events package

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

No branches or pull requests

2 participants