Thursday, March 4, 2010

closures in actionscript

From time to time, I've read that you can use closures in Actionscript, but I've never found a use for them until now. Before I explain that use, let me go over what closures are and how they work.

Normally, local variables die after functions are through running:


function someFunction() : void
{
var myFriend : String = "Fred";
}

someFunction();
trace( myFriend ); //1120: Access of undefined property myFriend.


But things get wacky when functions return other functions:


function functionMaker() : Function
{
return function() : void { trace ( "I am a new function!" ) }
}


In the above example, I haven't used any variables. I'll add one shortly. For now, just note that functionMaker's return type is Function and that it, in fact, does return a function. The function it returns in anonymous. That is, it doesn't have a name. We can access it this way:


var aNewFunction : Function = functionMaker();
aNewFunction(); //I am a new function!


functionMaker returns a new (anonymous) function, which we stored in a variable called aNewFunction. We then called the function stored in aNewFunction by following the variable's name with the call operator: ().

You can shorten the above to...

functionMaker()();

That looks odd, but it works. The first call operator calls functionMaker; the second class the function that functionMaker returns.

Let's take another look at the guts of functionMaker:


function functionMaker() : Function
{
return function() : void { trace ( "I am a new function!" ) }
}


From now on, in the case of functions that return other functions, I'm going to call the main function the outer function and the returned function the inner function:


//outer function
function functionMaker() : Function
{
//inner function
return function() : void { trace ( "I am a new function!" ) }
}


What happens if we create a local variable in the outer function and try to reference it from the main body of our code?


function functionMaker() : Function
{
var food : String = "cheese"?

return function() : void { trace ( "I am a new function!" ) }
}

functionMaker();
trace( food ); //1120: Access of undefined property food.


No surprise. food is a local variable to functionMaker. We can't access it from outside the function. However, what if we try to access it from within the inner function?


function functionMaker() : Function
{
var food : String = "cheese"?

return function() : void { trace ( "I am a new function, and I love " + food + "!" ) }
}

var aNewFunction : Function = functionMaker();
aNewFunction(); //I am a new function, and I love cheese!


Take a moment to think about what just happened. When we ran the inner function, it spit out the word "cheese" as part of its output, even though that word wasn't stored inside it. It got that word by referencing the outer function's variable. So inner functions have references to the variables in their parent functions.

Now take a closer look at these lines:

1. var aNewFunction : Function = functionMaker();
2. aNewFunction(); //I am a new function, and I love cheese!

functionMaker, the function that contains the value "cheese" finishes running on line one, so under normal circumstances, cheese should be dead by line two. But it's not. And that, kids, is a closure: inner functions have access to the variables in their parent, outer functions even after those outer functions have stopped running!

Not only can they access those variables, they can alter them!


function functionMaker() : Function
{
var food : String = "cheese"?

return function() : void { food += " crackers"; trace( "I am a new function, and I love " + food + "!" ) }
}

var aNewFunction : Function = functionMaker();
aNewFunction(); //I am a new function, and I love cheese crackers!


There's one last key aspect of closers I haven't explained: From the inner-function's point-of-view, the variable is static. That is, it keeps its value even after the inner function is done running! That means that each time you call the inner function, it can access the last value stores in the variable.


function counterMaker() : Function
{
var count : int = 0;

return function() : void { trace( count ++ ) }
}

var aNewCounter = counterMaker();
aNewCounter(); //0
aNewCounter(); //1
aNewCounter(); //2
aNewCounter(); //3


Compare this with a stand-alone version of the inner function:


function counter() : void
{
var count : int = 0;
trace( count++ );
}

counter(); //0
counter(); //0
counter(); //0
counter(); //0


In the stand-alone version, the variable counter gets reset to zero each time the function is called. It gets incremented in the trace, but by then it's too late. The function is over and its local variable dies. We can use closures to add static variables to functions!

As I said at the top of this article, I had read about closers for years, but I didn't see how they were useful in Actionsctipt, which is an object-oriented language. As such, if I wanted a counter with a static variable, I would make and use one like this:


public class Counter
{
private var _count : int = 0;

public function next() : { trace( _count++ ) }
}

var counter : Counter = new Counter();
counter.next(); //0
counter.next(); //1
counter.next(); //2
counter.next(); //3


So what made me suddenly want to use a closure? Well, I was trying to do something very specific involving regular expressions. I started with this text:

Photo is of a sunset.
Photo is of my sister.
Photo is of a horse.
Photo is of a birthday party.
... [many other similar lines]
Photo is of a calm lake near the city.

From that, I wanted to output...

Photo1 is of a sunset.
Photo2 is of my sister.
Photo3 is of a horse.
Photo4 is of a birthday party.
... [many other similar lines]
Photo[n] is of a calm lake near the city.

...with each new "Photo" getting an incremented number appended to it.

To start out with a simpler example, let's say that I didn't care about the numbers incrementing. I just want a random number appended to the end of each "Photo" (Photo2, Photo9, Photo3, etc.).


var text : String = "";
text += "Photo is of a sunset.";
text += "Photo is of my sister.";
text += "Photo is of a horse.";
text += "Photo is of a birthday party.";
text += "Photo is of a calm lake near the city.";

var regExp : RegExp = new RegExp( "Photo", "g" );

text = text.replace( regExp, String( Math.round( Math.random() * 10 ) );
trace( text );


That doesn't get me exactly what I want. It doesn't append a random number onto the end of each "Photo". Instead, it replaces each "Photo" with a random number:

6 is of a sunset.
0 is of my sister.
10 is of a horse.
6 is of a birthday party.
3 is of a calm lake near the city.

I could prepend the original string "Photo" onto the random number ...

text = text.replace( regExp, "Photo" + Math.round( Math.random() * 10 );

... but that second parameter is starting to get long, ugly and unreadable. Luckily, the String.replace() method accepts a function as its second parameter:


function appendRandomNumber() : String
{
return arguments[ 0 ] + Math.round( Math.random() * 10 );
}

var regExp : RegExp = new RegExp( "Photo", "g" );

text = text.replace( regExp, appendRandomNumber );
trace( text );


What happens here is that replace calls the appendRandomNumber method, handing it an array, which the function receives in a variable called arguments. If you want to know everything that's stored in the array, look up String.replace() in help. All we care about here is arguments[0], which contains whatever the regular expression matched: in this case, the word "Photo." The function returns that plus a random number:

Photo4 is of a sunset.
Photo7 is of my sister.
Photo0 is of a horse.
Photo10 is of a birthday party.
Photo6 is of a calm lake near the city.

Putting that idea together with closures, we can hand String.replace() an inner function that uses its parent's static variable to keep count:


function counterAppenderMaker() : Function
{
var counter : int = 1;

return function() : String { return arguments[0] + counter++ }
}

var regExp : RegExp = new RegExp( "Photo", "g" );

text = text.replace( regExp, counterAppenderMaker() );
trace( text );


Which yields this output:

Photo1 is of a sunset.
Photo2 is of my sister.
Photo3 is of a horse.
Photo4 is of a birthday party.
Photo5 is of a calm lake near the city.

I think this is an elegant solution. To be fair to OOP, there is another way of doing this:


public class AppendCounter
{
private var _counter : int = 1;

public function doIt() : String
{
return return arguments[0] + _counter++;
}
}

var regExp : RegExp = new RegExp( "Photo", "g" );
var appendCounter : AppendCounter = new AppendCounter();

text = text.replace( regExp, appendCounter.doIt );
trace( text );


I am not advocating closures over objects (or vice versa), but it's nice to know the closure method. Depending on what I'm trying to achieve, it may be clearer to have all the logic in one class rather than referencing a second class (or a global variable) just to increment a counter.

Thursday, January 21, 2010

subtleties

Whew! I just finished a hell of a debugging session. My goal was to make Flash submit a url to tinyurl.com for minification.

My code worked locally (when I ran the swf on my desktop), but it didn't work in the browser. So I was pretty sure it was a security issue. But I couldn't understand why I wasn't getting a response from http://tinyurl.com, because they DO have a crossdomain.xml file. See: http://www.tinyurl.com/crossdomain.xml

hint: if you're smarter than I am, you may be able to guess the problem just via the previous paragraph.

Here's the code that didn't work:


send();

function send() : void
{
var url : String = "http://www.google.com/search?q=cows";
var urlLoader : URLLoader = new URLLoader();
urlLoader.addEventListener( Event.COMPLETE, receive );
urlLoader.load( new URLRequest( "http://www.tinyurl.com/api-create.php?url=" + url ) );
}

//this callback function never gets called!
function receive( event : Event ) : void
{
var urlLoader : URLLoader = event.target as URLLoader;
//output is a text field on the stage
output.text = urlLoader.data;
}


Here's the working version. Can you spot the difference?


send();

function send() : void
{
var url : String = "http://www.google.com/search?q=cows";
var urlLoader : URLLoader = new URLLoader();
urlLoader.addEventListener( Event.COMPLETE, receive );
urlLoader.load( new URLRequest( "http://tinyurl.com/api-create.php?url=" + url ) );
}

function receive( event : Event ) : void
{
var urlLoader : URLLoader = event.target as URLLoader;
//output is a text field on the stage
output.text = urlLoader.data;
}


Give up? The difference is in this statement:

BUGGY: urlLoader.load( new URLRequest( "http://www.tinyurl.com/api-create.php?url=" + url ) );

WORKING: urlLoader.load( new URLRequest( "http://tinyurl.com/api-create.php?url=" + url ) );

In case you still don't see it, the buggy version has a www in-front-of the url and the working version doesn't.

tinyurl's crossdomain.xml file is located at http://tinyurl.com/crossdomain.xml NOT http://www.tinyurl.com/crossdomain.xml.

But you'll get to it if you type it the "wrong" way into the browser, because most browser normalize urls. Flash doesn't. To Flash, there is no crossdomain.xml file, because it was specifically looking for one at the www address, and there isn't one there.

The scary thing is that my FIRST assumption was that it was a crossdomain.xml issue, so I watched the Activity Window in Safari as I ran my app (the bad version) in the browser. The Activity window showed that the crossdomain file was being accessed correctly. But apparently that was just the BROWSER accessing it correctly. Flash still thought of it as a bogus file, since it came from a www site.

Thursday, January 14, 2010

how long is an mp3?

I need to know the length (in seconds) of a progressive mp3 before it starts playing. As-far-as I can tell, there are only three ways of getting this information:

1) if it's explicitly stated in some sort of data feed (e.g. an XML playlist), you can, of course, grab that info and hope it's accurate.

2) you can read it from the id3 tags -- the metadata embedded in the mp3 file itself -- and hope THAT'S accurate.

3) you can guestimate length using some simple math.

Option one won't work for me, because my clients are generally not going to enter durations into a data feed.

I spent hours trying to get option two to work, emailing other developers and asking them for advice. I wasn't receiving the id3 data. Someone mentioned to me that I was likely hitting a security wall. It turns out that though a SWF can play mp3s from another domain, it can't access embedded id3 data without permission. See: http://kb2.adobe.com/cps/963/50c96388.html

I figured this was the problem, so I added a crossdomain file and a SoundLoaderContext object to Sound.load(), but I still didn't receive any id3 data. Why? Because there wasn't id3 data in the mp3s I was using -- even though the client assured me that there was.

I realized that I can't count on there being id3 data. Even if there is, there may not be length data, as it's optional which id3 tags are included in an mp3 file (or if any are included at all).

Which lead me to try option three. I patched it together based on various similar solutions I found online:

This code runs in a loop:


if( _duration == 0 && ( bytesLoaded / bytesTotal ) > 0.1)
{
_duration = _sound.length / 1000 / bytesLoaded * bytesTotal;
}


It waits until ten percent (0.1) of the mp3 is cashed and then calculates duration. It's an estimate, because it's based on Sound.length, which is NOT necessarily the length of the whole mp3. It's the length of what's loaded so far.

And you can't simply guess the length from bytesTotal, because depending on the compression/bitrate of the mp3, the same number of bytes could mean different lengths. So the calculation takes into account the known length, the number of bytes loaded so far and the total number of bytes. (Divided by 1000 to convert from milliseconds to seconds.)

How much should I cache before I let Flash do the calculation? Is ten percent enough? (The JW Player uses ten percent.) I tried it with an mp3 which I knew to be 1 minute and 2 seconds. The guestimation was close but not perfect: 00:59 and 1:00 on subsequent runs.

I upped the cached-percent to 0.2, which produced perfect guesses. Also, on my high-bandwidth (but not exceptionally high-bandwidth) connection, it took about a second for 0.2 percent to cache, which is acceptable for my purposes.

But then I tried a one-hour-long mp3. As you might expect, it took quite some time for 20 percent to cache. Totally unacceptable. I changed the percent back to 0.1 and the hour-long mp3 produced a guess in a reasonable amount of time (it took about fifteen seconds to guess). And the guess was only off by one second.

In the end, I am probably going to use a three-pronged approach: first, I will check for duration data in the XML feed, using that if it's there (doubtful). Next, I will try to use id3 data. Failing both those approaches, I will guestimate with a ten-percent cache.

If anyone reading this has a better idea, please let me know!

I am also trying to figure out how to adjust the UI if Flash has to guestimate. When the hour-long mp3 finished playing, the time-duration display said 00:59:00/1:00:00, which doesn't thrill me. Should I fudge the time up to the duration? (if end_time != duratation; then end_time = duration.)

Someone suggested that I keep checking for duration and adjust it as it the guess gets more accurate (which it will as more data gets cached.) I don't know. I think it would be pretty distracting seeing the the duration-readout change. Also, my played-progress bar would jump around, popping to a longer or shorter width.

What do you think?