Archive for the ‘Flash Pitfalls’ Category

Document Classes… with Interfaces!

Often, a Flash developer will want to load an external swf and assign the contents to a strongly typed variable so as to take advantage of the compile-time and run-time safeguards associated with strong typing, as well as the intellisense capabilities of mature coding environments like FlashDevelop. Unfortunately, it can be less straightforward than we’d like to simply import the class definition for the external swf’s Document Class, particularly if our external content has any library items set to export with a Class name, or relies on Flash to automatically declare stage instances.

For an example of how this goes wrong, see the following zip file:

If you compile the LoadedContent.fla everything works just fine. The problem comes when you try to compile the ContentLoader, which mysteriously complains about problems in LoadedContent.as. You may wonder why there are suddenly problems in that class now when it compiles perfectly fine in the LoadedContent.fla. Let’s open up LoadedContent.as, as the answer is there:

package  {
	import flash.display.MovieClip;
	import flash.text.TextField;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public class LoadedContent extends MovieClip{
		public function get asset1():LoadedContentAsset {
			return _asset1; 
		}
 
		private var _asset1:LoadedContentAsset;
		private var _asset2:LoadedContentAsset;		
		private var _someClip:MovieClip;
 
 
		public function LoadedContent() {			
 
		}
 
		public function showImages():void {
			_asset1 = new Asset1(); // Declared in the .fla library
			_asset2 = new Asset2(); // Declared in the .fla library
 
			_asset1.x = 100;
			_asset1.y = 100;
			_asset2.x = _asset1.x + _asset1.width;
			_asset2.y = 100;
 
			this.addChild(_asset1);
			this.addChild(_asset2);
 
			_someClip = new SomeClip(); // Declared in the .fla library
 
			_someClip.x = _asset2.x + _asset2.width;
			_someClip.y = 100;
 
			this.addChild(_someClip);
		}
 
		public function sayHello():String {
			this.text_txt.text = "Hello World"; // exists on the timeline.
			return this.text_txt.text;
		}
 
	}
 
}

Notice how, when we compile the ContentLoader.fla and read the error messages they’re all pointing to lines of code that I’ve marked with a comment. The reason LoadedContent.fla compiles correctly is because, in the case of Asset1, Asset2, and SomeClip, the classes it’s instantiating exist in its library. LoadedContent.fla knows where to look for them, and has a meaningful point of reference to them. ContentLoader does not. It just sees a Class definition trying to perform actions with other classes that don’t exist. The same problem exists with the reference to this.text_txt in LoadedContent.as. Because LoadedContent.fla is using the default compiler setting of “Automatically Declare Stage Instances”, it can simply reference the text field without actually calling this.getChildByName() or similar. All ContentLoader sees when it reads that line in the LoadedContent definition is “You’re trying to perform an action on an undeclared variable!”

What to do? We could simply cast the loaded content as MovieClip or Object, since those are Dynamic classes. That would silence the error, but it would leave us without the benefits of a strongly typed environment. Since The Horseman openly admits his bias in favor of strongly typed code, he feels compelled to illustrate how this can still be done. The key is interfaces.

An interface is nothing more than a “contract”. When a Class implements an Interface, it promises that the functions and properties of the interface will be available for use by other data, eg: declared as public. Absolutely no implementation details are present in the Interface. When put in context of the LoadedContent situation, it means that it doesn’t define, know about, or care about any such thing as Asset1, Asset2, SomeClip, or this.text_txt. Here’s an example:

package  {
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public interface ILoadableContent {
 
		function showImages():void;
		function sayHello():String;		
 
	}
 
}

By convention in ActionScript, Interfaces always start with the letter ‘I’. This differentiates them from Class definitions at a glance. What this Interface tells us is that any Class that implements it is guaranteed to have public functions with the same function signatures as shown above. Luckily for us, LoadedContent already has those functions defined! It just needs to officially implement the interface like so:

package  {
	import flash.display.MovieClip;
	import flash.text.TextField;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 * 
	 *    Implement the interface here in the LoadedContent.
	 * 
	 */
	public class LoadedContent extends MovieClip implements ILoadableContent{
 
	// and down further are the public functions expected by the Interface.

Now in our ContentLoader we just need to replace the references to LoadableContent with references to ILoadableContent.

package  {
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.net.URLRequest;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public class ContentLoader extends MovieClip{
 
		// Type the content as the Interface, not the concrete Class.
		private var _loadedContent:ILoadableContent;
		private var _loader:Loader;
 
 
		public function ContentLoader() {
			_loader = new Loader();
			_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onEventCompleteLoader);
			_loader.load(new URLRequest("LoadedContent.swf"));
		}
 
		private function onEventCompleteLoader(e:Event):void {
			e.currentTarget.removeEventListener(Event.COMPLETE, onEventCompleteLoader);
			_loadedContent = e.target.content as ILoadableContent;
			_loader.unload();
			_loader = null;
 
			// Casting because an interface cannot be a DisplayObject, sadly.
			this.addChild(_loadedContent as DisplayObject); 
 
			_loadedContent.showImages();
			var hello:String = _loadedContent.sayHello();
			trace(hello);
		}
 
	}
 
}

Now the ContentLoader.fla will compile correctly. Since it isn’t looking at the concrete LoadedContent, but instead only looking at the Interface ILoadedContent there are no more conflicts to resolve. The only thing about this that is slightly bothersome is the fact that if you have a reference to a DisplayObject but only know of it by its interface, you cannot use it as an argument in a call to addChild() without first casting it as a DisplayObject. This is however, a very small tradeoff for the compile-time, run-time, and author-time benefits of working in a strongly-typed manner with a modern coding environment.

Here is a zip file containing the new and improved code.

The implications of this reach further than loading, however. If you code your components so that they implement interfaces, you can switch them out with each other more easily than if they are simply Classes, and referenced as such.

EDIT: These days instead of coercing the ILoadableContent into being a DisplayObject, I would instead give the ILoadableContent interface a get view function like this

package  {
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public interface ILoadableContent {
 
		function get view():DisplayObject;
		function showImages():void;
		function sayHello():String;		
 
	}
 
}

That way, when you call addChild you no longer have to blindly assume that the ILoadableContent is a DisplayObject. This is also nice in that it allows the ILoadableContent to choose a displayObject to act as its own view. Maybe it should be the view and it returns ‘this’. Maybe it has some other object it wants to be the view instead? It doesn’t have to matter with the get view function.

Tags: , , , ,

1 Comment


trace(fail = true);

I don’t understand it! This section of my code that I haven’t touched in days doesn’t work anymore!

There are a lot of reasons code might suddenly “go bad” for no apparent reason. Generally, it’s related to some unexpected “garbage in -> garbage out” type of situation at runtime, but at times it’s a bit more subtle than that. Attached are two separate flas with identical ActionScript. Both of them compile correctly, and neither throw any runtime errors. One of them exhibits some very strange runtime behavior. See if you can figure out what’s the cause.

What do you suppose could be the issue here?

If you guessed that it has something to do with the trace statements, you’re on the right track.

“But how can the trace statements be the cause of the problem? All they do is write output to the console window.”

Indeed, the trace statement itself does nothing other than print the final expression of its contents to the console window. But have a close look at a couple of the trace statements. Notice anything odd?

trace(m*=Math.PI);
trace(n++);

I frequently hear the phrase “laziness is a virtue” used to describe the qualities of an excellent programmer. Well what could be lazier, and thus closer to perfection than combining our evaluations and our debug output into the same line of code?

Omit trace actions

trace(fail = true);

Oh.

In order to save some filesize, as well as to cut down on the processor load associated with spewing out a lot of trace statements, it appears that the author of the second file has elected to Omit trace actions. What were we doing in those trace statements earlier? Oh, it looks like we were evaluating something. As you can see, if you decide to omit the trace actions (or even just boneheadedly comment them out) you’ll lose some actual logic that you really needed.

Friends don’t let friends evaluate inside trace statements. I’m sure this is equally true in other programming languages.

Tags: , , ,

2 Comments


Clash of the Instance Names

“What do you mean it’s weird to give the same instance name to different library symbols, on different frames of the same timeline?”

As you probably know, the world of Flash Development is populated largely by people who aren’t from a traditional programming background. The early iterations of ActionScript were incredibly lenient and forgiving in terms of the sort of… odd… things it would allow a developer to get away with. If ActionScript were one of your first languages, you might not even think of such a thing as odd! Things such as this…

A clash of instance names in the Flash IDE
Illustrating the root of the conflict.

The really big mistake here is that all of these library items inherit from the same user-defined Base Class, com.scriptocalypse.NewMovieClip.

package com.scriptocalypse {
	import flash.display.MovieClip;
	import flash.text.TextField;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public class NewMovieClip extends MovieClip{
 
		public function NewMovieClip() {
			trace(this + " instantiated.");			
			this.stop();
			this.mouseChildren = false;
			this.text_txt.text = this.toString();
		}
 
	}
 
}

Those of you still fresh out of AS2 might be scratching your heads over why this is suddenly an error of grave consequence. “I’ve been doing that for years, and nothing bad has ever happened!” Those of you from more traditional programming backgrounds might be scratching your heads over the entire thing and wondering “Why in the world would you want two instances of two different Classes to have the same instance name on the timeline? Foo is Foo, and Bar is Bar.”

I’ll address the latter first: Early versions of ActionScript were perfectly capable of accommodating this, and in fact this made it incredibly quick and simple to use a frame-based state transition scheme. If you wanted a different visual state to have a different style button, and you also wanted to take advantage of the timeline for using visual transitions without having to write new code for every state view, you could do that very quickly and easily. You could have Foo000 be your “back_btn” on frame 1, Bar000 be your “back_btn” on frame 2, and AwsmeBtn be your “back_btn” on frame 3. The AVM1 simply did not care. As long as something had the instance name in question, it would use that and not care one bit about type-safety at runtime.

For the AS2 migrants, that bit about runtime type-safety is the reason you can’t get away with this anymore. See, what Flash does when you publish your swf is create a lot of code for you that you probably weren’t aware of. Let’s say your document class looks like this:

package com.scriptocalypse {
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public class DocumentClass extends MovieClip{
 
		public function DocumentClass() {
			this.stop();
			this.foo_mc.addEventListener(MouseEvent.CLICK, onClickFoo);
		}
 
		private function onClickFoo(e:MouseEvent):void {
			this.gotoAndStop(this.totalFrames);
		}
 
	}
 
}

That foo_mc you put on the timeline… how does Flash know to reference it, or even what it is? There’s a checkbox that is selected by default in the Flash IDE.

Click File -> Publish Settings -> Flash -> Click the “Settings…” button next to the ActionScript 3 dropdown.

Do you see a checkbox marked “Automatically declare stage instances”? When you have that box selected, your code above is modified by the compiler when you publish the swf. It ends up looking more like this:

package com.scriptocalypse {
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
 
	/**
	 * ...
	 * @author The Horseman @ Scriptocalypse.com
	 */
	public class DocumentClass extends MovieClip{
 
		public var foo_mc:NewMovieClip0; // Where did this come from?????
 
		public function DocumentClass() {
			this.stop();
			this.foo_mc.addEventListener(MouseEvent.CLICK, onClickFoo);
		}
 
		private function onClickFoo(e:MouseEvent):void {
			this.gotoAndStop(this.totalFrames);
		}
 
	}
 
}

Notice that Flash helpfully wrote this for you. Notice also that the datatype is the “Class” field in the library. Now, you’ve got about 5 frames worth of these foo_mc instances on your stage. This is kind of a problem! Flash wrote that instance in your class and gave it the same variable name as your NewMovieClip0’s instance name. What is Flash supposed to do with the other 4 instances? It can’t rename them for you, and it can’t create new and oddly named variables for them… you wanted a variable on your DocumentClass called foo_mc and you’re going to get it!

So you compile. You may or may not get a compiler warning, but it succeeds regardless! The swf can play. So, you’re sitting on Frame 1 as happy as you can be. You then click a button to attempt to go to the last frame, but then find that bedlam has broken loose. Here’s your output window:

[object NewMovieClip0] instantiated.
[object NewMovieClip4] instantiated.
TypeError: Error #1034: Type Coercion failed: cannot convert NewMovieClip4@4e3f421 to NewMovieClip0.

This happens any time you try to coerce a class into “being” something that it isn’t. While it is true that both NewMovieClip0 and NewMovieClip4 are “NewMovieClip” subclasses, and if foo_mc had been declared as a “NewMovieClip” instance by Flash they could be coerced freely… but they are not declared as the more general “NewMovieClip” and so cannot take advantage of polymorphism and fit inside this NewMovieClip0-shaped “container.”.

This behavior can, depending on your Flash Player version, lead to all manner of incredibly bizarre and hard-to-diagnose bugs. The absolute worst involves cases where there are sounds buried inside the timelines of the clips that fail to coerce. In Flash Player 9, it is entirely possible that if you were to call gotoAndStop(totalFrames) in your document class that you would see in your output window a message that every NewMovieClipX on every frame had instantiated, even though the timeline only “skimmed” across them. If these clips were to contain sound files on their timelines, you would hear these play in an infinite loop, and be left with no way to stop them selectively.

Tags: , , ,

1 Comment