divillysausages.com

A BitFlag class for AS3

If you come from a programming language like C or C++, you're probably familiar with the concept of bit flags. If you don't know what they are, bit flags are an incredibly handy way to keep track of a large amount of booleans using only a single int or uint. The most common uses of bit flags are:

Perhaps it's because you need to fiddle around with binary operations which make bit flags so under-used. To be honest, every time I want to use them, I end up looking up the syntax somewhere as I'm not arsed actually keeping it to mind. So I finally got around to encapsulating all the behaviour in a nice class:

package
{
	import flash.system.System;
	import flash.utils.describeType;
	import flash.utils.Dictionary;
	
	/**
	 * A class for using bit flags in an object
	 * @author Damian Connolly - https://divillysausages.com
	 */
	public class BitFlag 
	{
		
		/*************************************************************************************************************/
		
		private static const MAX_INT:int	= 1 << 30; // the max value we can have if using ints
		private static const MAX_UINT:uint 	= 1 << 31; // the max value we can have if using uints
		
		/*************************************************************************************************************/
		
		private static var m_cache:Dictionary = null; // our cache for flag classes
		
		/*************************************************************************************************************/
		
		/**
		 * Destroys the cache for the flag classes
		 */
		public static function destroyCache():void
		{
			// no cache, do nothing
			if ( BitFlag.m_cache == null )
				return;
				
			// go through and clear all our objects
			for ( var key:* in BitFlag.m_cache )
			{
				BitFlag.m_cache[key] = null;
				delete BitFlag.m_cache[key];
			}
			
			// kill our dictionary
			BitFlag.m_cache = null;
		}
		
		/*************************************************************************************************************/
		
		private var m_flagClass:Class	= null;	// the class that we use to verify our flags
		private var m_flags:uint 		= 0; // the flags that we have
		
		/*************************************************************************************************************/
		
		/**
		 * The class that we use to verify our flags. Setting this will clear any flags that we have
		 */
		public function get flagClass():Class { return this.m_flagClass; }
		public function set flagClass( c:Class ):void
		{
			// clear any flags that we have
			this.m_flags = 0;
			
			// set our class and read the flags from it
			this.m_flagClass = c;
			if ( this.m_flagClass != null )
				this._setupFlagClass();
		}
		
		/*************************************************************************************************************/
		
		/**
		 * Creates the bit flag object
		 * @param flagClass The class that we'll use for our constants if we want to check the flags passed
		 */
		public function BitFlag( flagClass:Class = null ) 
		{
			this.flagClass = flagClass;
		}
		
		/**
		 * Destroys the bit flag object and clears it for garbage collection
		 */
		public function destroy():void
		{
			this.flagClass = null;
		}
		
		/**
		 * Adds a flag to our object
		 * @param flag The flag that we want to add
		 */
		public function addFlag( flag:uint ):void
		{
			// clean our flags if needed
			this.m_flags |= ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag;
		}
		
		/**
		 * Adds a list of flags to our object
		 * @param flags The list of flags that we want to add
		 */
		public function addFlags( ... flags ):void
		{
			// Add all the flags (clean if needed)
			var len:int = flags.length;
			for ( var i:int = 0; i < len; i++ )
				this.m_flags |= ( this.m_flagClass != null ) ? this._cleanFlags( flags[i] ) : flags[i];
		}
		
		/**
		 * Removes a flag from our object
		 * @param flag The flag that we want to remove
		 */
		public function removeFlag( flag:uint ):void
		{
			// clean our flags if needed
			this.m_flags &= ( this.m_flagClass != null ) ? ~this._cleanFlags( flag ) : ~flag;
		}
		
		/**
		 * Removes a list of flags from our object
		 * @param The list of flags that we want to remove
		 */
		public function removeFlags( ... flags ):void
		{
			// remove all the flags (clean if needed)
			var len:int = flags.length;
			for ( var i:int = 0; i < len; i++ )
				this.m_flags &= ( this.m_flagClass != null ) ? ~this._cleanFlags( flags[i] ) : ~flags[i];
		}
		
		/**
		 * Toggles a specific flag. If the current flag is false, this will set
		 * it to true and vice versa
		 * @param flag The flag that we want to toggle
		 */
		public function toggleFlag( flag:uint ):void
		{
			// clean the flags if needed
			this.m_flags ^= ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag;
		}
		
		/**
		 * Toggles a list of flags on our object. If a flag is currently false, this
		 * will set it to true and vice versa
		 * @param flags The list of flags that we want to toggle
		 */
		public function toggleFlags( ... flags ):void
		{
			// toggle all the flags (clean if needed)
			var len:int = flags.length;
			for ( var i:int = 0; i < len; i++ )
				this.m_flags ^= ( this.m_flagClass != null ) ? this._cleanFlags( flags[i] ) : flags[i];
		}
		
		/**
		 * Checks if we have a specific flag set for this class. If the flag passed in is multiple
		 * flags (i.e. Flag1 | Flag2 | Flag3), then this will return true only if we have all the flags
		 * @param flag The flag that we want to check
		 * @return True if we have the flag, false otherwise
		 */
		public function hasFlag( flag:uint ):Boolean
		{
			// check if we have the flag (clean if needed)
			flag = ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag;
			return ( this.m_flags & flag ) == flag;
		}
		
		/**
		 * Checks if we have all the flags provided set
		 * @param flags The list of flags that we want to check
		 * @return True if all the flags are set, false otherwise
		 */
		public function hasFlags( ... flags ):Boolean
		{
			// concat up our flag to check
			var allFlags:int	= 0;
			var len:int			= flags.length;
			for ( var i:int = 0; i < len; i++ )
				allFlags |= flags[i];
				
			// clean the flags if needed
			if ( this.m_flagClass != null )
				allFlags = this._cleanFlags( allFlags );
				
			// now check if all of the flags are set
			return ( this.m_flags & allFlags ) == allFlags; // match all
		}
		
		/**
		 * Checks if we have a specific flag set for this class (or flags can be piped)
		 * @param flag The flag that we want to check
		 * @return True if we have any of the flag, false otherwise
		 */
		public function hasAnyFlag( flag:uint ):Boolean
		{
			// check if we have the flag (clean if needed)
			flag = ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag;
			return ( this.m_flags & flag ) != 0;
		}
		
		/**
		 * Checks if we have any of the flags provided set
		 * @param flags The list of flags that we want to check
		 * @return True if any the flags are set, false otherwise
		 */
		public function hasAnyFlags( ... flags ):Boolean
		{
			// concat up our flag to check
			var allFlags:int	= 0;
			var len:int 		= flags.length;
			for ( var i:int = 0; i < len; i++ )
				allFlags |= flags[i];
				
			// clean the flags if needed
			if ( this.m_flagClass != null )
				allFlags = this._cleanFlags( allFlags );
				
			// check if we have any of the flags
			return ( this.m_flags & allFlags ) != 0;
		}
		
		/**
		 * Returns a String representation of the object
		 */
		public function toString():String
		{
			return "[BitFlag flags:" + this.m_flags.toString( 2 ) + "]";
		}
		
		/*************************************************************************************************************/
		
		// cleans any flags passed in to make sure they come from our class
		private function _cleanFlags( flags:uint ):uint
		{
			// if we don't have a class, we're not verifying, so ignore
			if ( this.m_flagClass == null )
				return flags;
				
			// if we don't have our vector for some reason do nothing
			if ( BitFlag.m_cache == null || !( this.m_flagClass in BitFlag.m_cache ) )
				return flags;
				
			// get our vector
			var v:Vector.<uint> = BitFlag.m_cache[this.m_flagClass];
			
			// clean the flags
			var len:int 		= v.length;
			var retFlags:uint	= 0;
			for ( var i:int = 0; i < len; i++ )
			{
				// if a flag in our class exists in this flag, remove it
				if ( ( flags & v[i] ) != 0 )
				{
					retFlags 	|= v[i];
					flags 		&= ~v[i];
				}
			}
			
			// if we have something left over, then there was a problem
			if ( flags != 0 )
				trace( "3:[BitFlag] While cleaning the flags, we found an unknown flag (" + flags + ") that doesn't exist in our flag class (" + this.m_flagClass + ")" );
				
			// return the cleaned flags
			return retFlags;
		}
		
		// takes a class and extracts all the flags from it so we can check any flags we get
		private function _setupFlagClass():void
		{
			// make sure we have a class
			if ( this.m_flagClass == null )
				return;
				
			// if we already have it in our cache, ignore
			if ( BitFlag.m_cache != null && ( this.m_flagClass in BitFlag.m_cache ) )
				return;
				
			// it's not there, describe the class
			var x:XML = describeType( this.m_flagClass );
			
			// get all the constants and take out any int and uints
			for each ( var cx:XML in x.constant )
			{
				// only take ints and uints
				var type:String = cx.@type;
				if ( type != "uint" && type != "int" )
				{
					trace( "0:[BitFlag] Ignoring '" + cx.@name + "' from class " + this.m_flagClass + " as it's not an int or an uint" );
					continue;
				}
					
				// if it's an int, make sure it's good
				if ( type == "int" )
				{
					var intFlag:int = this.m_flagClass[cx.@name];
					if ( intFlag < 0 || intFlag > BitFlag.MAX_INT ) // less than 0, or we've done something like (1<<31)
					{
						trace( "0:[BitFlag] Ignoring const '" + cx.@name + "' from class " + this.m_flagClass + " as out of range. The max possible flag for an int is (1 << 30)" );
						continue;
					}
				}
					
				// get our uint (convert ints)
				var flag:uint = this.m_flagClass[cx.@name];
				
				// make sure only one bit is set (i.e. it's a flag and not a number)
				// this check only works on numbers less than (1 << 30), so do a check for MAX_UINT
				if ( flag != ( flag & -flag ) && flag != BitFlag.MAX_UINT )
				{
					trace( "0:[BitFlag] Ignoring const '" + cx.@name + "' from class " + this.m_flagClass + " as it's not a flag" );
					continue;
				}
				
				// create our dictionary if needed
				if ( BitFlag.m_cache == null )
					BitFlag.m_cache = new Dictionary;
					
				// create our vector for this class if needed
				if ( !( this.m_flagClass in BitFlag.m_cache ) )
					BitFlag.m_cache[this.m_flagClass] = new Vector.<uint>;
					
				// add our const
				BitFlag.m_cache[this.m_flagClass].push( flag );
			}
			
			// dispose of the xml immediately
			System.disposeXML( x );
		}
		
	}

}

Examples

Using the class is pretty easy. First you need some flags though, with this in mind, I'll be using this Flags class for the examples:

internal class Flags
{
	public static const NONE:uint 	= 1 << 0;
	public static const ONE:uint 	= 1 << 1;
	public static const TWO:uint 	= 1 << 2;
	public static const THREE:uint 	= 1 << 3;
	public static const FOUR:uint 	= 1 << 4;
	
	public static const THIRTY_ONE:uint = 1 << 31;	// should be good (max uint)
	
	public static const FIVE:int 			= 1 << 5;	// should be good (using int)
	public static const THIRTY_INT:int 		= 1 << 30;	// should be good (max int)
	public static const THIRTY_ONE_INT:int	= 1 << 31;	// out of range for an int
	
	public static const RANDOM:uint = 348327932; // ignored as not a flag
	
	public static const SOME_OTHER_CONST:String = "blah"; // ignored as not an int or an uint
}

If you've never seen bit flags before, what we doing is bit shifting the value 1 left by x number of spaces. So (1 << 1) will move 1 left one space. When we look at the flag in binary, it's 10. Likewise (1 << 2) is 100, (1 << 3) is 1000 etc. This is how we're able to store multiple booleans on just one uint. For example, to store ONE ((1 << 1)) and THREE ((1 << 3)) on an uint would mean that it would look like 1010 in binary.

For the simple examples, just take the consts ONE to FIVE. The rest are there just for test purposes.

One point to note is that when using flags, if you're using ints, then you can have, at max, 31 flags (or up to (1 << 30)), as after this, we go into negative numbers. If you're using uints, then you get an extra one (or (1 << 31)) as int uses that final bit to store whether it's positive or not.

Adding flags

To add a flag to a BitFlag object, just call:

var flag:BitFlag = new BitFlag;
flag.addFlag( Flags.ONE );

If you want to add multiple flags at once, you can concat the flag yourself using:

flag.addFlag( Flags.ONE | Flags.TWO | Flags.THREE );

or using the magic of ActionScript:

flag.addFlags( Flags.ONE, Flags.TWO, Flags.THREE );

Removing flags

Removing flags follows the same logic:

flag.removeFlag( Flags.ONE );

or to remove multiple flags at once, you can:

flag.removeFlag( Flags.ONE | Flags.TWO | Flags.THREE );

or:

flag.removeFlags( Flags.ONE, Flags.TWO, Flags.THREE );

Toggling flags

Toggling flags following the same logic using the functions toggleFlag() and toggleFlags().

Checking flags

To check if a BitFlag has a flag set, you can use the functions hasFlag(), hasFlags() and hasAnyFlag(). hasFlag() acts as you'd expect it to, returning true if the BitFlag has the specific flag (i.e. it's set to 1). hasFlags() will only return true if all the flags passed are set, while hasAnyFlag() will return true if any of the flags passed are set.

Checking

As an added bonus, I added a feature where you can pass the class that you're using for your flags to the BitFlag when you create it. It'll use this class to verify all the flags that are added, removed, toggled etc. to make sure they're good. In the above example, if we want to enable checking, we create the BitFlag like this:

var flag:BitFlag = new BitFlag( Flags );

The results are cached, so you only take the performance hit the first time a flag class is used. If you want to destroy the cache, you can call BitFlag.destroyCache().

Only static const ints and static const uints are taken as flags, and they're verified, so you'll see any errors - with the exception of if you use a flag that's (1 << 32) or higher, as in this case the flags loop around so they're still valid - I can't see that they're too high. To make it clear, ( 1 << 0 ) == ( 1 << 32 ), ( 1 << 1 ) == ( 1 << 33 ) etc.

Code

You can grab the code at the link under the post, or by clicking here.

Updates

(26/04/12):
I updated the class as the hasFlag() wasn't working correctly if you used piped flags (i.e. Flags.ONE | Flags.TWO). If your BitFlag had one of these flags set, it would return true. Now it only returns true if your class has all of the flags. To test for any flag, use the new hasAnyFlag() and hasAnyFlags functions. I also added a getter/setter for the flagClass property so if you need to, you can reuse your BitFlag object.

Comments

Submit a comment

* indicates required