The Two Absolute Rules in Redtamarin


#1

Long long time ago, I think it was on the tamarin-devel or maybe on a follow-up by email convo, I received this one advice from one of the Adobe developer:

Try to write as less as possible of C++

Sadly I don’t remember the name of the guy but that one advice has resonated with me on so many levels to the point it influenced greatly the API design of Redtamarin itself.

Like that, it just seems a good advice someone would give to someone else who is less comfortable writing C++ (me), eg. instead of writing those 40 or so lines of C++, try instead to just add these couple of C++ calls and write most of the logic in AS3 to glue the things together.

But even now after 10 years or so of working in C/C++ with Redtamarin and knowing more stuff (aka being more comfortable with it) it is still a great advice, in fact it is the first rule:

1. Write the bare minimum C++ code you can get away with

Let’s take a simple example with Program.open()

package shell
{
    public class Program
    {
        /**
         * Executes the specified command line and returns the output.
         *
         * @includeExample Program_open.as
         * 
         * @langversion 3.0
         * @playerversion AVM 0.4
         */
        public static function open( command:String ):String
        {
            // ...
        }
    }
}

This natively is implemented as

private native static function _popenRead( command:String ):String;

in C++ it gives

    Stringp ProgramClass::_popenRead(Stringp command)
    {
        if( !command )
        {
            toplevel()->throwArgumentError(kNullArgumentError, "command");
        }

        StUTF8String commandUTF8(command);
        FILE *read_fp;
        char buffer[BUFSIZ + 1];
        int chars_read;

        VMPI_memset(buffer, '\0', sizeof(buffer));
        read_fp = VMPI_popen(commandUTF8.c_str(), "r");
        Stringp output = core()->newStringUTF8( "" );
        
        if (read_fp != NULL) {
            chars_read = (int) fread(buffer, sizeof(char), BUFSIZ, read_fp);
            output = output->append( core()->newStringUTF8(buffer, chars_read) );
            
            while(chars_read > 0) {
                buffer[chars_read - 1] = '\0';
                chars_read = (int) fread(buffer, sizeof(char), BUFSIZ, read_fp);
                output = output->append( core()->newStringUTF8(buffer, chars_read) );
            }
            
            VMPI_pclose(read_fp);
            return output;
        }
        
        return NULL;
    }

and ported to AS3 it gives

import C.stdio.FILE;
import C.stdio.BUFSIZ;
import C.stdio.popen;
import C.stdio.fread;
import C.stdio.pclose;
import flash.utils.ByteArray;

public static function open( command:String ):String
{
    if( !command )
    {
        Error.throwError( ArgumentError, Errors.kNullArgumentError, "command" );
    }

    var output:String = null;
    var chars_read:int;
    var read_fp:FILE = popen( command, "r" );

    if( read_fp )
    {
        var data:ByteArray = new ByteArray();
        var chunk:ByteArray = new ByteArray();
        chars_read = fread( chunk, BUFSIZ, read_fp );
        data.writeBytes( chunk );
        chunk.clear();

        while( chars_read > 0 )
        {
            chars_read = fread( chunk, BUFSIZ, read_fp );
            data.writeBytes( chunk );
            chunk.clear();
        }

        pclose( read_fp );
        data.position = 0;
        output = data.readUTFBytes( data.length );
    }

    return output;
}

That’s how this advice has influenced the API design, at the beginning of Redtamarin you had few C API calls and they were accessible only from the C++ side with function like VMPI_fopen() etc.

With time I added some more like VMPI_mkdir() etc. but they were still not available to the AS3 front-end, so if you wanted to add new functionalities you had to go and write new native classes and methods and do all the work in C++ (and off course, recompile everything to have access to it in the runtime).

But then I got this idea of “why not give access to this C API at the AS3 level?” and then the C package appeared, and as you can see in the AS3 implementation, you just need access to few functions: popen(), fread(), pclose() and one constant BUFSIZ, and with that you can rewrite the whole C++ thing with only AS3 code.

That’s how this simple advice ended up influencing the API design: we have a full blown C API accessible from AS3 front-end, and that gives many advantages:

  • you can write or rewrite your own low-level functionalities
  • “as if” you were writing them in C/C++, but in AS3 which is much simpler
  • and because it is AS3 you don’t have to recompile the whole redtamarin runtime
  • and cherry on the cake, it is (most of the time) cross-platform by default

And believe me, you do want to write those in AS3 and not C++, because

  • C/C++ is much much stricter than AS3, a simple ; missing and your code do not compile
  • if you add something cool in C++ you can not share it with other dev right away,
    you will have to patch redtamarin first, and the other dev will have to wait for a
    new redtamarin runtime to be released
  • it is much much more time consuming to write C/C++ than to write AS3

Simply put the difference is: with AS3 you can write it in less than 1 hour and share it right away.


And all that lead to the second rule:

2. Try to solve the problem with ActionScript 3.0 first

Oh sure, you can try to solve the problem in C++, it gives you much much power to do crazy stuff, but it is not necessarily the right way to solve the problem.

See, it’s not only AS3 that is a great programming language, it is also the AVM2 that comes with it.

Both combined, AVM2+AS3 that gives you a killer app.

Here a little story, about one year ago I got very stuck with some very complicated C++ stuff;
basically I was trying to run a native timer in its own thread (a bit like a worker but lower level)
that would call a function callback at each intervals.

For me it was like “the holy grail” of being able to make asynchronous timers works,
as it come form another thread it would not block the main thread etc.

So far, it was working but I was hitting this bug where I received this message: “GC called from different thread!”.

And I kind of pulled my hair out for few months trying to solve that in a lot of different ways, going very deep into the inner working of the AVM2, and then I ultimately gave up and telling myself “oh well… I look into it later”.

To then, about 1 month ago, facing yet again the very same problem, but from a different angle.

I needed a last minute functionality: be able to intercept the CTRL+C signal, and as v0.4.2 was already so late, I was like “yeah sure let’s quickly add that”.

Arf… yeah sure “quickly” …
With C++, you never know what can be quick or not to add.

To catch this famous CTRL+C under POSIX you need to use signals,
and you need to have a signal handler on SIGINT.

Great, it was working under macOS and Linux, just need to make it work for Windows right?

When you use something like signal( SIGINT, handler) here what the MSDN signal doc warn you about

SIGINT is not supported for any Win32 application. When a CTRL+C interrupt occurs, Win32 operating systems generate a new thread to specifically handle that interrupt. This can cause a single-thread application, such as one in UNIX, to become multithreaded and cause unexpected behavior.

It is really badly phrased.
SIGINT is supported but yeah using signal( SIGINT, handler) will automatically make your app go multi-threaded and that can create unexpected behavior.

And another difference is that POSIX signals will only be caught by the main thread, wether your app is multithreaded or not.

So guess what happened when I tested that under Windows?
I received again the dreaded error message “GC called from different thread!”

The problem was similar, not calling a timer on a different thread but trying to execute a user defined callback from a signal handler that get executed on another thread, and yet again I was stuck.

Why? because I was trying to solve the problem from the C++ perspective.
Simply put, I was trying to write too much logic in C++.

As soon as I wrote the strict minimum in C++ and then wrote most of the logic in AS3,
what was a huge hard to grasp problem became something almost trivial to solve :slight_smile:.

That’s the power of AVM2+AS3!!

And not only it solved the problem to catch CTRL+C, but it solved also the problem of timers intervals, and ultimately it helped me design a much much better “main loop” for Redtamarin, and also add on top a pluggable event system.


Morality:

1. Write the bare minimum C++ code you can get away with
2. Try to solve the problem with ActionScript 3.0 first

I can now announce that we will get an almost complete AVMGlue, that is the Flash/AIR API v9.0, in about 6 months or so.

Checkout the roadmap for #89 AVMGlue API.

Also, in the mean time other projects will starts:

  • NodeGlue
    porting the Node.js API to AVM2
  • ChakraGlue
    porting the ChakraCore API to AVM2
  • BrowserGlue
    emulating a browser environment inside AVM2

and many more stuff in the pipeline …

Also the next v0.4.2 release will make it super easy to become a contributor,
just running tests, or writing asdoc or writing API in AS3 it well enough to get started