boostworthyisryantaylor

Archive for August, 2007

FDT 3: You’re Gonna Need Some Ant With That Sauce

FDT 3 is almost here - fans of FDT 1.5 know how awesome that is. I've been fortunate enough to be involved in the private beta, so I have had my hands on it for some time now. I must say, I like it better than version 1.5 for sure (and not just because it supports AS3 now). I'll save the review for another time though; this long post is all about Ant support in Eclipse and how you can use it to compile your AS3 projects. Though I am using FDT 3 for this tutorial, if you aren't in the private beta group you can still follow along using Flex Builder. If you are using the standalone version, you will need to use the 'Software Updates > Find and Install' tool to get 'Eclipse Java Development Tools' downloaded and installed so that you can use the Ant panel which I speak of throughout this article.

Moving along, you will need to download a copy of the Flex 3 SDK to your machine. It contains everything you will need to compile, test, and document your various AS3 projects. Also, the examples below are for Windows users, however the same rules apply for Mac users, you will simply need to change some file extensions and so forth.

Setting Up Eclipse

If the Ant panel is not already open, you can find it here:

Window > Show View > Ant

This is what you should see if it is open:

Eclipse Ant Panel

With the Ant panel open, it is time to create a build file.

Constructing An Ant Build File

First off, you will need to create a 'build.xml' file which Ant will use to automate your build. At it's simplest form, it should look something like this:

CODE:
  1. <project name="FDT3ExampleTasks" default="compileAllAndLaunch" basedir=".">
  2.  
  3. </project>

Breaking down the project node attributes, 'name' is the name that will appear in the Ant panel of Eclipse. The 'default' attribute is a reference to the target in which Ant will run by default, say if you double-click the build file in the Ant panel for instance. Finally, there is the 'basedir' attribute which basically tells Ant what directory the property 'basedir' should represent. If you are going to store your build file(s) in a directory other than the root directory of your project, then you will want to change that path to reflect this. For instance, if you created a directory called 'build' and stored your build file(s) in there, you would want to make your basedir point towards '../' and so forth.

Before moving on - now that you have a 'build.xml' file in the works, you can go ahead and add it to the Ant panel. The simplest way to do this is by dragging the build file from the Flash Explorer panel down into the Ant panel. Upon dropping the file it should become listed in the panel. As tasks get added they will show up listed under that particular file like so:

Eclipse Ant Panel

Double-clicking the tasks is how you will run them.

Ok, so now that we have a foundation for our build file, we need to add some properties so that we can easily reference commonly used paths and whatnot. You can define properties directly inside the 'build.xml' file or create a separate 'build.properties' file to store them externally from the build file itself. Below are demonstrations of both practices. Also note that once you define a property, you call upon it using the following notation: ${propertyname}.

Defining properties inside the build file:

CODE:
  1. <project name="FDT3ExampleTasks" default="compileAllAndLaunch" basedir=".">  
  2.  
  3.     <property name="flex3dir"     value="C:/flash_tools/flex_3_sdk/"                      />
  4.     <property name="flex3bindir"  value="${flex3dir}/bin"                                 />
  5.     <property name="flex3libsdir" value="${flex3dir}/frameworks/libs"                     />
  6.     <property name="mxmlc"        value="${flex3bindir}/mxmlc.exe"                        />
  7.     <property name="asdoc"        value="${flex3bindir}/asdoc.exe"                        />
  8.     <property name="flashplayer"  value="${flex3dir}/runtimes/player/win/FlashPlayer.exe" />
  9.     <property name="bindir"       value="${basedir}/bin"                                  />
  10.     <property name="classesdir"   value="${basedir}/src/classes"                          />
  11.     <property name="deploydir"    value="${basedir}/deploy"                               />
  12.     <property name="docsdir"      value="${basedir}/docs"                                 />
  13.     <property name="framerate"    value="31"                                              />
  14.     <property name="bgcolor"      value="0xFFFFFF"                                        />
  15.     <property name="width"        value="550"                                             />
  16.     <property name="height"       value="400"                                             />
  17.  
  18. </project>

Defining properties inside a separate build properties file (name it 'build.properties' and save it in the same directory as the build file(s):

CODE:
  1. flex3dir     = "C:/flash_tools/flex_3_sdk/"
  2. flex3bindir  = "${flex3dir}/bin"
  3. flex3libsdir = "${flex3dir}/frameworks/libs"
  4. mxmlc        = "${flex3bindir}/mxmlc.exe"
  5. asdoc        = "${flex3bindir}/asdoc.exe"
  6. flashplayer  = "${flex3dir}/runtimes/player/win/FlashPlayer.exe"
  7. bindir       = "${basedir}/bin"
  8. classesdir   = "${basedir}/src/classes"
  9. deploydir    = "${basedir}/deploy"
  10. docsdir      = "${basedir}/docs"
  11. framerate    = "31"
  12. bgcolor      = "0xFFFFFF"
  13. width        = "550"
  14. height       = "400"

As shown above, build.properties files are very simple and easy to create. The advantage to using one is basically the same as using a config XML file in a Flash project to prevent needing to make changes directly in Flash (but in this case the build file).

Ok, so now on to the good stuff. Let's create a simple task to compile our 'main.swf' file.

CODE:
  1. <target name="compileMain">
  2.     <exec executable="${mxmlc}">
  3.         <arg line="-source-path '${classesdir}' -library-path '${flex3libsdir}' -default-frame-rate=${framerate} -default-background-color=${bgcolor} -default-size ${width} ${height} -strict=true '${classesdir}/com/boostworthy/Main.as' -output '${deploydir}/main.swf'"/>
  4.     </exec>
  5. </target>

Each target needs to have a unique name which represents that task. Using an 'exec' node, you can specify an executable to call. In our case, we pass it the property we created which points to the mxmlc compiler. Last but not least we have an 'arg' node which is used to pass arguments to the executable just like we would in the command-line. Note that you can create a separate 'arg' node for each argument if you wish. I prefer keeping everything on one line to prevent the document from being super long, however it can make things a little less readable if that is a concern.

You will want to create a task like this one for each SWF you need to compile. I usually just copy and paste one to a new line, give it a new name, and change the SWF file name and document class. As your application grows, you will likely want to create a 'compileAll' task for compiling each SWF instead of having to run each task on it's own. Fortunately, there is a very nice way we can reuse all of our existing tasks. Here is an example of how you could run three different tasks with ease:

CODE:
  1. <target name="compileAll" depends="compileMain, compileNavigation, compileAbout"/>

The 'depends' attribute is very handy. It simply states that Ant must do each task listed prior to running that task. In this case, we just simply list out each compile task we would like it to run, then never specify any actions to be taken for the 'compileAll' task itself.

Ok, so now that we have SWF files compiling, how can we launch our app to see it in action? Again, we will need to create a new task.

CODE:
  1. <target name="launch">
  2.     <exec executable="${flashplayer}" spawn="false">
  3.         <arg line="'${deploydir}/main.swf'" />
  4.     </exec>
  5. </target>

Very similar to the task we created for compiling, however this time we are launching Flash Player and passing the SWF file we would like to open as an argument. The 'spawn' attribute of the 'exec' node is set to 'false' by default (meaning if you do not list it), however I wanted to show it here because flipping it to 'true' will disable trace outputs in the console which may be desirable.

So far so good. Currently, to build and test our app we must first run the task 'compileAll' then 'launch'. This is ok, but we could easily reduce this to one step.

CODE:
  1. <target name="compileAllAndLaunch" depends="compileAll, launch"/>

Again, using the beautiful 'depends' attribute, we simply specify to run the task 'compileAll' followed by 'launch'.

At this point, Ant is saving us a ton of time. We can easily compile each SWF file individually or all of them at once. We can also launch the app to see it in action and choose to receive trace outputs in the console. One additional task that I always use is a task for generating documentation. If you have been using JavaDoc commenting throughout your code, you can use the Adobe ASDoc tool that is supplied with the Flex 3 SDK.

CODE:
  1. <target name="generateDocs">
  2.     <delete includeemptydirs="true">
  3.         <fileset dir="${docsdir}" includes="**/*" />
  4.     </delete>
  5.     <exec executable="${asdoc}" spawn="true">
  6.         <arg line="-doc-sources '${classesdir}' -output '${docsdir}' -main-title 'Your App API' -window-title 'Your App API'" />
  7.     </exec>
  8. </target>

This task simply goes into the specified source directory and outputs html documentation. You can specify the title that appears on the top of the documents as well. The 'delete' node at the beginning simply clears the docs directory prior to generating the new documentation.

Before moving on to the next section, I also wanted to mention that you can just as easily use these same techniques to compile, test, and deploy AIR applications. You will need to create some additional properties that point towards the necessary directories and command-line tools. Some sample AIR properties:

CODE:
  1. <property name="airlibsdir"   value="${flex3libsdir}/air"                             />
  2. <property name="adl"          value="${flex3bindir}/adl.exe"                          />
  3. <property name="adt"          value="${flex3bindir}/adt.bat"                          />
  4. <property name="appxml"       value="${basedir}/application.xml"                      />
  5. <property name="icondir"      value="${basedir}/icon"                                 />

Instead of mxmlc and Flash Player, you will want to compile and test your app using 'adl'. When it comes time to package your application as an AIR file, something like this will suffice using 'adt':

CODE:
  1. <target name="deploy" depends="generateDocs">
  2.     <exec executable="${adt}" spawn="true">
  3.         <arg line="-package '${deploydir}/${appname}' '${appxml}' '${icondir}' '${mainswf}'" />
  4.     </exec>
  5. </target>

That pretty much covers most of the basics for compiling your AS3 with Ant. Now let's move on to some lesser-known things you can do (in the Flash world at least).

Advanced Ant

Ant can do more than just compile and launch your projects. You can even use it to handle SVN requests, FTP files to a server, and package your applications for hand-off to a client. One of my favorites is that last one - packaging up your goods for a client hand-off. What I like to do is create an empty directory, copy only certain file types and directories into it, then zip it up so I can email it or whatever.

CODE:
  1. <target name="package" depends="generateDocs">
  2.     <copy overwrite="true" todir="../my_app_for_client">
  3.         <fileset dir="../my_app">
  4.             <exclude name="**/.settings/**" />
  5.             <exclude name="**/*.as3_classpath"/>
  6.             <exclude name="**/*.project"/>
  7.             <exclude name="**/.svn/**" />
  8.         </fileset>
  9.     </copy>
  10.     <zip file="../my_app_for_client.zip">
  11.         <fileset dir="..">
  12.             <include name="**/my_app_for_client/**" />
  13.         </fileset>
  14.     </zip>
  15. </target>

By using 'fileset' nodes, a collection of directories/files can be specified and filters can be put in place using 'include' and 'exclude' nodes. In the example above, I am copying all files over except for the FDT project files and SVN databases.

Another cool thing that you can do is check to ensure that certain properties have been set prior to running a task to prevent unwanted bad happenings.

CODE:
  1. <target name="checkForASDoc">
  2.     <fail unless="asdoc">The 'asdoc' property has not been set. Please set this property with a reference to 'asdoc.exe' on your hard drive.</fail>
  3. </target>
  4.  
  5. <target name="generateDocs" depends="checkForASDoc">
  6.     <delete includeemptydirs="true">
  7.         <fileset dir="${docsdir}" includes="**/*" />
  8.     </delete>
  9.     <exec executable="${asdoc}" spawn="true">
  10.         <arg line="-doc-sources '${classesdir}' -output '${docsdir}' -main-title 'Your App API' -window-title 'Your App API'" />
  11.     </exec>
  12. </target>

Now, thanks to the 'depends' attribute, 'checkForASDoc' will be called prior to the 'generateDocs' task taking action. If the 'asdoc' property has not been set, the specified message will be output to the console.

We can go one step further and create actual conditionals as well. Let's say we created a property named 'createdocs' and we wanted to be able to set it to 'true' or 'false'; the intent being to create docs or not create docs based on the value that is set.

CODE:
  1. <target name="checkForDocs">
  2.     <condition property="docstarget" value="generateDocs">
  3.         <equals arg1="${createdocs}" arg2="true" />
  4.     </condition>
  5.     <condition property="docstarget" value="noDocs">
  6.         <equals arg1="${createdocs}" arg2="false" />
  7.     </condition>
  8.        
  9.     <antcall target="${docstarget}" />
  10. </target>

It's a little weird how it reads, but essentially you create a 'condition' node and it's child nodes specify the comparisons. In this case, we use an 'equals' node to compare 'arg1' to 'arg2'. If the statement is true, the specified local property 'docstarget' is set to the value specified by the 'value' attribute in the 'condition' node.

Final Word

This post could go on forever discussing the cool stuff you can do with Ant, but I think you get the idea. Complete documentation for Ant and it's many tasks can be found here.

On a final note, if you run into any problems while running mxmlc, specifically ones complaining about 'jvm.dll', you most likely have JRE 6+ installed. Uninstall it and grab JRE 5 from the archive located on the Java site. Hopefully Adobe will address this in the months to come.

If any of you have additional tasks that you find especially useful that I didn't cover in this post, feel free to post away in the comments.

15 comments

Anatomy Of A Bitmap Caching System

Earlier this year, I was working on a project that involved some heavy loads of content being displayed on screen at once and was targeted for Flash Player 8. Since the project was a full screen Flash project and the heavy loads of content were going to be in re-sizable windows that also needed some drag-and-toss physics on them, I knew there was obviously going to be some serious performance issues. To get around this, I developed an architecture for caching all inactive content, as well as windows that were currently getting dragged, as rasterized bitmap images. The result was a dramatic increase in performance.

Before diving into the architecture, here is a very quick and simple example of using a bitmap data object to draw the contents of a movie clip to a single bitmap file.

ActionScript:
  1. public function cache(mcToCache:MovieClip, mcContainer:MovieClip):Void
  2. {
  3.     // Create a new bitmap data object and draw the contents of 'mcToCache' into it.
  4.     var objBitmap:BitmapData = new BitmapData(mcToCache._width, mcToCache._height, true, 0x000000);
  5.     objBitmap.draw(mcToCache);
  6.    
  7.     // Now attach the bitmap to the specified container movie clip.
  8.     mcContainer.attachBitmap(objBitmap, 0, "always", true);
  9. }

Though the concept of drawing image files to bitmap data objects for the sake of caching and quick loading as been used by many, this system goes beyond that by drawing entire containers full of content to bitmap files, then seamlessly returning them to live versions in their previous states. Since the containers contained a variety of objects such as images, text, buttons, scroll bars, and scroll panes, I used a not-so-widely known, but very powerful design pattern called 'the momento pattern'. The momento pattern provides an elegant solution for tracking an object's state. There are three objects involved in the pattern - the momento, the originator, and the caretaker. Here's how it works:

The Momento

The momento object has an API for storing and retrieving information about a specific object's state. Let's say for instance that information about a scroll bar's current state needs to be tracked. Your momento object might look something like this:

ActionScript:
  1. class ExampleMomento
  2. {
  3.     private static var DEFAULT_SCROLL_VALUE:Number = 0;
  4.    
  5.     private var nScrollValue:Number;
  6.    
  7.     public function ExampleMomento()
  8.     {
  9.         init();
  10.     }
  11.    
  12.     public function init():Void
  13.     {
  14.         nScrollValue = DEFAULT_SCROLL_VALUE;
  15.     }
  16.    
  17.     public function getScrollValue():Number
  18.     {
  19.         return nScrollValue;
  20.     }
  21.    
  22.     public function setScrollValue(nScrollValue:Number):Void
  23.     {
  24.         this.nScrollValue = nScrollValue;
  25.     }
  26. }

The Originator

The originator is the object who is producing the momento object and whose state is in need of being tracked. The originator must have an API for getting and setting it's momento object.

ActionScript:
  1. class OriginatorExample extends MovieClip
  2. {
  3.     private var mcScrollBar:ScrollBarComponent;
  4.    
  5.     private var objMomento:ExampleMomento;
  6.    
  7.     private function OriginatorExample()
  8.     {
  9.         init();
  10.     }
  11.    
  12.     public function init():Void
  13.     {
  14.         objMomento = new ExampleMomento();
  15.     }
  16.    
  17.     public function getMomento():ExampleMomento
  18.     {
  19.         objMomento.setScrollValue(mcScrollBar.value);
  20.        
  21.         return objMomento;
  22.     }
  23.    
  24.     public function setMomento(objMomento:ExampleMomento):Void
  25.     {
  26.         this.objMomento = objMomento;
  27.        
  28.         mcScrollBar.value = objMomento.getScrollValue();
  29.     }
  30. }

The Caretaker

The caretaker is the object that temporarily stores a momento object for the originator. In our particular example, let's say the originator gets temporarily removed. The caretaker will get the originator's momento object which contains data about the current state of the originator (such as it's scroll bar) before it is removed, then removes it. Upon re-attaching the originator to the display, the caretaker will pass it the momento object so that the originator can resume it's previous state. In our example, the position of the scroll bar handle will be resumed.

ActionScript:
  1. class CaretakerExample extends MovieClip
  2. {
  3.     private var mcContainer:MovieClip;
  4.    
  5.     private var mcOriginator:ExampleOriginator;
  6.    
  7.     private var objOriginatorMomento:ExampleMomento;
  8.    
  9.     private function CaretakerExample()
  10.     {
  11.        
  12.     }
  13.    
  14.     public function removeOriginator():Void
  15.     {
  16.         objOriginatorMomento = mcOriginator.getMomento();
  17.        
  18.         mcContainer.removeMovieClip();
  19.     }
  20.    
  21.     public function attachOriginator():Void
  22.     {
  23.         createEmptyMovieClip("mcContainer", getNextHighestDepth());
  24.        
  25.         mcOriginator = mcContainer.attachMovie("Originator", "mcOriginator", 0);
  26.        
  27.         mcOriginator.setMomento(objOriginatorMomento);
  28.     }
  29. }

Now, combining the bitmap caching technique with the momento pattern, you have a pretty solid system for caching and uncaching objects on stage. Here are some simple example files of this system in action:

Download 'bitmap_cache_system_example.zip'

The more complex the content being cached is, the more careful you need to be that everything is ready to go when the momento object is passed back to the originator. In the example files, you will notice that in the case of the toggle, I used the 'onLoad' handler to wait until the toggle class is loaded into memory and registered to the toggle movie clip before re-applying it's previous state. This practice is especially important when dealing with multiple UI components that are heavy in nature, such as a drop down menu.

I hope some of you find this practice as helpful as I have. In the project I was working on (it's not public yet, so I can't show it) I was able to maintain a frame rate of no less than 26 frames per second while dragging windows full of text and components around the stage with many other windows present as well. With the system disabled, the frame rate was so slow that the site was basically unusable. That's a massive difference, so definitely consider this in your next project if performance is a concern.

3 comments

Pictures From The Cape

Cape Cod 2007

For those interested, I finally got around to throwing a gallery together with some of the pictures I took while I was up in Cape Cod for a few weeks.

http://www.boostworthy.com/2007/capecod/

I love the Cape.

1 comment

Adobe AIR Extension For Flash CS3

Adobe has officially released an extension for previewing and packaging AIR applications directly within Flash CS3. The extension is a free download and can be found here on Adobe Labs.

No comments

Notes From Yesterday’s Adobe onAIR Event

Hats off to Adobe for hosting a great event here in Atlanta. For a free event, I must say that they definitely took good care of us. A variety of great food and drinks were available buffet-style in the main lounge for breakfast, lunch, and then even happy hour (which included alcoholic beverages). They also gave out a lot of prizes and goodies which was definitely nice.

As far as the speakers and presentations went, my favorite was Christian Cantrell's on the embedded database API. He was an excellent speaker and I found the presentation to be the most interesting. The Mikes (Downey and Chambers) were entertaining as well.

Here are a few tidbits that I found especially noteworthy:

- Buzzword's text rendering engine is entirely written by them, in ActionScript. Very impressive.
- Using a single application icon that is 128 x 128 will render results just as well as if you included icons for four separate sizes.
- Aptana supports the development of AIR projects.
- The AIR embedded database may support actual media file types in the future (not references). Whoa.

I think it goes without question that the highlight demo of the day was my friend (and fellow Schematic employee) Alan Queen's demonstration of his audio application "DigiMix" that he has been working on the past few weeks. Beyond it's great use of both Flex and AIR technologies, what really makes it remarkable is that he has found a way to support audio formats other than MP3 (such as WAV and AIF) entirely in Flash. This is a huge breakthrough and will undoubtedly be a huge deal when he begins releasing all of this in the future. For now, if you didn't see it yesterday, you will have to wait until a video of it (filmed by Ryan Stewart) appears on the Adobe site in the near future.

Overall it was an awesome day and I was super pumped to see a lot of familiar faces; some of which I have not seen for quite some time. If you are debating whether or not to attend one of these events in a city near you, I highly recommend you do.

On a final note - sorry to all of you who had to witness me pummeling my co-workers in Wii Tennis in the main lounge. I'm a ninja at the net; back up off me.

More information on the tour can be found here.

No comments

Adobe onAIR Event :: Live From Fox Theater

If you're here and you are reading this, a bunch of us from Schematic are at the front table along the left side of the main theater. Come by and say hello!

No comments

Back From The Cape!

I have just returned from an amazing two weeks in Cape Cod - and just in time for tomorrow's (or looking at the clock, technically today's) Adobe onAIR Event. If you are in the Atlanta area, drop in and say hello; I will be there all day (9:15am - 7:15pm).

Regarding the Cape, it was a very inspirational time for me to put down the computer for a few weeks and spend some time with good ol' nature and my trusty Canon SLR. I took around 700 photos while I was there, all of which are loaded into Aperture where I will be reviewing and organizing them for a gallery which I plan to launch...hopefully within the next few weeks.

On a final note - I have a ton of emails and stuff to catch up on, so if you contacted me within the past few weeks I will be getting back to you soon.

No comments