There’s more to AS3’s break and continue statements than you might think. Chances are, you’ve used them to skip to after a loop (break) and skip the current loop iteration (continue), but they can do so much more. Today’s article will cover some of the advanced ways to use the break and continue statements in AS3 resulting in nicer—and maybe even faster—code.

Perhaps the most useful advanced usage of the break and continue statements is to break out of a nested loop. Consider this search for a value in a two-dimensional Vector:

function search(numbers:Vector.<Vector.<int>>, target:int): void
{
    var found:Boolean;
    outerLoop: for each (var row:Vector.<int> in numbers)
    {
        for each (var val:int in row)
        {
            if (val == target)
            {
                found = true;
                break outerLoop;
            }
        }
    }
    if (!found)
    {
        trace(target + " not found!");
    }
}

The key here is that we put outerLoop: before the outer loop and then, if we find the target value, we can specify the loop to break out of by not just saying break;, but instead by saying break outerLoop;. Just saying break; would only break out of the inner loop and we would continue needlessly searching the rest of the rows. For big Vectors, this can save a lot of CPU time!

To see how we would use continue in this way, consider an example where the values of a three-dimensional Vector are sorted by row and column in ascending order and we want to count the number of elements in the Vector that are less than some value threshold:

function lessThan(numbers:Vector.<Vector.<Vector.<int>>>, threshold:int): int
{
    var count:int;
    for each (var layer:Vector.<Vector.<int>> in numbers)
    {
        outerLoop: for each (var row:Vector.<int> in layer)
        {
            for each (var val:int in row)
            {
                if (val >= threshold)
                {
                    // the rest of this 2D grid must be greater too
                    // skip this 2D grid but keep checking subsequent 2D grids
                    continue outerLoop;
                }
                count++;
            }
        }
    }
    return count;
}

These uses of break and continue are not strictly necessary, but if we didn’t have them we would be forced to add Boolean flags to our loop conditions. These flags would slow down the loop by adding needless checking, not to mention the extra typing, stack size, and SWF bloat that would result when multiplied across all the loops where a tidy break label; could have been applied.

While the above is very useful, it is not the only advanced usage of break and continue in AS3. Labels allow us to break or continue non-loop blocks too. Consider this case with a plain “curly braces-only” block:

trace("pre");
block: {
	trace("in block");
	break block;
	trace("after break");
}
trace("post");
 
// OUTPUT:
// pre
// in block
// post

This example illustrates that if we are willing to add a label to the block, we can name it when we use break or continue. Of what use is this outside of contrived examples? Well, consider the following function that performs cumulative actions as more if statements are passed:

function atLeast(x:int): void
{
	trace("--begin " + x + "--");
	end: {
		if (x < 1) break end;
		trace("\tat least 1");
		if (x < 2) break end;
		trace("\tat least 2");
		if (x < 3) break end;
		trace("\tat least 3");
	}
	trace("--end--");
}

The requirements are that we always print the “begin” and “end” lines as well as the appropriate “at least…” lines when x is greater than 1, 2, or 3. We can implement this by simply typing out the three “at least…” lines and then adding cutoff points between them. How do we cut the printing off and jump to the “end” line? We use a named block (end), of course!.

Before you think that this is yet another contrived example, consider how you would implement this without the named block/break to label functionality. We can’t simply use return to end the function when you want to cut off the printing because that would skip printing the “end” line. We could decide to duplicate the “end” line printing in the cutoff detection if, but then we have to duplicate a bunch of code like this:

function atLeastEndDupe(x:int): void
{
	trace("--begin " + x + "--");
	if (x < 1) { trace("--end--"); return; }
	trace("\tat least 1");
	if (x < 2) { trace("--end--"); return; }
	trace("\tat least 2");
	if (x < 3) { trace("--end--"); return; }
	trace("\tat least 3");
	trace("--end--");
}

What a mess! If we had more lines of code at the end (e.g. multiple lines to print), we would have even more to duplicate in each and every cutoff. Another way we could implement this would be to duplicate the “at least…” lines like this:

function atLeastBodyDupe(x:int): void
{
	trace("--begin " + x + "--");
	if (x >= 3)
	{
		trace("\tat least 1");
		trace("\tat least 2");
		trace("\tat least 3");
	}
	else if (x >= 2)
	{
		trace("\tat least 1");
		trace("\tat least 2");
	}
	else if (x >= 1)
	{
		trace("\tat least 1");
	}
	trace("--end--");
}

This version is even longer! Yes, we’ve avoided duplicating the end statements, but now we have to duplicate all the middle statements. It would have been so much easier to just use a named break statement.

While named break statements are clearly better in terms of typing, maintenance, and (arguably) readability, we should investigate their performance to make sure we’re not inadvertently harming our app in that arena. Who knows, maybe we’ll end up with a performance gain. Here’s a performance-testing app that tests out the above three methods with some minor modifications:

  • trace statements (“begin”, “end”, “at least…”) replaced by simple int setting
  • Replaced return with continue in the inline version of atLeastEndDupe. Ironically, a named block would have been necessary if this wasn’t being done in a loop!
package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class LabelsTest extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function LabelsTest()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			var beforeTime:int;
			var afterTime:int;
			const REPS:int = 100000000;
			var x:int;
			var y:int;
 
			log("Method,Time");
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				atLeast(x);
			}
			afterTime = getTimer();
			log("Labels," + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				atLeastEndDupe(x);
			}
			afterTime = getTimer();
			log("End Dupe," + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				atLeastBodyDupe(x);
			}
			afterTime = getTimer();
			log("Body Dupe," + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				y = 0;
				end: {
					if (x < 1) break end;
					y = 1;
					if (x < 2) break end;
					y = 2;
					if (x < 3) break end;
					y = 3;
				}
				y = 4;
			}
			afterTime = getTimer();
			log("Labels (inline)," + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				y = 0;
				if (x < 1) { y = 4; continue; }
				y = 1;
				if (x < 2) { y = 4; continue; }
				y = 2;
				if (x < 3) { y = 4; continue; }
				y = 3;
				y = 4;
			}
			afterTime = getTimer();
			log("End Dupe (inline)," + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (x = 0; x < REPS; ++x)
			{
				y = 0;
				if (x >= 3)
				{
					y = 1;
					y = 2;
					y = 3;
				}
				else if (x >= 2)
				{
					y = 1;
					y = 2;
				}
				else if (x >= 1)
				{
					y = 1;
				}
				y = 4;
			}
			afterTime = getTimer();
			log("Body Dupe (inline)," + (afterTime-beforeTime));
		}
 
		private function atLeastBodyDupe(x:int): void
		{
			var y:int;
			y = 0;
			if (x >= 3)
			{
				y = 1;
				y = 2;
				y = 3;
			}
			else if (x >= 2)
			{
				y = 1;
				y = 2;
			}
			else if (x >= 1)
			{
				y = 1;
			}
			y = 4;
		}
 
		private function atLeastEndDupe(x:int): void
		{
			var y:int;
			y = 0;
			if (x < 1) { y = 4; return; }
			y = 1;
			if (x < 2) { y = 4; return; }
			y = 2;
			if (x < 3) { y = 4; return; }
			y = 3;
			y = 4;
		}
 
		private function atLeast(x:int): void
		{
			var y:int;
			y = 0;
			end: {
				if (x < 1) break end;
				y = 1;
				if (x < 2) break end;
				y = 2;
				if (x < 3) break end;
				y = 3;
			}
			y = 4;
		}
	}
}

I ran the above performance test on this environment:

  • Flex SDK (MXMLC) 4.1.0.16076, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 10.3.181.14
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.6.7

And got these results:

Method Time
Labels 744
End Dupe 741
Body Dupe 727
Labels (inline) 214
End Dupe (inline) 202
Body Dupe (inline) 183

Here is the above table in chart form:

Labels Performance

For all its merits, the labeled break is unfortunately slower than the alternatives by 2.3% in the function case and a whopping 17% in the inline case. You will have to decide if the performance tradeoff is worth it, but it probably is when performance isn’t critical.

Know of any other advanced uses for break and continue? Post a comment below!