Thursday, May 1, 2014

Migrating from Alchemy to FlasCC/CrossBridge - The flyield() Method Alternative

In Alchemy, when you're compiling and reusing C/C++ code in Flash, you may encounter the problem caused by loops. In C/C++, it's very common to use a infinity loop such as

while(1)
{
}
or
for(;;)
{
}
where we will put everything such as main game logic, into the loop and break the loop when certain conditions are satisfied. However, this way doesn't work for Flash. At the time of Alchemy, Flash/Action Script 3 was still 'single' threaded. This infinity loop will block everything - no UI updates, no interaction responses, the Flash program looks like dead - as everything else is waiting for the loop to finish. One way to solve the problem is to break this C/C++ run-loop into frame by frame calls on the AS3 side (See CrossBridge SDK's "Sample 4: Animation" for details). At that time, we have one handy function called "flyield()" in the Alchemy C/C++ API, to simplify the solution. All you need is to stuff such function into those infinity loops, and declare the functions contains those infinity loops as "AS3_FunctionAsync". And when Flash sees this "flyield()" function, it will know that it should freeze the loop for a while, jump out to update the UI and let other things work, then come back to run the loop again.

However, there is no such function "flyield()" any more in FlasCC/CrossBridge. But now, we have two ways to deal with such infinity loops. One is the way provided by the newly introduced concurrency API, the other is the classical "flyield()"-like way.

Thanks to the new concurrency API of Action Script 3, you will never need to worry about the error that "Error #1502: A script has executed for longer than the default timeout period of 15 seconds". With workers, you can do all the computation intensive calculations in the background, and let the UI runs in main thread. So those intensive calculations will not block your whole program any more. And the concurrency API is also available in FlasCC/CrossBridge. To use the concurrency API, you can check the "Sample 9 - Pthreads" in the CrossBridge SDK.

In FlasCC/CrossBridge, you simply can run all the C/C++ code in the background worker by the following line in your "Console.as".
CModule.startBackground(this, new [], new [])
The most common case is in your Main loop, you need to pause the logic and let the UI listen to the user input. In many C/C++ applications, the waiting for user input model is an infinity loop. In this case, you can use "avm2_self_msleep" to pause the loop and add an input event listener, once get the user input, use "avm2_wake" to resume the loop and the main logic.
See the following source code of for more details:
https://flaswf.googlecode.com/svn/trunk/Games/Sanguosha/lib
(especially the file "FlasCCPortLayer.cpp". Update: (2014/7/18) Alternative way for getting input is to use the "avm2_ui_thunk" and "CModule.serviceUIRequests" combination, see the Crossbridge quake1 example for details.)

The above way works for some cases, for example when the C/C++ function consume a lot time to run, but is not so convenient for many C/C++ game logic as the old " flyield()" function in the aforementioned case. You need to reorganize the code structure using workers. To make things easy, actually, there is an almost equivalent function in FlasCC/CrossBridge - "avm2_wait_for_ui_frame".
To use this "flyield()" alternative, you must also run your C/C++ code in the background. In other words, you need the following code in your "Console.as" again:
CModule.startBackground(this, new [], new [])
Then, all you need to do is to insert the following code
avm2_wait_for_ui_frame(0);
somewhere in the infinity loop.

One drawback of this method is that you need to put the C/C++ loop in your main function. This is not very convenient as the old "AS3_FunctionAsync" declaration in Alchemy when you want to pass some parameters into the loop. One way is to initialize parameter as global variables in C/C++ by passing values from AS3 side before call the C/C++ main function(CModule.startBackground), another way is to use the C/C++ "argv" - the second parameter of CModule.startBackground. Well, this may solve the problem to pass parameters, another important issue is that how to return values from C/C++ main to AS3? So you can see this workaround, which only works with the C/C++ main function, is still not so convenient as the old asynchronous functions in Alchemy. In this case, different from the main logic loop, actually you usually want a C/C++ function to do some time consuming calculation and return the result, while not be restricted by the Flash script execution time limit. To resolve all the difficulties and troubles fundamentally, you'd better use the more advanced and complicated Pthreads (See "Sample 9: Pthreads") and Workers (run the FlasCC/CrossBridge wrapper function in the background as what you can do with a time consuming AS3 function, see for example http://esdot.ca/site/2012/intro-to-as3-workers-hello-world) with FlasCC/CrossBridge.

Source code of an example using avm2_wait_for_ui_frame:
https://flaswf.googlecode.com/svn/trunk/flaswfblog/Tutorials/flyieldAlternative

Links:
http://stackoverflow.com/questions/13560133/increase-flash-script-execution-time-flascc
http://forums.adobe.com/message/6227761

Sponsors