AlecMcE.com

Coding on the Flash Platform

Replacing ENTER_FRAME

View Comments

Today an interesting discussion took place on Twitter, in which I was involved. It caught my attention for a variety of reasons, and I wanted to document the conversation, the problem and the solution here. It started when it was identified that Flash’s event dispatcher is sub-optimal in performance terms, and revolved around trying to find a solution.

Why replace the ENTER_FRAME?

Every event that is dispatched creates an object. An ENTER_FRAME creates as many objects per second as there are frames. If you have mutliple objects dispatching ENTER_FRAME events, that’s many objects creating event objects many times per second. Computationally, chains of “many” are one of those things you want to avoid. So, can we do better?

Doing Better

Robert Penner proposed creating an as3signal and using a MovieClip with two frames to dispatch that signal on each frame. He posted his idea to this GitHub gist.

One drawback to this approach is that it requires a MovieClip symbol with two empty frames, to which the two event dispatcher methods could be attached. I remembered reading in this excellent article by Bit 101 how using a [Frame] metadata tag would force your application to have two frames. This seemed to be what Penner needed for his functionality to work correctly, so I came up with my own GitHub gist which extends the concept. The code is also added ‘below the fold‘, at the bottom of this article.

[Update, after Jackson's comment, below] The idea behind the approach is that a frame-loop is an internal Flash player structure. It sits underneath the AS3 functionality, and so it doesn’t produce ENTER_FRAME events. By hooking into them we can produce our own iterative ‘event’ (Penner proposes using an as3signal), which doesn’t construct an object each time it is dispatched. It is a lovely idea, and quite distinct from invoking one ENTER_FRAME dispatcher on the root or stage of the application.

[Update, after Joa Ebert tweeted test results] Whether this method is superior to the ‘one-event ticker’ is open to question. Joa Ebert applied the frameloop concept to AudioBox and found no memory nor performance improvements.

Background

It all started today with a statement on Twitter from Robert Penner:

Listening to a single #AS3 enterFrame will create over 1000 event instances per minute. @robpenner

In fact, at a decent frame rate, you’re looking at 1800 event instances per minute. Across the blogosphere and on Twitter, there has been a lot of talk about the cost of object construction in AS3. People are pushing the limitations of the player, and are looking for performance optimisation wherever they can find it. Strategies such as object pooling are becoming mainstream in order to avoid the creation and destruction of objects. In the meanwhile, the internal Flash event model is creating and destroying huge numbers of objects to furnish us with an event model.

At the same time however, there has been a murmur of discomfiture about the state of the event model. (Not that I have much of a voice, but I chimed in to this debate as well!). The most notable result of this murmur was Penner’s decision to create as3signals. It is an on-going project to which I have contributed in a small way, and which I whole-heartedly support.

After Penner’s tweet, the #as3 twitterati (please tweet if there are glaring omissions from this list) became very excited indeed.

My initial thoughts led to the idea that maybe the setInterval/setTimeout functions, which are hangovers from AS2 might offer a solution – perhaps they shortcut the event system? Not only ain’t it so, they’re even worse as this Action Script Viewer deconstruction of the setInterval methods demonstrate.

So what else? Mike Chambers (aka @Mesh) in conversation with @TheFlashBum pointed towards a solution using a ‘ticker’; a single ENTER_FRAME dispatcher, and @bengarney confirmed that he also uses this technique.

It seems that @scottjanousek and @robpenner had the idea of using the player frameloop (which the solution offered here exploits) at pretty much the same time. (@scottjanousek’s tweets are protected, but it was reported by this tweet.)

Whether and how this vein of thought will develop is really interesting. It is fascinating how a tweet from one of the best-known ActionScript developers can provoke such a lot of attention from the general community. It is interesting too to see the envelope of what is possible being pushed.

FrameLoop Example Source Code

To use this code, you will need the as3signals library. You will also need to compile through the MXML compiler. I have tested it with the Flex 4 SDK – if you try and discover that it does not work for other target platforms, please share the information by leaving a comment!.

Main.as

package
{
	import org.osflash.signals.Signal;

	import flash.display.Shape;
	import flash.display.Sprite;

	/**
	 * A Main application class that uses the [Frame] MetaData Tag to use the
	 * EventFrameDispatcher class to configure itself. This class contains a
	 * frame signal which is constructed and passed into it by the
	 * EventFrameDispatcher class before init() is called. Application
	 * functionality should be placed in the init() method
	 *
	 * @author Alec McEachran
	 */
	[Frame(factoryClass="EnterFrameDispatcher")]
	public class Main extends Sprite
	{
		/** an as3signal that is dispatched on every ENTER_FRAME */
		public var frame:Signal;

		/**
		 * do interesting things in here...
		 */
		public function init():void
		{
			// functionality here...
		}
	}
}

EnterFrameDispatcher.as

package
{
	import org.osflash.signals.Signal;

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.utils.getDefinitionByName;

	/**
	 * The EnterFrameDispatcher is injected into the Main class through
	 * the [Frame] MetaData tag.
	 *
	 * The class creates a frame signal. By being injected, two frames are
	 * generated, onto which the frameMethod are attached so that the
	 * frame signal is dispatched on every frame cycle. It then injects this
	 * object into the Main class and calls the Main class's init() method.
	 *
	 * @author Alec McEachran
	 *
	 * based on an idea by Robert Penner @see http://gist.github.com/243630
	 */
	public class EnterFrameDispatcher extends MovieClip
	{
		public var frame:Signal;

		public function EnterFrameDispatcher()
		{
			frame = new Signal();
			addFrameScript(0, frameMethod, 1, frameMethod);

			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			nextFrame();
		}

		private function onEnterFrame(event:Event):void
		{
			removeEventListener(Event.ENTER_FRAME, onEnterFrame);
			init();
			play();
		}

		private function frameMethod():void
		{
			frame.dispatch();
		}

		private function init():void
		{
			var mainClass:Class = Class(getDefinitionByName("Main"));
			if (mainClass)
			{
				var app:* = new mainClass();
				app.frame = frame;
				addChild(app);
				app.init();
			}
		}
	}
}

Written by alec

November 26th, 2009 at 11:37 pm

  • cambiata
    Hi Alec!

    Thank you for sharing.

    As your code example doesn't "do" anything, maybe the following change/addition to your could clarify it, for Signal newbees like me...:

    public function init():void {
    // functionality here...
    trace('init');
    frame.add(onFrame);
    }

    private function onFrame():void {
    trace('onFrame');
    }

    Regards! / Jonas
  • og2t
    How about of triggering stage.invalidate() from within Event.ENTER_FRAME handler, and then subscribing to the Event.RENDER to defer updating objects on the screen?
  • I'm not sure that would give us any performance improvements would it? It is certainly neat in that it cuts down the number of event objects being constructed per cycle, but it is still double the events that the EnterFrame ticker uses: one event for the ENTER_FRAME and one for the RENDER.

    Still, a nice addition to the panoply of alternatives, thanks og2t.
  • @joa reports that he implemented the frameloop in his AudioBox tool, but found no memory nor performance improvements: http://bit.ly/7FGv2c, http://bit.ly/8KW0XM.
  • I don't think so Jackson. The frame loop is internal-to-player functionality; the player has no reason to construct an Event object when passing from frame to frame. That's why this solution is different and interesting! I have to admit, I haven't tested this assertion, mostly because it was late in the UK and I needed to go to sleep! It would seem that Penner has though: http://bit.ly/8IYtuv. I will try to today if I get the chance.

    I need to make this clearer in the post, I think, to avoid this confusion. Thanks for the feedback!
  • Great recap and lots of great info. Thanks for pulling it all together in one place.
  • jacksondunstan
    I too have (briefly) struggled with this issue. It seems to me that you have essentially two options: ENTER_FRAME and Timers. Both of these call your callback via the AS3 events system and therefore result in an Event allocation. What you've done above is to still use ENTER_FRAME, but then not use the AS3 event system to notify everyone of the new frame (as3signals uses its own lists of Functions) and therefore limit the Event object creation to just one. I use a similar approach in my own proprietary framework (owned by the company, sorry), but the core issue remains: thousands of Event objects per minute (60*stage.frameRate) are created and freed. This seems to be an intractable problem in AS3 Flash programming due to the fact that only ENTER_FRAME and Timer events can be used for rapid updates. I'd be super excited if this discussion led to a way around that limitation (the original problem tweeted by Robert Penner)...
  • I don't think so Jackson. The frame loop is internal-to-player functionality; the player has no reason to construct an Event object when passing from frame to frame. That's why this solution is different and interesting! I have to admit, I haven't tested this assertion, mostly because it was late in the UK and I needed to go to sleep! It would seem that Penner has though: http://bit.ly/8IYtuv. I will try to today if I get the chance.

    I need to make this clearer in the post, I think, to avoid this confusion. Thanks for the feedback!
  • jacksondunstan
    But his original tweet that you quoted says "Listening to a single #AS3 enterFrame will create over 1000 event instances per minute.". Then your whole "Why replace the ENTER_FRAME?" section says this again in even more depth. In your code example you are literally listening to a single AS3 enterFrame. The only savings I see is by making sure that you only listen to that *single* enterFrame and not have lots of enterFrame listeners. But if, as you claim now, Event objects are not created when passing from frame to frame, then why would it matter in the first place? Maybe I have missed something fundamental about this discussion... :)
  • Hi Jackson,

    I think you've missed the point - that or I'm being foolish!

    The hypothesis is that there's a difference between adding an event listener using addEventListener, and triggering a method to be called in a frame script. The first necessitates the creation of an Event object, but there's no reason why the method called by the addFrameScript should create an event object. In which case, there ought to be a marginal saving between a loop created by addFrameScript over a loop created by addEventListener. As I say, it's a hypothesis. Joa Ebert's results cast doubt about it, but until a rigorous, isolated test can be done, and both the test and results agreed to be accurate, we can't really be sure.

    Perhaps the confusion stems from the fact that I do in fact create an onEnterFrame listener in the EnterFrameDispatcher class? I do, because the [Frame] metadata tag works in such a way that the original class (Main) is inaccessible until the second frame. I have to wait for a frame to elapse before I can inject the frame Signal into the Main class. I add an ENTER_FRAME listener, then trigger nextFrame(). As soon as the onEnterFrame is triggered I remove that listener - it is not responsible for the loop.

    The loop is in fact generated by the addFrameScript(0, frameMethod, 1, frameMethod) line, which attaches the frameMethod function to both frames in the 2-frame timeline (created by the [Frame] metadata tag), which I then play.
  • jacksondunstan
    I see what you're getting at now. I saw the ENTER_FRAME listener but didn't see that you remove it and replace it with an addFrameScript. I hadn't heard of addFrameScript, probably because it's undocumented. It does seem to fix the problem I brought up in my initial comment of all rapid update callbacks requiring an AS3 event since this approach just uses a plain Function callback. I like the idea and really look forward to someone doing as you suggest: a rigorous, isolated test. Joa's results are, sadly, not encouraging. :( Thanks for the fascinating article and patient explanation!
blog comments powered by Disqus