Sizable applications are often in need of externalizing code from the main application. Often times this is done via a library or framework and it is well understood in the Flash community how to use these in our applications. What is less-understood is how we can go about delegating some parts of our applications to plugins. Read on for a simple technique to get plugins into your Flash application.

When you write a simple AS3 application, you’ll usually write it as a single SWF. It may load external assets like images, other SWFs, or videos, but your application will likely be a single SWF. I call this the one SWF solution. As your application gets more advanced and you feel the need to split out parts of it as they are more generally useful as libraries, you’ll start to compile those libraries to a SWC (which, remember, is a ZIP file containing a SWF) and then compile the library into your application SWF. This is a two SWF solution. The present article is about a plugin system which, as you might have guessed, constitutes a three SWF solution. Here is a breakdown of the three SWFs:

  • App SWF – Full implementation of all of your application’s classes and interfaces
  • API SWF – Dummy implementations of all the classes and interfaces plugins are allowed to use
  • Plugin SWFs – Plugin-specific classes and interfaces.

The App SWF is cleanly split from the Plugin SWFs and the API SWF since it doesn’t compile against either. Instead, it simply loads Plugin SWFs and uses what it finds it has loaded.

The API SWF doesn’t depend on the App SWF or the Plugin SWFs since it too doesn’t compile against either. It is full of dummy classes whose function, variable, and constant definitions need not be specified beyond what is required to compile. For example, a function returning void need not have any body and a function returning int may simply have the body return 0;. These dummy classes need not even be complete as they do not need to specify any function, variable, or constant that you don’t want plugins to know about at compile time. The point of the API SWF is simply to provide a SWC (which, again, contains a SWF) containing an interface for the plugins to compile against.

The Plugin SWFs are the only SWFs that have any dependency: they depend on the API SWF. Still, since the API SWF is a subset of the classes and interfaces in the App SWF, the plugin is largely cut off from full access to the application. It can, however, get at unspecified functions, variables, and constants at runtime runtime by using dynamic access techniques like someAPIClassObject["hiddenFunc"](); or ApplicationDomain.currentDomain.getDefinition("SomeAppClass").

Let’s see how this all works with some concrete examples starting with a simple app:

Vector2.as
package
{
	public class Vector2
	{
		public var x:Number;
		public var y:Number;
		public function magnitude(): Number
		{
			return Math.sqrt(x*x + y*y);
		}
	}
}
IHasVector.as
package
{
	public interface IHasVector
	{
		function get vector(): Vector2;
	}
}
App.as
package
{
	import flash.display.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.text.*;
	import flash.net.*;
 
	public class App extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function App()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onPlugin1Loaded);
			loader.load(new URLRequest("../plugins/Plugin1.swf"));
		}
 
		private function onPlugin1Loaded(ev:Event): void
		{
			var vec:Vector2 = IHasVector(ev.target.content).vector;
			log("Plugin 1 x=" + vec.x + ", y=" + vec.y);
 
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onPlugin2Loaded);
			loader.load(new URLRequest("../plugins/Plugin2.swf"));
		}
 
		private function onPlugin2Loaded(ev:Event): void
		{
			var vec:Vector2 = IHasVector(ev.target.content).vector;
			log("Plugin 2 x=" + vec.x + ", y=" + vec.y);
		}
	}
}

As you can see, the App SWF simply loads two Plugin SWFs (Plugin1.swf and Plugin2.swf), treats them as an IHasVector, and prints their Vector2 values. Here’s how to compile the app:

mxmlc app/App.as

Easy! Now let’s look at the classes and interfaces in the API SWF:

Vector2.as
package
{
	public class Vector2
	{
		public var x:Number;
		public var y:Number;
		public function magnitude(): Number { return 0; }
	}
}
IHasVector.as
package
{
	public interface IHasVector
	{
		function get vector(): Vector2;
	}
}

There isn’t much code to strip out here, but you can see that the example magnitude function in Vector2 was replaced by simply returning 0. In a real app there would be a lot more code to strip out, but this is just an example. Here’s how you compile the API SWF:

compc -sp api -is api -output api/API.swc

Here we have a couple of unusual arguments to compc. The first, -sp api specifies the source path of the API class and interface files. In my example environment, I named this api. The second is the input source, which is the same as the source path. Lastly, I specify the name of the output SWC to produce. This SWC is what you give to plugin developers to compile against, as we’ll see in the next section. Let’s get right to that and take a look at a couple of trivial plugins:

Plugin1.as
package
{
	import flash.display.*;
 
	public class Plugin1 extends Sprite implements IHasVector
	{
		private var __vector:Vector2;
 
		public function Plugin1()
		{
			__vector = new Vector2();
			__vector.x = 1;
			__vector.y = 2;
		}
 
		public function get vector(): Vector2
		{
			return __vector;
		}
	}
}
Plugin2.as
package
{
	import flash.display.*;
 
	public class Plugin2 extends Sprite implements IHasVector
	{
		private var __vector:Vector2;
 
		public function Plugin2()
		{
			__vector = new Vector2();
			__vector.x = 3;
			__vector.y = 4;
		}
 
		public function get vector(): Vector2
		{
			return __vector;
		}
	}
}

The only difference here is the values stored in the vector. The plugins are, however, free to do any work they need to as their constructors will be called once loaded and they then have free reign to do whatever they’d like, including usage of Vector2.magnitude or any other functionality exposed through the API SWC. Here’s how you compile the plugins:

mxmlc plugins/Plugin1.as -external-library-path=api/API.swc
mxmlc plugins/Plugin2.as -external-library-path=api/API.swc

Specifying the external library path tells the compiler to not add any used classes from the API SWC into the Plugin SWFs. It’s saying “trust me, these will already be around when you load me”. Since your App SWF runs first and in so doing defines the actual Vector2 class and IHasVector interface, you’re holding up your end of the deal with the compc compiler. If you try to run the plugin outside of the App SWF, you’ll get a runtime error telling you that the IHasVector or Vector2 class isn’t defined and your plugin will fail to run. If you run it the proper way though, you’ll get the following output:

Plugin 1 x=1, y=2
Plugin 2 x=3, y=4

In conclusion, this is a simple-to-understand and simple-to-implement plugin system for AS3 and Flash. It carries no overhead in API classes compiled into the plugins, no overhead in speed to implement a scripting language such as Lua or JavaScript, and allows you full, native AS3 speed and power in your plugins. The main drawback is that you need to define your API and that may be tedious and present a maintenance problem. However, a script could be developed to help keep these in sync similar to those scripts that generated intrinsic classes for AS2. Any volunteers?

Download the above demonstration app, API, and plugins, and build script.