Search code examples
actionscript-3inheritanceflash-cs4symbols

In AS3, can a library symbol extend another library symbol, assuming each are linked to a class?


For example:

  1. Library symbol "Card" is linked to class "Card" which extends "MovieClip". Library symbol "Card" contains a card background image.
  2. Library symbol "Ace" is linked to class "Ace", which extends class "Card". Library symbol "Ace" contains a TextField with a big letter "A".

So we have Ace extends Card which extends MovieClip. Ace therefore extends MovieClip, but does not DIRECTLY extend MovieClip.

When I drop an instance of Ace on the stage and compile the clip, all that shows up is the big letter A. However, I expected the background image from Card to be included, since Ace extends Card, and the Card symbol contains the background.

It seems like Flash ignores symbol content unless it belongs to the top-level class being instantiated. I think it's LAME that one symbol can't extend another. The IDE could easily draw Card as a non-editable background while I'm editing Ace which extends it, and it should instantiate Card's content and then Ace's content when an Ace is instantiated. Thoughts?


Solution

  • Taurayi's solution is inspiring, because it establishes that missing explicit link from Class to Symbol, ensuring the Symbol's content is instantiated whether it's the top-level class or just a base class in an inheritance chain. A side effect of that approach, however, is that it adds an extra containment level in Card's content, namely the CardSprite container.

    I have managed to implement a practical generic solution that actually preserves the expected stacking behavior of all inherited symbols. For example, if you check "numChildren" on an instance of Symbol2 below, it will be exactly the sum of Symbol1.numChildren and Symbol2.numChildren, so it's a true merge stacking of symbol content.

    When your symbol is in an inheritance chain, simply add this "ensureLinkage" call anytime after a call to the super() method.

    package
    {
        public class Symbol1 extends Sprite
        {
            public function Symbol1()
            {
                super();
                BugFixes.ensureLinkage( this, "Symbol1" );
            }
        }
    }
    
    
    package
    {
        public class Symbol2 extends Symbol1
        {
            public function Symbol2()
            {
                super();
                BugFixes.ensureLinkage( this, "Symbol2" );
            }
        }
    }
    

    Note: Don't forget to make sure your top-level symbol also explicitly defines a class with the above pattern.

    When Symbol2 and Symbol1 are linked to corresponding symbols in the library, their content will now stack. Just drop an instance of Symbol2 on the stage, and test the movie. You'll see that Symbol1's content appears under Symbol2's content. (Note: does not appear in the designer, since this is a runtime fix).

    The implementation of ensureLinkage is as follows:

    package
    {
        import flash.utils.getQualifiedClassName;
        import flash.utils.getDefinitionByName;
        import flash.events.Event;
    
        public class BugFixes
        {
    
            public static var linkageMonitor:Object = new Object();
            private static var linkageMonitorAuthority:Array = new Array();
    
            public function BugFixes()
            {
            }
    
            public static function ensureLinkage( instance:*, className:String )
            {
                if (getQualifiedClassName( instance ) != className) //detect non-top-level construction
                {
                    //prevent inevitable factorial-recursive construction
                    var stack:Array = linkageMonitor[instance] as Array;
                    if (stack == null)
                    {
                        stack = new Array();
                        stack["numChildren"] = instance.numChildren;
                        linkageMonitor[instance] = stack;
                    }
    
                    var barredByAuthority:Boolean = false;
                    if (linkageMonitorAuthority.length > 0)
                        barredByAuthority = (linkageMonitorAuthority[linkageMonitorAuthority.length - 1] as Array).indexOf( className ) > -1;
                    if (stack.indexOf( className ) == -1 && !barredByAuthority)
                    {
                        stack.push( className ); //remember construction
                        trace( "ensuring Linkage of inherited class " + className );
    
                        //perform top-level construction to trigger symbol linkage and child object instantiation
                        linkageMonitorAuthority.push( stack );
                        var temp:* = new (getDefinitionByName( className ) as Class)();
                        linkageMonitorAuthority.pop();
    
                        //Merge children
                        while (temp.numChildren > 0)
                            instance.addChild( temp.getChildAt( 0 ) );
    
                        //Merge properties
                        for (var prop:String in temp)
                            instance[prop] = temp[prop];
                    }
                    else
                    {
                        trace( "skipping redundant construction of: " + className );
                    }
                }
                else
                {
                    var stack:Array = linkageMonitor[instance] as Array;
                    if (stack != null)
                    {
                        var nc:int = int(stack["numChildren"]);
                        trace("construction completing for " + getQualifiedClassName( instance ) );
                        for (var i:int = 0; i < nc; i++)
                            instance.setChildIndex( instance.getChildAt( 0 ), instance.numChildren - 1 );
                    }
                    delete linkageMonitor[instance]; //top-level constructor is completing, all relevant sub-objects have been constructed
                }
            }
        }
    }
    

    Basically, it detects whether symbols are going to need manually instantiated, by seeing whether the qualified class name of the instance matches the expected class name passed to the call from the class itself. Since it's called after "super", the calls start at the deepest class and ensure its library symbol's children are instantiated by making a temporary top-level instance and claiming its children as its own. The very first call for the instance also grabs the original number of children present, since the top-level clip in the stack will have already instantiated its children before any constructor code is run at all. By storing that number, a final step can then pull those initial children to the top where they belong. The class ensures no unnecessary recursion takes place, by using an "authority" stack to ensure the main stack is always visible to child constructors.

    One issue is that static strokes are not persisted, but that is only because AS3 provides no API for accessing strokes (i.e. once you draw a line in the designer, or with graphics.lineTo, there is no way to programatically access that stroke for enumeration or modification purposes, except to clear all strokes at once). So that's not a limitation of this approach, but rather Flash's API.

    Perhaps Adobe was simply unable to come up with this implementation :P

    Please note that if your symbols do any work that ties the symbol instance to other code, there could be an issue, since this class claims ownership of children from a temporary instance. It also claims the values of variable references from the temporary instance using a for loop, but that's the best it can do in a generic implementation such as this.