Archive

You are currently browsing the Scott Morgan blog archives for November, 2007.

Nov

18

AS3 Garbage Collection, the reason your tweens are ending early.

By scott

Josh Tynjala and I were speaking today, well actually typing over IM. We were discussing a problem we both ran into recently, the fl.transitions.Tween class was not dispatching events consistently. Sometimes the TweenEvent.MOTION_FINISH event would be broadcasted, other times it would not. Sometimes our programmatic tweens were visually completing, other times they would not, in a few cases they wouldn’t even start. After a little debugging we realized that the AS3 Garbage Collection was killing the tweens. You may be asking why would the garbage collection be doing this? Let’s start out by looking at some example code.

var len:int = _model.getNumberOfItems();
for (var i:int = 0;i<len;i++)>
     var clip:MovieClip = _scope.getChildByName('button'+i);
     var myTween:Tween = new Tween(clip, 'alpha', Regular.easeOut, 0, 1, 0.5, true);
     myTween.addEventListener(TweenEvent.MOTION_FINISH, buttonAnimationComplete);
}

Pretty simple code, in my AS2 days I used code similar to this a million times and never had a problem. All I am doing is looping through a set number of MovieClips and fading them up from 0 to 100%. Most times this worked fine, but a few times the tweens never completed and buttonAnimationComplete was not called. This is because the AS3 Garbage Collection was deleting the Tweens just like it should be. What? The tweens should be deleted? Yes.

Notice that I am storing the reference to my tween in a local variable that gets overridden in every iteration of the loop. This means there is no real reference to the object and when there is no reference to an object it is flagged for removal by the garbage collector. Since there is no real way to know when the garbage collection cycle is going to run the tweens were completing some of the time and other times were being thrown into the back of the big garbage truck that slowly creeps around your code.

The best way to ensure that this doesn’t happen is to store your reference in a class level member. I ended up storing all of my tween references in a class level array. Once all of the tweens were complete I cleared the array. Here is the updated code that I used, the entire class is not shown here, trust me your scroller will thank me.

private var _buttonTweens:Array = new Array();
private var _buttonTweenCompleteCount:int;

private function animateButtons():void {
     var len:int = _model.getNumberOfItems();
     for (var i:int = 0;i<len;i++)>
          _buttonTweens.push(new Tween(clip, 'alpha', Regular.easeOut, 0, 1, 0.5, true));
          _buttonTweens[_buttonTweens.length - 1].addEventListener(TweenEvent.MOTION_FINISH, buttonAnimationComplete);
     }
}

private function buttonAnimationComplete(e:TweenEvent):void {
     buttonTweenCompleteCount++
     if (buttonTweenCompleteCount == _model.getNumberOfItems()) {
          _buttonTweens = [];
          dispatchEvent(new Event('buttonsReady'));
     }
}

I am just using Tweens as an example, but you can run into this same issue with any object that has local references that are overridden. For example, I had a Timer event set to iterate every 100 milliseconds and it called loadAsset(), a method that did just what it says, it loaded an asset.

private function loadAsset():void {
     _currentItemToLoad++;
     var myLoader:Loader = new Loader();
     myLoader.addEventListener(Event.COMPLETE, assetLoaded);
     myLoader.load(new URLRequest('images/galleryImage' + currentItemToLoad + '.jpg'));
}
private function assetLoaded(e:Event):void {
     var holder:Sprite = new Sprite();
     holder.visible = false;
     holder.addChild(e.target.content);
     _scope.addChild(holder);

     e.target.removeEventListener(Event.COMPLETE, assetLoaded);
}

Just like the Tween example above my Event.COMPLETE event was not firing all the time. This is because I was storing the reference to the Loader in a local variable that was overridden each time the loadAsset method was called. To get around this I stored all my references in a class level member (an object or an array work nicely).

Hopefully this helps you figure out why you are not seeing certain events in your code, it had me stumped for a bit. Now you know, and as G.I. Joe said “knowing is half the battle”.

Nov

15

Grant Skinner’s OSX Squeeze Effect in AS3

By scott

First off, thank you Grant for releasing the source for your squeeze effect. Dirty code is better than no code. After looking at the source code I began to wonder how much better it would run in AS3 so I converted it. I didn’t clean up any of the code, just a straight conversion. It seems to run a little smoother. Next I am going to make it dynamic allowing you to load in an image passed in through flashVars. Maybe if I’m really bored I will set it up so you can load in multiple images from an xml file and build a gallery type app using this effect. Again, thanks Grant, as always, very cool work!


 
Download the rough AS3 source here.
 

Nov

11

Accessing document class of an externally loaded swf with AS3

By scott

I have seen a lot of posts lately with people asking how they can access variables and methods in an external swf that is loaded at runtime using AS3. This isn’t a difficult task, but it is much different than AS2/AS1 where you could just call directly into the loaded swf using the instance chain if you were in the same security sandbox.

First off, lets create our swf that wil be loaded in at runtime. Open up your favourite actionscript editor and create a new class with the following code.

package com.scottgmorgan {
     import flash.external.ExternalInterface;
     import flash.display.Sprite;
     public class ExternalMovie extends Sprite {
          public function ExternalMovie():void {
               //nothing in our constructor right now.
          }
          public function alert(msg:String):void {
               trace(msg);
               ExternalInterface.call('alert', msg);
          }
     }
}

Now create a new FLA and set the above class as the document class. If you are not sure how to do this simply enter the class path (com.scottgmorgan.ExternalMovie) into the document class textfield found in the property panel. Lather, rinse, repeat, compile.

Next we will create the swf that will load our ExternalMovie swf we just created. Let’s jump back to our favourite actionscript editor and create a new class with the following code.

package com.scottgmorgan {
     import flash.display.Loader;
     import flash.net.URLRequest;
     import flash.events.Event;
     import flash.display.LoaderInfo;
     import flash.display.Sprite;
     public class SourceMovie extends Sprite {
          public function SourceMovie():void {
               var loader:Loader = new Loader();
               loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
               loader.load(new URLRequest('ExternalMovie.swf'));
          }
          private function onLoadComplete(e:Event):void {
               var loaderInfo:LoaderInfo = e.target as LoaderInfo;
               addChild(e.target.content);
               var swf:Object = loaderInfo.content;
               swf.alert('Hello World');
          }
     }
}

That’s it, LoaderInfo saves the day. LoaderInfo.content connects you with the document class of the externally loaded swf. Lets create a new FLA, assign the SourceMovie class as the document class and compile. Make sure the SourceMovie.swf and ExternalMovie.swf are in the same directory. The as files should be in /com/scottgmorgan/. Let’s compile the SourceMovie and you should see “Hello World” in the output window if you run the swf inside the IDE, if you run it from a browser you should see an alert dialog with “Hello World”.

Another option you have is to use the ApplicationDomain class. Using the ApplicationDomain class you can add the classes from the ExternalMovie to the SourceMovie’s ApplicationDomain. This is a great way to load in code libraries at runtime.

Lets pretend we have a large application with multiple levels of security, maybe we are creating a content management system and we need multiple permission levels. User A can only update content, User B can update content and update the site map, User C is an administrator and can do everything User A and B can do but can also access tracking information, edit user profiles, update permissions, etc. When User B logs in the application loads the site map code library (sitemapadmin.swf) and adds its classes to the main ApplicationDomain. When User C logs in the sitemapadmin.swf classes would be added to the main ApplicationDomain, for this user the application would also load the trackingadmin.swf, and useradmin.swf code libraries and add all the included classes to the main ApplicationDomain.

Let’s update our SourceMovie.as file and add the ExternalMovie class to SourceMovie’s ApplicationDomain.

package com.scottgmorgan {
     import flash.display.Loader;
     import flash.net.URLRequest;
     import flash.events.Event;
     import flash.display.LoaderInfo;
     import flash.display.Sprite;
     import flash.system.ApplicationDomain;
     public class SourceMovie extends Sprite {
          public function SourceMovie():void {
               var loader:Loader = new Loader();
               loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
               loader.load(new URLRequest('ExternalMovie.swf'));
          }
          private function onLoadComplete(e:Event):void {
               ApplicationDomain.currentDomain.getDefinition("com.scottgmorgan.ExternalMovie");
               var myExternalMovie:ExternalMovie = ExternalMovie(e.target.content);
               myExternalMovie.alert('Hello World');
          }
     }
}

There you have it. One thing you will notice is you don’t have to add the loaded swf to the display list to access its classes. Hopefully you will be able to use these techniques in future projects.