9.30.2010

Tailing Flash

>: tail -f /Users/jspiro/Library/Preferences/Macromedia/Flash\ Player/Logs/flashlog.txt

How did it never occurred to me to tail with Flash before? Shameful.

Though, really, I wish I could control who gets to write to that log, or identify who IS writing to it -- there's so much spam from other running SWFs.

9.17.2010

Dragging's a Drag

For reasons that are beyond my comprehension, Flex List components have a property to allow moving of list items, but not one for copying.

If you allow moving, then you SURELY must also want to allow a poor implementation of copying, whereby your object is deep copied rather than cloned via some interface, putting Objects into your dataProvider rather than whatever class you were using. Also, copying is enabled by holding the control key -- not obvious to me, at least.

package
{
import mx.events.DragEvent;
import mx.managers.DragManager;

import spark.components.List;

/** Disables copy-dragging on List */
public final class NonCopyableList extends List
{
 override protected function dragDropHandler(event:DragEvent):void {
  event.ctrlKey = false;
  event.action = DragManager.MOVE;
  super.dragDropHandler(event);
 }
 
 override protected function dragOverHandler(event:DragEvent):void {
  event.ctrlKey = false;
  super.dragOverHandler(event);
 }
}
}

You're welcome.

7.27.2010

Best Practices when Listening

* Try to always use a weak listener, even though it's extra typing. The classes of bugs you are exposed to with strong listeners are mostly running out of memory, taxing the garbage collector, and killing performance as you take memory off the heap -- they creep up and are hard to detect. The classes of bugs with weak listeners are functionally worse -- but easy to detect -- stuff just doesn't work because the listeners got GCed (they are a little less performant too, due to tracking their weak-ness).

* When writing weak listeners: Make sure that objects you are listening on will not get prematurely garbage collected and are attached to something -- either the class, or some static Singleton.

* Make sure the object will get garbage collected eventually and write a comment about it.

* Assume that all weak listeners and objects will never get GC'd and will keep listening forever -- code for this. In other words, explicitly remove listeners when it is IMPERATIVE that you do not receive any more messages, do not rely on setting a parent to null to break the cycle.

* You DO NOT have to remove weak listeners if it doesn't matter if they continue to receive events until they GC -- e.g. a mouse move handler -- once you've removed the object from the display list, it doesn't matter that this handler MIGHT run. If you code your handlers to guard against runtime errors, then you never have to remove weak listeners. And if the listeners are still receiving after a long while... then you have a memory leak and the object you are listening on is not getting GC'd (see this post).

* If you are going to skip removing a listener, DOCUMENT IT at the line where you add the listener. If you won't document it, then don't do it.

* This is tricky stuff, so treat listeners with respect like you would NEW and DELETE in C++. Work them out in your head -- document the hell out of them. They are expensive and cause almost all memory and (many) performance leaks.

Random() Performance Tips

* Perceived performance is more important than actual performance, and is cheaper to implement. Learn about how to fake speed in progress bars, and never, ever, ever, ever let Flash lock up while doing processing -- break work across frames and do it asynchronously -- keep the UI responsive even if it's modally locked.

* Initializing variables to null is unnecessary, costly, and distracting (when you're trying to read code -- null or zero unless otherwise specified).

* Eliminate "this." wherever possible -- this may be outdated, but in the past it produced better bytecode.

* Don't use coding conveniences like lots and lots of extra variables, WITH blocks, extra "this" statements. They all add space and time in AS3 even though most other languages would compile down equivalent statements to the same thing.

* Long package and class names get stored in the SWF -- interned strings in the constant pool are a large percentage of SWF size.

* Keep lazy initialization of datastructures to a minimum (e.g. if (!foo) foo = new Foo() versus private var foo:Foo = new Foo()) -- class init and static init code is smaller and only runs once. The only advantages to lazy init are for data structures that are expensive to keep around if not being used, and/or you will null them periodically.

* Function arguments and local variables are cheapest to use because AS3 doesn't have to search up the scope chain when you access them. Global statics are awesome -- though caching their values locally is best if you're going to reference them a lot. Deep lookups like foo.bar.baz.getterFunction() are expensive if you're going to do a lot of work in baz -- try caching foo.bar.baz first.

* Anonymous functions: Don't get me started ;) See this post.

* If you use Flex, grep -r validateNow -- and eliminate them unless absolutely necessary. And if you don't remove it, leave a paragraph on why it must be there.

* Getter functions cost more than versus get() functions; and they often have hidden side effects. Don't write a getter unless that's all it does, and in constant time.

* Mark ever variable as const unless you need it to be variable. One day there may be a performance benefit, like finals in Java. But until then, bask in one fewer category of bugs.

* If you're embedding gobs of XML (damned if I know why you'd need to do this): Pasting the XML into a class as a static XML object offers the best performance and compression. The compile-time processing and optimization is significant versus runtime parsing, type checking, and decompression in a ByteArray (if you compressed it on the wire)

JITing in the Flash VM

Three things that came to mind recently:

* Constructors should be as short and fast as possible -- don't do any computation in them. Flash never JITs the constructor function, it's always fully interpreted. (There are performance tests to show this, though I know it for fact.) If you must do some work in the constructor, or the constructor will get called often (e.g. not a Singleton) -- write a private function to do the work and call it from the constructor.

* If you are using an Array and indexing it numerically (i.e. not an associative array -- one that uses string indexes), you can improve access speed to the array by hinting as follows:
array[int(idx*2+1)]
Even if idx is already an int, the type coercion sends a hint to the JIT.

* Finally, not so important, but in tight loops or ones with many iterations: Best practice is to cache the length of an array to a const rather than inlining it.

It's been suggested to me that iterating over Arrays backwards is faster (and you can save a lookup by simply decrementing the index each iteration until it is -1 rather than testing for equality against the length). Though it very well might a) not be true b) be JIT/platform-dependent. It's confusing as hell, so I'd only suggest doing that if you're already doing something impossible-to-understand like dynamic programming or have shown benefits on at least one platform.

4.08.2010

Printing in AS3

Don't.

---
Okay, so I'm trying to be snarky. But since this blog occasionally attempts to be useful:

Printing in Flash has always been inconsistent, poorly implemented, and plain broken. As of Flash 10.1, it got some big updates to the point where it's actually useful.

You still cannot print vectors with transparency -- in which case you're forced to bitmap them first. You still cannot print bitmaps without them looking like shit. You cannot render decent quality bitmaps without using more memory than god.

But printing now works consistently (even if terribly) across platforms; vector printing really works now; and you can spool pages across frames (meaning a potentially unlimited number of pages, no longer timing out, and being able to show an animation and update progress while it chugs along). I can post code some day -- if there's demand.

3.30.2010

Horizontal Mouse Wheel / Swipe Gesture Support in Flash

It's possible to detect horizontal scrolling actions from the mouse or MacBook touch pads with a little JavaScript magic. Caveat: This only works on FireFox 3.1+ and Webkit-based browsers (Chrom{e,ium} and Safari). Microsoft still has not extended their events to send horizontal scroll messages*.

To do this, we're going to inject JS dynamically at runtime. I'm only giving you code snippets, you'll have to tie the logic together yourself.

First, you'll want to make sure you have a working ExternalInterface and add a callback to handle scroll events coming from JS:
if (ExternalInterface.available && ExternalInterface.objectID != null)
{
    try {
        ExternalInterface.call('eval', 'true;');
        ExternalInterface.addCallback("scrollEvent", onScrollEvent);
    } catch (e:Error){
        // nope!
    }
}

Here's your handler:
function onScrollEvent(xDelta:Number, yDelta:Number):void
{
    // scroll something in a fancy manner by xDelta and yDelta
    // note: Webkit may give you TWO non-zero values as it allows
    // simultaneous scrolling
}

Now we need to drop the JS into a String const:
const SCROLL_SCRIPT:String = (<![CDATA[
    function setupScrolling(objectID)
    {
        var flashObject = document.getElementsByName(objectID)[0];
        var eventListenerObject = flashObject;
        var isWebkit = false;

        if (navigator && navigator.vendor)
        {
            isWebkit = navigator.vendor.match("Apple") || navigator.vendor.match("Google");
        }
        
        // some events will need to point to the containing object tag
        if (isWebkit && flashObject.parentNode.tagName.toLowerCase() == "object")
        {
            eventListenerObject = flashObject.parentNode;
        }
        
        var scrollHandler = function(event)
        {
            var xDelta = 0;
            var yDelta = 0;
            
            // IE special case
            if (!event)
                event = window.event;
            
            // IE/Webkit/Opera
            if (event.wheelDelta)
            {
                // horizontal scrolling is supported in Webkit
                if (event.wheelDeltaX)
                {
                    // Webkit can scroll two directions simultaneously
                    xDelta = event.wheelDeltaX;
                    yDelta = event.wheelDeltaY;
                }
                else
                {
                    // fallback to standard scrolling interface
                    yDelta = event.wheelDelta;
                }
    
                // you'll have to play with these,
                // browsers on Windows and OS X handle them differently
                xDelta /= 120;
                yDelta /= 120;
    
                // Opera special case
                if (window.opera)
                {
                    yDelta = -yDelta;
                    // Opera doesn't support hscroll; vscroll is also buggy
                }
            }
            // Firefox (Mozilla)
            else if (event.detail)
            {
                yDelta = -event.detail/1.5;
                // hscroll supported in FF3.1+
                if (event.axis)
                {
                    if (event.axis == event.HORIZONTAL_AXIS)
                    {
                        // FF can only scroll one dirction at a time
                        xDelta = yDelta;
                        yDelta = 0;
                    }
                }
            }
    
            try
            {
                flashObject.scrollEvent(xDelta, yDelta);
            }
            catch(e) {};

            if (event.preventDefault)
                event.preventDefault();
            event.returnValue = false;            
        }
        
        if (window.addEventListener)
        {
            // not IE
            eventListenerObject.addEventListener('mouseover', function(e)
            {
                if (isWebkit)
                {
                    window.onmousewheel = scrollHandler;
                }
                else
                {
                    window.addEventListener("DOMMouseScroll", scrollHandler, false);
                }
            }, false);
        }
        else
        {
            // IE
            flashObject.onmouseover = function(e)
            {
                document.onmousewheel = scrollHandler;
            };
        }
    }]]>).toString();

Finally, execute it in the browser:
ExternalInterface.call("eval", SCROLL_SCRIPT);
ExternalInterface.call("eval", 'setupScrolling( "' + ExternalInterface.objectID + '" );');

Try it out (click the logo to look at my other documents at Scribd!) -- zoom in as far as you can go:



---
* Poor man's support for IE: Use a modifier key to enable horizontal scrolling. For instance, vertical scrolling works as usual -- and if you want hscroll, hold down CTRL while vscrolling.

2.24.2010

Finding Unused Classes with MXMLC

I came up with a quick and dirty method for finding unused AS3 clsses if you compile with MXMLC. The trick is to use a hidden compiler flag that I added while at Adobe: --keep-generated-signatures (and -incremental must be on too). This outputs a generated-signatures directory in your source tree filled with .sig files (named using the package structure, delimited by underscores: mx_core_Foo.sig). These are all the AS files that were compiled and/or synthesized in-memory (e.g. from [Embed] metadata) by MXMLC.

So, now you have a list of AS files that were compiled. Now we need a list of files that we have. cd to your source directory (if you have more than one, you're on your own):
$ cd src/
$ diff <(find . -name "*.as"|sed 's@\./@@'|sort) <(find generated-signatures -name "*.sig"|sed 's@generated-signatures/@@'|sed 's@_@/@g'|sed 's@\.sig@.as@'|sort)|grep "<"|sed 's@< @@'


This should give you a list of files that do not have signatures in the compiler, and should be safe to remove.


For the curious: .sig files are public class signatures -- they are a canonically ordered list of every function, field, import, metadata, etc., that could potentially have a dependency, in either direction, on another class being compiled. One way I used this was for an optimization to determine if a class' dependencies need to be recompiled or not -- if the signature didn't change, they don't. Every AS class gets a signature generated -- on the extremely rare case where a signature can't be generated (due to unforseen syntax or something), a warning is printed with mini-stacktrace to the console -- if you don't see that, you can be sure it's complete.