package aerys.minko.scene.controller
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	/**
	 * EnterFrameController are controllers triggered whenever the Scene.enterFrame
	 * signal is executed.
	 *
	 * The best way to
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class EnterFrameController extends AbstractController
	{
		private var _numTargetsInScene	: Dictionary;

        protected function getNumTargetsInScene(scene : Scene) : uint
        {
            return _numTargetsInScene[scene];
        }

		public function EnterFrameController(targetType	: Class = null)
		{
			super(targetType);

            initialize();
		}

        private function initialize() : void
        {
            _numTargetsInScene = new Dictionary(true);

            targetAdded.add(targetAddedHandler);
            targetRemoved.add(targetRemovedHandler);
        }

		protected function targetAddedHandler(ctrl		: EnterFrameController,
											  target	: ISceneNode) : void
		{
            if (target.scene)
                targetAddedToScene(target, target.scene);

            target.added.add(addedHandler);
            target.removed.add(removedHandler);
		}

		protected function targetRemovedHandler(ctrl	: EnterFrameController,
												target	: ISceneNode) : void
		{
            if (target.scene)
                targetRemovedFromScene(target, target.scene);

            target.added.remove(addedHandler);
            target.removed.remove(removedHandler);
		}

		private function addedHandler(target	: ISceneNode,
									  ancestor	: Group) : void
		{
			var scene : Scene = target.scene;

			if (!scene)
				return ;

			targetAddedToScene(target, scene);
		}

		protected function targetAddedToScene(target : ISceneNode, scene : Scene) : void
		{
            if (!_numTargetsInScene[scene])
            {
                _numTargetsInScene[scene] = 1;
                scene.enterFrame.add(sceneEnterFrameHandler);
            }
            else
                _numTargetsInScene[scene]++;
		}

		private function removedHandler(target		: ISceneNode,
										ancestor	: Group) : void
		{
			var scene : Scene = ancestor.scene;

			if (!scene)
				return ;

			targetRemovedFromScene(target, scene);
		}

		protected function targetRemovedFromScene(target : ISceneNode, scene : Scene) : void
		{
            _numTargetsInScene[scene]--;
            if (_numTargetsInScene[scene] == 0)
            {
                delete _numTargetsInScene[scene];
                scene.enterFrame.remove(sceneEnterFrameHandler);
            }
		}

		protected function sceneEnterFrameHandler(scene			: Scene,
												  viewport		: Viewport,
												  destination	: BitmapData,
												  time			: Number) : void
		{
			throw new Error(
				'The method EnterFrameController.sceneEnterFrameHandler must be overriden.'
			);
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;

	/**
	 * EnterFrameController are controllers triggered whenever the Scene.enterFrame
	 * signal is executed.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class EnterFrameController extends AbstractController
	{
		private var _numTargetsInScene	: Dictionary;

        protected function getNumTargetsInScene(scene : Scene) : uint
        {
            return _numTargetsInScene[scene];
        }

		public function EnterFrameController(targetType	: Class = null)
		{
			super(targetType);

            initialize();
		}

        private function initialize() : void
        {
            _numTargetsInScene = new Dictionary(true);

            targetAdded.add(targetAddedHandler);
            targetRemoved.add(targetRemovedHandler);
        }

		protected function targetAddedHandler(ctrl		: EnterFrameController,
											  target	: ISceneNode) : void
		{
            if (target.scene)
                targetAddedToScene(target, target.scene);

            target.added.add(addedHandler);
            target.removed.add(removedHandler);
		}

		protected function targetRemovedHandler(ctrl	: EnterFrameController,
												target	: ISceneNode) : void
		{
            if (target.scene)
                targetRemovedFromScene(target, target.scene);

            target.added.remove(addedHandler);
            target.removed.remove(removedHandler);
		}

		private function addedHandler(target	: ISceneNode,
									  ancestor	: Group) : void
		{
			var scene : Scene = target.scene;

			if (!scene)
				return ;

			targetAddedToScene(target, scene);
		}

		protected function targetAddedToScene(target : ISceneNode, scene : Scene) : void
		{
            if (!_numTargetsInScene[scene])
            {
                _numTargetsInScene[scene] = 1;
                scene.enterFrame.add(sceneEnterFrameHandler);
            }
            else
                _numTargetsInScene[scene]++;
		}

		private function removedHandler(target		: ISceneNode,
										ancestor	: Group) : void
		{
			var scene : Scene = ancestor.scene;

			if (!scene)
				return ;

			targetRemovedFromScene(target, scene);
		}

		protected function targetRemovedFromScene(target : ISceneNode, scene : Scene) : void
		{
            _numTargetsInScene[scene]--;
            if (_numTargetsInScene[scene] == 0)
            {
                delete _numTargetsInScene[scene];
                scene.enterFrame.remove(sceneEnterFrameHandler);
            }
		}

		protected function sceneEnterFrameHandler(scene			: Scene,
												  viewport		: Viewport,
												  destination	: BitmapData,
												  time			: Number) : void
		{
			throw new Error(
				'The method EnterFrameController.sceneEnterFrameHandler must be overriden.'
			);
		}
	}
}

fix typo

fix typo


package aerys.minko.scene
{
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.binding.DataBindings;

	import avmplus.getQualifiedClassName;

	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.describeType;
	import flash.utils.flash_proxy;
	import flash.utils.getQualifiedClassName;

	import mx.messaging.channels.StreamingAMFChannel;

	public dynamic class SceneIterator extends Proxy
	{
		private static const TYPE_CACHE	: Dictionary		= new Dictionary(true);
		private static const OPERATORS	: Vector.<String>	= new <String>[
			'//', '/', '[', ']', '..', '.', '~=', '?=', '=', '@', '*', '(', ')',
			'>=', '>', '<=', '<', '==', '='
		];
		private static const REGEX_TRIM	: RegExp			= /^\s+|\s+$/;

		private var _path		: String				= null;
		private var _selection	: Vector.<ISceneNode>	= null;
		private var _modifier	: String				= null;

		public function get length() : uint
		{
			return _selection.length;
		}

		public function SceneIterator(path 		: String,
									  selection : Vector.<ISceneNode>,
									  modifier	: String	= "")
		{
			super();

			_modifier = modifier;

			initialize(path, selection);
		}

		public function toString() : String
		{
			return _selection.toString();
		}

//		override flash_proxy function setProperty(name : *, value : *):void
//		{
//			var propertyName : String = name;
//
//			for each (var node : ISceneNode in _selection)
//				getValueObject(node, _modifier)[propertyName] = value;
//		}

		override flash_proxy function getProperty(name : *) : *
		{
			var index : int = parseInt(name);

			if (index == name)
				return index < _selection.length ? _selection[index] : null;
			else
			{
				throw new Error(
					'Unable to get a property on a set of objects. '
					+ 'You must use the [] operator to fetch one of the objects.'
				);
			}
		}

		override flash_proxy function nextNameIndex(index : int) : int
		{
			return index < _selection.length ? index + 1 : 0;
		}

		override flash_proxy function nextName(index : int) : String
		{
			return String(index - 1);
		}

		override flash_proxy function nextValue(index : int) : *
		{
			return _selection[int(index - 1)];
		}

//		override flash_proxy function callProperty(name:*, ...parameters):*
//		{
//			var methodName : String = name;
//
//			for each (var node : ISceneNode in _selection)
//			{
//				var method : Function = getValueObject(node, _modifier)[methodName];
//
//				method.apply(null, parameters);
//			}
//
//			return this;
//		}

		private function initialize(path : String, selection : Vector.<ISceneNode>) : void
		{
			_path = path;

			// update root
			var token : String = getToken();

			_selection = selection.slice();
			if (token == "/")
			{
				selectRoots();
				nextToken(token);
			}

			// parse
			while (token = getToken())
			{
				switch (token)
				{
					case '//' :
						nextToken(token);
						selectDescendants();
						break ;
					case '/' :
						nextToken(token);
						selectChildren();
						break ;
					default :
						nextToken(token);
						parseNodeType(token);
						break ;
				}
			}
		}

		private function getToken(doNext : Boolean = false) : String
		{
			var token	: String	= null;

			if (!_path)
				return null;

			_path = _path.replace(/^\s+/, '');

			var nextOpIndex : int = int.MAX_VALUE;

			for each (var op : String in OPERATORS)
			{
				var opIndex : int = _path.indexOf(op);

				if (opIndex > 0 && opIndex < nextOpIndex)
					nextOpIndex = opIndex;

				if (opIndex == 0)
				{
					token = op;
					break ;
				}
			}

			if (!token)
				token = _path.substring(0, nextOpIndex);

			if (doNext)
				nextToken(token);

			return token;
		}

		private function getValueToken() : Object
		{
			var value : Object	= null;

			_path = _path.replace(/^\s+/, '');

			if (_path.charAt(0) == "'")
			{
				var endOfStringIndex : int = _path.indexOf("'", 1);

				if (endOfStringIndex < 0)
					throw new Error("Unterminated string expression.");

				var stringValue	: String	=  _path.substring(1, endOfStringIndex);

				_path = _path.substring(endOfStringIndex + 1);

				value = stringValue;
			}
			else
			{
				var token : String	= getToken(true);

				if (token == 'true')
					value = true;
				else if (token == 'false')
					value = false;
				else if (token.indexOf('0x') == 0)
					value = parseInt(token, 16);
			}

			return value;
		}

		private function nextToken(token : String) : void
		{
			_path = _path.substring(_path.indexOf(token) + token.length);
		}

		private function selectChildren(typeName : String = null) : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			if (typeName != null)
				typeName = typeName.toLowerCase();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node is Group)
				{
					var group 		: Group = node as Group;
					var numChildren : uint 	= group.numChildren;

					for (var i : uint = 0; i < numChildren; ++i)
					{
						var child 		: ISceneNode 	= group.getChildAt(i);
						var className	: String		= getQualifiedClassName(child)
						var childType 	: String 		= className.substr(className.lastIndexOf(':') + 1);

						if (typeName == null || childType.toLowerCase() == typeName)
							_selection.push(child);
					}
				}
			}
		}

		private function selectRoots() : void
		{
			var selection	: Vector.<ISceneNode>	= _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
				if (_selection.indexOf(node.root) < 0)
					_selection.push(node);
		}

		private function selectDescendants() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				_selection.push(node);
				if (node is Group)
					(node as Group).getDescendantsByType(ISceneNode, _selection);
			}
		}

		private function selectParents() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node.parent)
					_selection.push(node.parent);
				else
					_selection.push(node);
			}
		}

		private function parseNodeType(nodeType : String) : void
		{
			if (nodeType == '.')
			{
				// nothing
			}
			if (nodeType == '..')
				selectParents();
			else if (nodeType == '*')
				selectChildren();
			else
				selectChildren(nodeType);

			// apply predicates
			var token : String = getToken();

			while (token == '[')
			{
				nextToken(token);
				parsePredicate();

				token = getToken();
			}
		}

		private function parsePredicate() : void
		{
			var propertyName	: String	= getToken(true);
			var isBinding		: Boolean	= propertyName == '@';

			if (isBinding)
				propertyName = getToken(true);

			var index	: int	= parseInt(propertyName);

			if (propertyName == 'hasController')
				filterOnController();
			if (propertyName == 'hasProperty')
				filterOnProperty();
			else if (propertyName == 'position')
				filterOnPosition();
			else if (propertyName == 'last')
				filterLast();
			else if (propertyName == 'first')
				filterFirst();
			else if (index.toString() == propertyName)
			{
				if (index < _selection.length)
				{
					_selection[0] = _selection[index];
					_selection.length = 1;
				}
				else
					_selection.length = 0;
			}
			else
				filterOnValue(propertyName, isBinding);

			checkNextToken(']');
		}

		private function filterLast() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			_selection[0] = _selection[uint(_selection.length - 1)];
			_selection.length = 1;
		}

		private function filterFirst() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			_selection.length = 1;
		}

		private function filterOnValue(propertyName : String, isBinding : Boolean = false) : void
		{
			var operator	: String	= getToken(true);
			var chunks		: Array		= [propertyName];

			while (operator == '.')
			{
				chunks.push(getToken(true));
				operator = getToken(true);
			}

			var value		: Object	= getValueToken();
			var numNodes	: uint		= _selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node		: ISceneNode	= _selection[i];
				var nodeValue 	: Object 		= null;

				if (isBinding && (node['bindings'] is DataBindings))
					nodeValue = (node['bindings'] as DataBindings).getProperty(propertyName);
				else
				{
					try
					{
						nodeValue = getValueObject(node, chunks);
						if (!compare(operator, nodeValue, value))
							removeFromSelection(i);
					}
					catch (e : Error)
					{
						removeFromSelection(i);
					}
				}

			}
		}

		private function compare(operator : String, a : Object, b : Object) : Boolean
		{
			switch (operator)
			{
				case '>' :
					return a > b;
				case '>=' :
					return a >= b;
				case '<' :
					return a >= b;
				case '<=' :
					return a <= b;
				case '=' :
				case '==' :
					return a == b;
				case '~=' :
					var matches	: Array	= String(a).match(b);

					return matches && matches.length != 0;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnController() : Object
		{
			checkNextToken('(');
			var controllerName : String = getToken(true);
			checkNextToken(')');

			var numNodes	: uint	= _selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node			: ISceneNode	= _selection[i];
				var numControllers	: uint			= node.numControllers;
				var keepSceneNode	: Boolean		= false;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controllerType : String = getQualifiedClassName(node.getController(controllerId));
					controllerType = controllerType.substr(controllerType.lastIndexOf(':') + 1);

					if (controllerType == controllerName)
					{
						keepSceneNode = true;
						break;
					}
				}

				if (!keepSceneNode)
					removeFromSelection(i);
			}

			return null;
		}

		private function filterOnPosition() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			var operator 	: String 	= getToken(true);
			var value 		: uint 		= uint(parseInt(getToken(true)));

			switch (operator)
			{
				case '>':
					++value;
				case '>=':
					_selection = _selection.slice(value);
					break;
				case '<':
					--value;
				case '<=':
					_selection = _selection.slice(0, value);
					break;
				case '=':
				case '==':
					_selection[0] = _selection[value];
					_selection.length = 1;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnProperty() : void
		{
			checkNextToken('(');

			var chunks		: Array 	= [getToken(true)];
			var operator	: String 	= getToken(true);

			while (operator == '.')
			{
				chunks.push(operator);
				operator = getToken(true);
			}

			if (operator != ')')
				throwParseError(')', operator);

			var numNodes : uint	= _selection.length;
			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				try
				{
					getValueObject(_selection[i], chunks);
				}
				catch (e : Error)
				{
					removeFromSelection(i);
				}
			}
		}

		private function getValueObject(source : Object, chunks : Array) : Object
		{
			if (chunks)
				for each (var chunk : String in chunks)
					source = source[chunk];

			return source;
		}

		private function removeFromSelection(index : uint) : void
		{
			var numNodes : uint = _selection.length - 1;

			_selection[index] = _selection[numNodes];
			_selection.length = numNodes;
		}

		private function checkNextToken(expected : String) : void
		{
			var token : String = getToken(true);

			if (token != expected)
				throwParseError(expected, token);
		}

		private function throwParseError(expected	: String,
										 got		: String) : void
		{
			throw new Error(
				'Parse error: expected \'' + expected + '\', got \'' + got + '\'.'
			);
		}
	}
}

package aerys.minko.scene
{
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.binding.DataBindings;

	import avmplus.getQualifiedClassName;

	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.describeType;
	import flash.utils.flash_proxy;
	import flash.utils.getQualifiedClassName;

	public dynamic class SceneIterator extends Proxy
	{
		private static const TYPE_CACHE	: Dictionary		= new Dictionary(true);
		private static const OPERATORS	: Vector.<String>	= new <String>[
			'//', '/', '[', ']', '..', '.', '~=', '?=', '=', '@', '*', '(', ')',
			'>=', '>', '<=', '<', '==', '='
		];
		private static const REGEX_TRIM	: RegExp			= /^\s+|\s+$/;

		private var _path		: String				= null;
		private var _selection	: Vector.<ISceneNode>	= null;
		private var _modifier	: String				= null;

		public function get length() : uint
		{
			return _selection.length;
		}

		public function SceneIterator(path 		: String,
									  selection : Vector.<ISceneNode>,
									  modifier	: String	= "")
		{
			super();

			_modifier = modifier;

			initialize(path, selection);
		}

		public function toString() : String
		{
			return _selection.toString();
		}

//		override flash_proxy function setProperty(name : *, value : *):void
//		{
//			var propertyName : String = name;
//
//			for each (var node : ISceneNode in _selection)
//				getValueObject(node, _modifier)[propertyName] = value;
//		}

		override flash_proxy function getProperty(name : *) : *
		{
			var index : int = parseInt(name);

			if (index == name)
				return index < _selection.length ? _selection[index] : null;
			else
			{
				throw new Error(
					'Unable to get a property on a set of objects. '
					+ 'You must use the [] operator to fetch one of the objects.'
				);
			}
		}

		override flash_proxy function nextNameIndex(index : int) : int
		{
			return index < _selection.length ? index + 1 : 0;
		}

		override flash_proxy function nextName(index : int) : String
		{
			return String(index - 1);
		}

		override flash_proxy function nextValue(index : int) : *
		{
			return _selection[int(index - 1)];
		}

//		override flash_proxy function callProperty(name:*, ...parameters):*
//		{
//			var methodName : String = name;
//
//			for each (var node : ISceneNode in _selection)
//			{
//				var method : Function = getValueObject(node, _modifier)[methodName];
//
//				method.apply(null, parameters);
//			}
//
//			return this;
//		}

		private function initialize(path : String, selection : Vector.<ISceneNode>) : void
		{
			_path = path;

			// update root
			var token : String = getToken();

			_selection = selection.slice();
			if (token == "/")
			{
				selectRoots();
				nextToken(token);
			}

			// parse
			while (token = getToken())
			{
				switch (token)
				{
					case '//' :
						nextToken(token);
						selectDescendants();
						break ;
					case '/' :
						nextToken(token);
						selectChildren();
						break ;
					default :
						nextToken(token);
						parseNodeType(token);
						break ;
				}
			}
		}

		private function getToken(doNext : Boolean = false) : String
		{
			var token	: String	= null;

			if (!_path)
				return null;

			_path = _path.replace(/^\s+/, '');

			var nextOpIndex : int = int.MAX_VALUE;

			for each (var op : String in OPERATORS)
			{
				var opIndex : int = _path.indexOf(op);

				if (opIndex > 0 && opIndex < nextOpIndex)
					nextOpIndex = opIndex;

				if (opIndex == 0)
				{
					token = op;
					break ;
				}
			}

			if (!token)
				token = _path.substring(0, nextOpIndex);

			if (doNext)
				nextToken(token);

			return token;
		}

		private function getValueToken() : Object
		{
			var value : Object	= null;

			_path = _path.replace(/^\s+/, '');

			if (_path.charAt(0) == "'")
			{
				var endOfStringIndex : int = _path.indexOf("'", 1);

				if (endOfStringIndex < 0)
					throw new Error("Unterminated string expression.");

				var stringValue	: String	=  _path.substring(1, endOfStringIndex);

				_path = _path.substring(endOfStringIndex + 1);

				value = stringValue;
			}
			else
			{
				var token : String	= getToken(true);

				if (token == 'true')
					value = true;
				else if (token == 'false')
					value = false;
				else if (token.indexOf('0x') == 0)
					value = parseInt(token, 16);
			}

			return value;
		}

		private function nextToken(token : String) : void
		{
			_path = _path.substring(_path.indexOf(token) + token.length);
		}

		private function selectChildren(typeName : String = null) : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			if (typeName != null)
				typeName = typeName.toLowerCase();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node is Group)
				{
					var group 		: Group = node as Group;
					var numChildren : uint 	= group.numChildren;

					for (var i : uint = 0; i < numChildren; ++i)
					{
						var child 		: ISceneNode 	= group.getChildAt(i);
						var className	: String		= getQualifiedClassName(child)
						var childType 	: String 		= className.substr(className.lastIndexOf(':') + 1);

						if (typeName == null || childType.toLowerCase() == typeName)
							_selection.push(child);
					}
				}
			}
		}

		private function selectRoots() : void
		{
			var selection	: Vector.<ISceneNode>	= _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
				if (_selection.indexOf(node.root) < 0)
					_selection.push(node);
		}

		private function selectDescendants() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				_selection.push(node);
				if (node is Group)
					(node as Group).getDescendantsByType(ISceneNode, _selection);
			}
		}

		private function selectParents() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node.parent)
					_selection.push(node.parent);
				else
					_selection.push(node);
			}
		}

		private function parseNodeType(nodeType : String) : void
		{
			if (nodeType == '.')
			{
				// nothing
			}
			if (nodeType == '..')
				selectParents();
			else if (nodeType == '*')
				selectChildren();
			else
				selectChildren(nodeType);

			// apply predicates
			var token : String = getToken();

			while (token == '[')
			{
				nextToken(token);
				parsePredicate();

				token = getToken();
			}
		}

		private function parsePredicate() : void
		{
			var propertyName	: String	= getToken(true);
			var isBinding		: Boolean	= propertyName == '@';

			if (isBinding)
				propertyName = getToken(true);

			var index	: int	= parseInt(propertyName);

			if (propertyName == 'hasController')
				filterOnController();
			if (propertyName == 'hasProperty')
				filterOnProperty();
			else if (propertyName == 'position')
				filterOnPosition();
			else if (propertyName == 'last')
				filterLast();
			else if (propertyName == 'first')
				filterFirst();
			else if (index.toString() == propertyName)
			{
				if (index < _selection.length)
				{
					_selection[0] = _selection[index];
					_selection.length = 1;
				}
				else
					_selection.length = 0;
			}
			else
				filterOnValue(propertyName, isBinding);

			checkNextToken(']');
		}

		private function filterLast() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			_selection[0] = _selection[uint(_selection.length - 1)];
			_selection.length = 1;
		}

		private function filterFirst() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			_selection.length = 1;
		}

		private function filterOnValue(propertyName : String, isBinding : Boolean = false) : void
		{
			var operator	: String	= getToken(true);
			var chunks		: Array		= [propertyName];

			while (operator == '.')
			{
				chunks.push(getToken(true));
				operator = getToken(true);
			}

			var value		: Object	= getValueToken();
			var numNodes	: uint		= _selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node		: ISceneNode	= _selection[i];
				var nodeValue 	: Object 		= null;

				if (isBinding && (node['bindings'] is DataBindings))
					nodeValue = (node['bindings'] as DataBindings).getProperty(propertyName);
				else
				{
					try
					{
						nodeValue = getValueObject(node, chunks);
						if (!compare(operator, nodeValue, value))
							removeFromSelection(i);
					}
					catch (e : Error)
					{
						removeFromSelection(i);
					}
				}

			}
		}

		private function compare(operator : String, a : Object, b : Object) : Boolean
		{
			switch (operator)
			{
				case '>' :
					return a > b;
				case '>=' :
					return a >= b;
				case '<' :
					return a >= b;
				case '<=' :
					return a <= b;
				case '=' :
				case '==' :
					return a == b;
				case '~=' :
					var matches	: Array	= String(a).match(b);

					return matches && matches.length != 0;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnController() : Object
		{
			checkNextToken('(');
			var controllerName : String = getToken(true);
			checkNextToken(')');

			var numNodes	: uint	= _selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node			: ISceneNode	= _selection[i];
				var numControllers	: uint			= node.numControllers;
				var keepSceneNode	: Boolean		= false;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controllerType : String = getQualifiedClassName(node.getController(controllerId));
					controllerType = controllerType.substr(controllerType.lastIndexOf(':') + 1);

					if (controllerType == controllerName)
					{
						keepSceneNode = true;
						break;
					}
				}

				if (!keepSceneNode)
					removeFromSelection(i);
			}

			return null;
		}

		private function filterOnPosition() : void
		{
			checkNextToken('(');
			checkNextToken(')');

			var operator 	: String 	= getToken(true);
			var value 		: uint 		= uint(parseInt(getToken(true)));

			switch (operator)
			{
				case '>':
					++value;
				case '>=':
					_selection = _selection.slice(value);
					break;
				case '<':
					--value;
				case '<=':
					_selection = _selection.slice(0, value);
					break;
				case '=':
				case '==':
					_selection[0] = _selection[value];
					_selection.length = 1;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnProperty() : void
		{
			checkNextToken('(');

			var chunks		: Array 	= [getToken(true)];
			var operator	: String 	= getToken(true);

			while (operator == '.')
			{
				chunks.push(operator);
				operator = getToken(true);
			}

			if (operator != ')')
				throwParseError(')', operator);

			var numNodes : uint	= _selection.length;
			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				try
				{
					getValueObject(_selection[i], chunks);
				}
				catch (e : Error)
				{
					removeFromSelection(i);
				}
			}
		}

		private function getValueObject(source : Object, chunks : Array) : Object
		{
			if (chunks)
				for each (var chunk : String in chunks)
					source = source[chunk];

			return source;
		}

		private function removeFromSelection(index : uint) : void
		{
			var numNodes : uint = _selection.length - 1;

			_selection[index] = _selection[numNodes];
			_selection.length = numNodes;
		}

		private function checkNextToken(expected : String) : void
		{
			var token : String = getToken(true);

			if (token != expected)
				throwParseError(expected, token);
		}

		private function throwParseError(expected	: String,
										 got		: String) : void
		{
			throw new Error(
				'Parse error: expected \'' + expected + '\', got \'' + got + '\'.'
			);
		}
	}
}

Update src/aerys/minko/scene/SceneIterator.as

Fixes issue #52


///////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///////////////////////////////////////////////////////////////////////////
package widgets.Geoprocessing.parameters
{

import com.esri.ags.tasks.supportClasses.DataFile;

public class DataFileParameter extends BaseParameter
{
    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------

    private static const URL_DELIMITER:String = "url:";

    private static const VALID_URL_REGEX:RegExp = /^(ht|f)tps?:\/\/[^\s\.]+(\.[^\s\.]+)*((\/|\.)[^\s\.]+)+\/?$/;

    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  defaultValue
    //----------------------------------

    private var _defaultValue:DataFile;

    override public function get defaultValue():Object
    {
        return _defaultValue;
    }

    override public function set defaultValue(value:Object):void
    {
        _defaultValue = new DataFile(value.url);
    }

    //----------------------------------
    //  type
    //----------------------------------

    override public function get type():String
    {
        return GPParameterTypes.DATA_FILE;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

    override public function defaultValueFromString(description:String):void
    {
        var url:String = description.substr(URL_DELIMITER.length, description.length);
        _defaultValue = new DataFile(url);
    }

    override public function hasValidValue():Boolean
    {
        if (_defaultValue.url)
        {
            var validURLIndex:int = _defaultValue.url.search(VALID_URL_REGEX);
            return validURLIndex == 0;
        }
        else
        {
            return false;
        }
    }
}

}

///////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///////////////////////////////////////////////////////////////////////////
package widgets.Geoprocessing.parameters
{

import com.esri.ags.tasks.supportClasses.DataFile;

public class DataFileParameter extends BaseParameter
{
    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------

    private static const URL_DELIMITER:String = "url:";
    private static const ITEM_ID_DELIMITER:String = "itemID:";

    private static const VALID_URL_REGEX:RegExp = /^(ht|f)tps?:\/\/[^\s\.]+(\.[^\s\.]+)*((\/|\.)[^\s\.]+)+\/?$/;

    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  defaultValue
    //----------------------------------

    private var _defaultValue:DataFile;

    override public function get defaultValue():Object
    {
        return _defaultValue;
    }

    override public function set defaultValue(value:Object):void
    {
        _defaultValue = new DataFile(value.url, value.itemID);
    }

    //----------------------------------
    //  type
    //----------------------------------

    override public function get type():String
    {
        return GPParameterTypes.DATA_FILE;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

    override public function defaultValueFromString(description:String):void
    {
        var dataFile:DataFile = new DataFile();

        if (description.indexOf(URL_DELIMITER) == 0)
        {
            dataFile.url = description.substr(URL_DELIMITER.length);
        }
        else if (description.indexOf(ITEM_ID_DELIMITER) == 0)
        {
            dataFile.itemID = description.substr(ITEM_ID_DELIMITER.length);
        }

        _defaultValue = dataFile;
    }

    override public function hasValidValue():Boolean
    {
        if (_defaultValue.itemID)
        {
            return true;
        }
        else if (_defaultValue.url)
        {
            var validURLIndex:int = _defaultValue.url.search(VALID_URL_REGEX);
            return validURLIndex == 0;
        }
        else
        {
            return false;
        }
    }
}

}

Update DataFileParameter to handle item ID.


package aerys.minko.render.material.basic
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.material.Material;
	import aerys.minko.render.resource.texture.TextureResource;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Matrix4x4;

	public class BasicMaterial extends Material
	{
		public static const DEFAULT_NAME			: String		= 'BasicMaterial';
		public static const DEFAULT_BASIC_SHADER	: BasicShader	= new BasicShader();
		public static const DEFAULT_EFFECT			: Effect		= new Effect(DEFAULT_BASIC_SHADER);

		public function get blending() : uint
		{
			return getProperty(BasicProperties.BLENDING);
		}
		public function set blending(value : uint) : void
		{
			setProperty(BasicProperties.BLENDING, value);
		}

		public function get triangleCulling() : uint
		{
			return getProperty(BasicProperties.TRIANGLE_CULLING);
		}
		public function set triangleCulling(value : uint) : void
		{
			setProperty(BasicProperties.TRIANGLE_CULLING, value);
		}

		public function get diffuseColor() : uint
		{
			return getProperty(BasicProperties.DIFFUSE_COLOR);
		}
		public function set diffuseColor(value : uint) : void
		{
			setProperty(BasicProperties.DIFFUSE_COLOR, value);
		}

		public function get diffuseMap() : TextureResource
		{
			return getProperty(BasicProperties.DIFFUSE_MAP);
		}
		public function set diffuseMap(value : TextureResource) : void
		{
			setProperty(BasicProperties.DIFFUSE_MAP, value);
		}

		public function get alphaThreshold() : Number
		{
			return getProperty(BasicProperties.ALPHA_THRESHOLD);
		}
		public function set alphaThreshold(value : Number) : void
		{
			setProperty(BasicProperties.ALPHA_THRESHOLD, value);
		}

		public function get depthTest() : uint
		{
			return getProperty(BasicProperties.DEPTH_TEST);
		}
		public function set depthTest(value : uint) : void
		{
			setProperty(BasicProperties.DEPTH_TEST, value);
		}

		public function get depthWriteEnabled() : Boolean
		{
			return getProperty(BasicProperties.DEPTH_WRITE_ENABLED);
		}
		public function set depthWriteEnabled(value : Boolean) : void
		{
			setProperty(BasicProperties.DEPTH_WRITE_ENABLED, value);
		}

		public function get stencilTriangleFace() : uint
		{
			return getProperty(BasicProperties.STENCIL_TRIANGLE_FACE);
		}
		public function set stencilTriangleFace(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_TRIANGLE_FACE, value);
		}

		public function get stencilCompareMode() : uint
		{
			return getProperty(BasicProperties.STENCIL_COMPARE_MODE);
		}
		public function set stencilCompareMode(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_COMPARE_MODE, value);
		}

		public function get stencilActionBothPass() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_BOTH_PASS);
		}
		public function set stencilActionBothPass(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_BOTH_PASS, value);
		}

		public function get stencilActionDepthFail() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_DEPTH_FAIL);
		}
		public function set stencilActionDepthFail(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_DEPTH_FAIL, value);
		}

		public function get stencilActionDepthPassStencilFail() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL);
		}
		public function set stencilActionDepthPassStencilFail(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL, value);
		}

		public function get stencilReferenceValue() : Number
		{
			return getProperty(BasicProperties.STENCIL_REFERENCE_VALUE);
		}
		public function set stencilReferenceValue(value : Number) : void
		{
			setProperty(BasicProperties.STENCIL_REFERENCE_VALUE, value);
		}

		public function get stencilReadMask() : uint
		{
			return getProperty(BasicProperties.STENCIL_READ_MASK);
		}
		public function set stencilReadMask(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_READ_MASK, value);
		}

		public function get stencilWriteMask() : uint
		{
			return getProperty(BasicProperties.STENCIL_WRITE_MASK);
		}
		public function set stencilWriteMask(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_WRITE_MASK, value);
		}

		public function get diffuseTransform() : Matrix4x4
		{
			return getProperty(BasicProperties.DIFFUSE_TRANSFORM);
		}
		public function set diffuseTransform(value : Matrix4x4) : void
		{
			setProperty(BasicProperties.DIFFUSE_TRANSFORM, value);
		}

		public function BasicMaterial(properties : Object = null, effect : Effect = null, name : String = DEFAULT_NAME)
		{
			super(effect || DEFAULT_EFFECT, properties, name);
		}

		override public function clone() : IDataProvider
		{
			var mat : BasicMaterial	= new BasicMaterial(this, effect, name);

			return mat;
		}
	}
}

package aerys.minko.render.material.basic
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.material.Material;
	import aerys.minko.render.resource.texture.TextureResource;
	import aerys.minko.type.binding.IDataProvider;
	import aerys.minko.type.math.Matrix4x4;

	public class BasicMaterial extends Material
	{
		public static const DEFAULT_NAME			: String		= 'BasicMaterial';
		public static const DEFAULT_BASIC_SHADER	: BasicShader	= new BasicShader();
		public static const DEFAULT_EFFECT			: Effect		= new Effect(DEFAULT_BASIC_SHADER);

		public function get blending() : uint
		{
			return getProperty(BasicProperties.BLENDING);
		}
		public function set blending(value : uint) : void
		{
			setProperty(BasicProperties.BLENDING, value);
		}

		public function get triangleCulling() : uint
		{
			return getProperty(BasicProperties.TRIANGLE_CULLING);
		}
		public function set triangleCulling(value : uint) : void
		{
			setProperty(BasicProperties.TRIANGLE_CULLING, value);
		}

		public function get diffuseColor() : uint
		{
			return getProperty(BasicProperties.DIFFUSE_COLOR);
		}
		public function set diffuseColor(value : uint) : void
		{
			setProperty(BasicProperties.DIFFUSE_COLOR, value);
		}

		public function get diffuseMap() : TextureResource
		{
			return getProperty(BasicProperties.DIFFUSE_MAP);
		}
		public function set diffuseMap(value : TextureResource) : void
		{
			setProperty(BasicProperties.DIFFUSE_MAP, value);
		}

		public function get alphaMap() : TextureResource
		{
			return getProperty(BasicProperties.ALPHA_MAP) as TextureResource;
		}
		public function set alphaMap(value : TextureResource) : void
		{
			setProperty(BasicProperties.ALPHA_MAP, value);
		}

		public function get alphaThreshold() : Number
		{
			return getProperty(BasicProperties.ALPHA_THRESHOLD);
		}
		public function set alphaThreshold(value : Number) : void
		{
			setProperty(BasicProperties.ALPHA_THRESHOLD, value);
		}

		public function get depthTest() : uint
		{
			return getProperty(BasicProperties.DEPTH_TEST);
		}
		public function set depthTest(value : uint) : void
		{
			setProperty(BasicProperties.DEPTH_TEST, value);
		}

		public function get depthWriteEnabled() : Boolean
		{
			return getProperty(BasicProperties.DEPTH_WRITE_ENABLED);
		}
		public function set depthWriteEnabled(value : Boolean) : void
		{
			setProperty(BasicProperties.DEPTH_WRITE_ENABLED, value);
		}

		public function get stencilTriangleFace() : uint
		{
			return getProperty(BasicProperties.STENCIL_TRIANGLE_FACE);
		}
		public function set stencilTriangleFace(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_TRIANGLE_FACE, value);
		}

		public function get stencilCompareMode() : uint
		{
			return getProperty(BasicProperties.STENCIL_COMPARE_MODE);
		}
		public function set stencilCompareMode(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_COMPARE_MODE, value);
		}

		public function get stencilActionBothPass() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_BOTH_PASS);
		}
		public function set stencilActionBothPass(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_BOTH_PASS, value);
		}

		public function get stencilActionDepthFail() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_DEPTH_FAIL);
		}
		public function set stencilActionDepthFail(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_DEPTH_FAIL, value);
		}

		public function get stencilActionDepthPassStencilFail() : uint
		{
			return getProperty(BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL);
		}
		public function set stencilActionDepthPassStencilFail(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL, value);
		}

		public function get stencilReferenceValue() : Number
		{
			return getProperty(BasicProperties.STENCIL_REFERENCE_VALUE);
		}
		public function set stencilReferenceValue(value : Number) : void
		{
			setProperty(BasicProperties.STENCIL_REFERENCE_VALUE, value);
		}

		public function get stencilReadMask() : uint
		{
			return getProperty(BasicProperties.STENCIL_READ_MASK);
		}
		public function set stencilReadMask(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_READ_MASK, value);
		}

		public function get stencilWriteMask() : uint
		{
			return getProperty(BasicProperties.STENCIL_WRITE_MASK);
		}
		public function set stencilWriteMask(value : uint) : void
		{
			setProperty(BasicProperties.STENCIL_WRITE_MASK, value);
		}

		public function get diffuseTransform() : Matrix4x4
		{
			return getProperty(BasicProperties.DIFFUSE_TRANSFORM);
		}
		public function set diffuseTransform(value : Matrix4x4) : void
		{
			setProperty(BasicProperties.DIFFUSE_TRANSFORM, value);
		}

		public function BasicMaterial(properties : Object = null, effect : Effect = null, name : String = DEFAULT_NAME)
		{
			super(effect || DEFAULT_EFFECT, properties, name);
		}

		override public function clone() : IDataProvider
		{
			var mat : BasicMaterial	= new BasicMaterial(this, effect, name);

			return mat;
		}
	}
}

add the BasicMaterial.alphaMap property

add the BasicMaterial.alphaMap property


package aerys.minko.scene.controller
{
	import aerys.minko.scene.data.TransformDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	public final class TransformController extends AbstractController
	{
		private var _node	: ISceneNode;
		private var _data	: TransformDataProvider;

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											node 	: ISceneNode) : void
		{
			if (_node)
				throw new Error();

			_node = node;
			_node.transform.changed.add(transformChangedHandler);
			_node.added.add(addedHandler);
			_node.removed.add(removedHandler);

			if (_node is Mesh)
			{
				_node.addedToScene.add(meshAddedToSceneHandler);
				_node.removedFromScene.add(meshRemovedFromSceneHandler);
			}

			transformChangedHandler(_node.transform);
		}

		private function targetRemovedHandler(ctrl	: TransformController,
											  node 	: ISceneNode) : void
		{
			if (_node is Mesh)
			{
				_node.addedToScene.remove(meshAddedToSceneHandler);
				_node.removedFromScene.remove(meshRemovedFromSceneHandler);
			}

			_node.removed.remove(removedHandler);
			_node.added.remove(addedHandler);
			_node.transform.changed.remove(transformChangedHandler);
			_node = null;
		}

		private function meshAddedToSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			_data = new TransformDataProvider(mesh.localToWorld, mesh.worldToLocal);
			mesh.bindings.addProvider(_data);
		}

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			mesh.bindings.removeProvider(_data);
			_data = null;
		}

		private function transformChangedHandler(transform : Matrix4x4) : void
		{
			var parent : ISceneNode = _node.parent;

			if (_node.parent)
			{
				_node.localToWorld.lock()
					.copyFrom(_node.transform)
					.append(parent.localToWorld)
					.unlock();
			}
			else
				_node.localToWorld.copyFrom(_node.transform);

			_node.worldToLocal.lock()
				.copyFrom(_node.localToWorld)
				.invert()
				.unlock();
		}

		private function addedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.add(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}

		private function removedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.remove(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.scene.data.TransformDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	public final class TransformController extends AbstractController
	{
		private var _node	: ISceneNode;
		private var _data	: TransformDataProvider;

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											node 	: ISceneNode) : void
		{
			if (_node)
				throw new Error();

			_node = node;
			_node.transform.changed.add(transformChangedHandler);
			_node.added.add(addedHandler);
			_node.removed.add(removedHandler);

			if (_node is Mesh)
			{
				_node.addedToScene.add(meshAddedToSceneHandler);
				_node.removedFromScene.add(meshRemovedFromSceneHandler);
			}

			transformChangedHandler(_node.transform);
		}

		private function targetRemovedHandler(ctrl	: TransformController,
											  node 	: ISceneNode) : void
		{
			if (_node is Mesh)
			{
				_node.addedToScene.remove(meshAddedToSceneHandler);
				_node.removedFromScene.remove(meshRemovedFromSceneHandler);
			}

			_node.removed.remove(removedHandler);
			_node.added.remove(addedHandler);
			_node.transform.changed.remove(transformChangedHandler);
			_node = null;
		}

		private function meshAddedToSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			_data = new TransformDataProvider(mesh.localToWorld, mesh.worldToLocal);
			mesh.bindings.addProvider(_data);
		}

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			mesh.bindings.removeProvider(_data);
			_data = null;
		}

		private function transformChangedHandler(transform : Matrix4x4) : void
		{
			var parent : ISceneNode = _node.parent;

            _node.worldToLocal.lock();
            _node.localToWorld.lock();

			if (_node.parent)
			{
				_node.localToWorld.lock()
					.copyFrom(_node.transform)
					.append(parent.localToWorld)
			}
			else
				_node.localToWorld.copyFrom(_node.transform);

			_node.worldToLocal.lock()
				.copyFrom(_node.localToWorld)
				.invert();

            _node.worldToLocal.unlock();
            _node.localToWorld.unlock();
		}

		private function addedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.add(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}

		private function removedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.remove(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}
	}
}

fix TransformController.transformChangedHandler() to lock() and unlock() worldToLocal and localToWorld at the same time


import mx.transitions.easing.*;
import com.mosesSupposes.fuse.*;

class idiomesAnimation extends Movieclip
        {

  	private var arrayLang_arr:Array = new Array ("cat", "es", "en", "fr");

			function idiomesAnimation():Void
		{
			this.stop();
			this.onEnterFrame = this.enterSlide;

		}

	private function enterSlide()
                {

			arrayLang_arr[0].slideTo('0', '-40', 0.6, 'easeInOutBack', 1);
			arrayLang_arr[1].slideTo('0', '-40', 0.8, 'easeInOutBack', 1.1);
			arrayLang_arr[2].slideTo('0', '-40', 0.9, 'easeInOutBack', 1.2);
			arrayLang_arr[3].slideTo('0', '-40', 1, 'easeInOutBack', 1.3);

		}

	public function IdiomesColour()
		{

			this.onRelease = this arrayColour;

			target = this.arrayLang_arr[i];
			target._tint = 0x8DC60D;

			return this;

		}

	private function arrayColour()
		{

			for ( var:i ; arrayLang_arr[i] <4; i++)
			{
				this.colorTo("#628D22", 0.3, "easeOutCirc");
				_parent.colorTo('#4b4b4b', 1, 'linear', .2);

			}

		}

	}

import mx.transitions.easing.*;
import com.mosesSupposes.fuse.*;

class idiomesAnimation extends Movieclip
        {

  	private var arrayLang_arr:Array = new Array ("cat", "es", "en", "fr");

			function idiomesAnimation():Void
		{
			this.stop();
			this.onEnterFrame = this.enterSlide;

		}

	private function enterSlide()
                {

			arrayLang_arr[0].slideTo('0', '-40', 0.6, 'easeInOutBack', 1);
			arrayLang_arr[1].slideTo('0', '-40', 0.8, 'easeInOutBack', 1.1);
			arrayLang_arr[2].slideTo('0', '-40', 0.9, 'easeInOutBack', 1.2);
			arrayLang_arr[3].slideTo('0', '-40', 1, 'easeInOutBack', 1.3);

		}

	public function IdiomesColour()
		{

			this.onRelease = this arrayColour;

			target = this.arrayLang_arr[i];
			target._tint = 0x8DC60D;

			return this;

		}

	private function arrayColour()
		{

			for ( var:i:Number=0 ; arrayLang_arr[i] <4; i++)
			{
				this.colorTo("#628D22", 0.3, "easeOutCirc");
				_parent.colorTo('#4b4b4b', 1, 'linear', .2);

			}

		}

	}

Update idiomesAnimation.as

Update idiomesAnimation.as


package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        /* Adobe's Cirrus server for RTMFP connections.
           The Cirrus key is defined at compile time by
           reading from the CIRRUS_KEY environment var. */
        private const CIRRUS_URL:String = "rtmfp://p2p.rtmfp.net";
        private const CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 1;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 10000;

        // Bytes per second. Set to undefined to disable limit.
        public const RATE_LIMIT:Number = undefined;
        // Seconds.
        private const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;

        private var fac_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            output_text = new TextField();
            output_text.width = stage.stageWidth;
            output_text.height = stage.stageHeight;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44cc44;

            badge = new Badge();

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var fac_spec:String;

            puts("Parameters loaded.");

            if (this.loaderInfo.parameters["debug"])
                addChild(output_text);
            else
                addChild(badge);

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameter["proxy"])
                proxy_main();
            else
                client_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String, addr:Object;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(CIRRUS_URL, CIRRUS_KEY);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(LOCAL_TOR_CLIENT_ADDR.host, LOCAL_TOR_CLIENT_ADDR.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;
            var variables:URLVariables;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables;
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(CIRRUS_URL, CIRRUS_KEY);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* Parse an address in the form "host:port". Returns an Object with
           keys "host" (String) and "port" (int). Returns null on error. */
        private static function parse_addr_spec(spec:String):Object
        {
            var parts:Array;
            var addr:Object;

            parts = spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1]))
                return null;
            addr = {}
            addr.host = parts[0];
            addr.port = parseInt(parts[1]);

            return addr;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        /* Adobe's Cirrus server for RTMFP connections.
           The Cirrus key is defined at compile time by
           reading from the CIRRUS_KEY environment var. */
        private const CIRRUS_URL:String = "rtmfp://p2p.rtmfp.net";
        private const CIRRUS_KEY:String = RTMFP::CIRRUS_KEY;

        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "tor-facilitator.bamsoftware.com",
            port: 9002
        };

        /* Local Tor client to use in case of RTMFP connection. */
        private const LOCAL_TOR_CLIENT_ADDR:Object = {
            host: "127.0.0.1",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 1;

        // Milliseconds.
        private const FACILITATOR_POLL_INTERVAL:int = 10000;

        // Bytes per second. Set to undefined to disable limit.
        public const RATE_LIMIT:Number = undefined;
        // Seconds.
        private const RATE_LIMIT_HISTORY:Number = 5.0;

        /* TextField for debug output. */
        private var output_text:TextField;

        /* UI shown when debug is off. */
        private var badge:Badge;

        /* Number of proxy pairs currently connected (up to
           MAX_NUM_PROXY_PAIRS). */
        private var num_proxy_pairs:int = 0;

        private var fac_addr:Object;

        public var rate_limit:RateLimit;

        public function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            // Absolute positioning.
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            output_text = new TextField();
            output_text.width = stage.stageWidth;
            output_text.height = stage.stageHeight;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44cc44;

            badge = new Badge();

            if (RATE_LIMIT)
                rate_limit = new BucketRateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
            else
                rate_limit = new RateUnlimit();

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var fac_spec:String;

            puts("Parameters loaded.");

            if (this.loaderInfo.parameters["debug"])
                addChild(output_text);
            else
                addChild(badge);

            fac_addr = get_param_addr("facilitator", DEFAULT_FACILITATOR_ADDR);
            if (!fac_addr) {
                puts("Error: Facilitator spec must be in the form \"host:port\".");
                return;
            }

            if (this.loaderInfo.parameters["client"])
                client_main();
            else
                proxy_main();
        }

        /* Get an address structure from the given movie parameter, or the given
           default. Returns null on error. */
        private function get_param_addr(param:String, default_addr:Object):Object
        {
            var spec:String, addr:Object;

            spec = this.loaderInfo.parameters[param];
            if (spec)
                return parse_addr_spec(spec);
            else
                return default_addr;
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function proxy_main():void
        {
            var fac_url:String;
            var loader:URLLoader;

            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            loader = new URLLoader();
            /* Get the x-www-form-urlencoded values. */
            loader.dataFormat = URLLoaderDataFormat.VARIABLES;
            loader.addEventListener(Event.COMPLETE, fac_complete);
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(new URLRequest(fac_url));
        }

        private function fac_complete(e:Event):void
        {
            var loader:URLLoader;
            var client_spec:String;
            var relay_spec:String;
            var proxy_pair:Object;

            setTimeout(proxy_main, FACILITATOR_POLL_INTERVAL);

            loader = e.target as URLLoader;
            client_spec = loader.data.client;
            if (client_spec == "") {
                puts("No clients.");
                return;
            } else if (!client_spec) {
                puts("Error: missing \"client\" in response.");
                return;
            }
            relay_spec = loader.data.relay;
            if (!relay_spec) {
                puts("Error: missing \"relay\" in response.");
                return;
            }
            puts("Facilitator: got client:\"" + client_spec + "\" "
                + "relay:\"" + relay_spec + "\".");

            try {
                proxy_pair = make_proxy_pair(client_spec, relay_spec);
            } catch (e:ArgumentError) {
                puts("Error: " + e);
                return;
            }
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
                badge.proxy_end();
            });
            proxy_pair.connect();

            num_proxy_pairs++;
            badge.proxy_begin();
        }

        private function client_main():void
        {
            var rs:RTMFPSocket;

            rs = new RTMFPSocket(CIRRUS_URL, CIRRUS_KEY);
            rs.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Got RTMFP id " + rs.id);
                register(rs);
            });
            rs.addEventListener(RTMFPSocket.ACCEPT_EVENT, client_accept);

            rs.listen();
        }

        private function client_accept(e:Event):void {
            var rs:RTMFPSocket;
            var s_t:Socket;
            var proxy_pair:ProxyPair;

            rs = e.target as RTMFPSocket;
            s_t = new Socket();

            puts("Got RTMFP connection from " + rs.peer_id);

            proxy_pair = new ProxyPair(this, rs, function ():void {
                /* Do nothing; already connected. */
            }, s_t, function ():void {
                s_t.connect(LOCAL_TOR_CLIENT_ADDR.host, LOCAL_TOR_CLIENT_ADDR.port);
            });
            proxy_pair.connect();
        }

        private function register(rs:RTMFPSocket):void {
            var fac_url:String;
            var loader:URLLoader;
            var request:URLRequest;
            var variables:URLVariables;

            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, function (e:Event):void {
                puts("Facilitator: registered.");
            });
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
                rs.close();
            });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
                rs.close();
            });

            fac_url = "http://" + encodeURIComponent(fac_addr.host)
                + ":" + encodeURIComponent(fac_addr.port) + "/";
            request = new URLRequest(fac_url);
            request.method = URLRequestMethod.POST;
            request.data = new URLVariables;
            request.data["client"] = rs.id;

            puts("Facilitator: connecting to " + fac_url + ".");
            loader.load(request);
        }

        private function make_proxy_pair(client_spec:String, relay_spec:String):ProxyPair
        {
            var proxy_pair:ProxyPair;
            var addr_c:Object;
            var addr_r:Object;
            var s_c:*;
            var s_r:Socket;

            addr_r = swfcat.parse_addr_spec(relay_spec);
            if (!addr_r)
                throw new ArgumentError("Relay spec must be in the form \"host:port\".");

            addr_c = swfcat.parse_addr_spec(client_spec);
            if (addr_c) {
                s_c = new Socket();
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(addr_c.host, addr_c.port);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + addr_c.host + ":" + addr_c.port + ","
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            if (client_spec.match(/^[0-9A-Fa-f]{64}$/)) {
                s_c = new RTMFPSocket(CIRRUS_URL, CIRRUS_KEY);
                s_r = new Socket();
                proxy_pair = new ProxyPair(this, s_c, function ():void {
                    s_c.connect(client_spec);
                }, s_r, function ():void {
                    s_r.connect(addr_r.host, addr_r.port);
                });
                proxy_pair.set_name("<" + client_spec.substr(0, 4) + "...,"
                    + addr_r.host + ":" + addr_r.port + ">");
                return proxy_pair;
            }

            throw new ArgumentError("Can't parse client spec \"" + client_spec + "\".");
        }

        /* Parse an address in the form "host:port". Returns an Object with
           keys "host" (String) and "port" (int). Returns null on error. */
        private static function parse_addr_spec(spec:String):Object
        {
            var parts:Array;
            var addr:Object;

            parts = spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1]))
                return null;
            addr = {}
            addr.host = parts[0];
            addr.port = parseInt(parts[1]);

            return addr;
        }
    }
}

import flash.text.TextFormat;
import flash.text.TextField;
import flash.utils.getTimer;

class Badge extends flash.display.Sprite
{
    /* Number of proxy pairs currently connected. */
    private var num_proxy_pairs:int = 0;
    /* Number of proxy pairs ever connected. */
    private var total_proxy_pairs:int = 0;

    [Embed(source="badge.png")]
    private var BadgeImage:Class;
    private var tot_client_count_tf:TextField;
    private var tot_client_count_fmt:TextFormat;
    private var cur_client_count_tf:TextField;
    private var cur_client_count_fmt:TextFormat;

    public function Badge()
    {
        /* Setup client counter for badge. */
        tot_client_count_fmt = new TextFormat();
        tot_client_count_fmt.color = 0xFFFFFF;
        tot_client_count_fmt.align = "center";
        tot_client_count_fmt.font = "courier-new";
        tot_client_count_fmt.bold = true;
        tot_client_count_fmt.size = 10;
        tot_client_count_tf = new TextField();
        tot_client_count_tf.width = 20;
        tot_client_count_tf.height = 17;
        tot_client_count_tf.background = false;
        tot_client_count_tf.defaultTextFormat = tot_client_count_fmt;
        tot_client_count_tf.x=47;
        tot_client_count_tf.y=0;

        cur_client_count_fmt = new TextFormat();
        cur_client_count_fmt.color = 0xFFFFFF;
        cur_client_count_fmt.align = "center";
        cur_client_count_fmt.font = "courier-new";
        cur_client_count_fmt.bold = true;
        cur_client_count_fmt.size = 10;
        cur_client_count_tf = new TextField();
        cur_client_count_tf.width = 20;
        cur_client_count_tf.height = 17;
        cur_client_count_tf.background = false;
        cur_client_count_tf.defaultTextFormat = cur_client_count_fmt;
        cur_client_count_tf.x=47;
        cur_client_count_tf.y=6;

        addChild(new BadgeImage());
        addChild(tot_client_count_tf);
        addChild(cur_client_count_tf);

        /* Update the client counter on badge. */
        update_client_count();
    }

    public function proxy_begin():void
    {
        num_proxy_pairs++;
        total_proxy_pairs++;
        update_client_count();
    }

    public function proxy_end():void
    {
        num_proxy_pairs--;
        update_client_count();
    }

    private function update_client_count():void
    {
        /* Update total client count. */
        if (String(total_proxy_pairs).length == 1)
            tot_client_count_tf.text = "0" + String(total_proxy_pairs);
        else
            tot_client_count_tf.text = String(total_proxy_pairs);

        /* Update current client count. */
        cur_client_count_tf.text = "";
        for(var i:Number = 0; i < num_proxy_pairs; i++)
            cur_client_count_tf.appendText(".");
    }
}

class RateLimit
{
    public function RateLimit()
    {
    }

    public function update(n:Number):Boolean
    {
        return true;
    }

    public function when():Number
    {
        return 0.0;
    }

    public function is_limited():Boolean
    {
        return false;
    }
}

class RateUnlimit extends RateLimit
{
    public function RateUnlimit()
    {
    }

    public override function update(n:Number):Boolean
    {
        return true;
    }

    public override function when():Number
    {
        return 0.0;
    }

    public override function is_limited():Boolean
    {
        return false;
    }
}

class BucketRateLimit extends RateLimit
{
    private var amount:Number;
    private var capacity:Number;
    private var time:Number;
    private var last_update:uint;

    public function BucketRateLimit(capacity:Number, time:Number)
    {
        this.amount = 0.0;
        /* capacity / time is the rate we are aiming for. */
        this.capacity = capacity;
        this.time = time;
        this.last_update = getTimer();
    }

    private function age():void
    {
        var now:uint;
        var delta:Number;

        now = getTimer();
        delta = (now - last_update) / 1000.0;
        last_update = now;

        amount -= delta * capacity / time;
        if (amount < 0.0)
            amount = 0.0;
    }

    public override function update(n:Number):Boolean
    {
        age();
        amount += n;

        return amount <= capacity;
    }

    public override function when():Number
    {
        age();
        return (amount - capacity) / (capacity / time);
    }

    public override function is_limited():Boolean
    {
        age();
        return amount > capacity;
    }
}

Make proxy mode the default in swfcat.

Make proxy mode the default in swfcat.

In other words, use client=true instead of nothing for client mode, and
nothing instead of proxy=true for proxy mode. This makes it a drop-in
replacement for master swfcat.


//
// $Id$

package com.threerings.flash {

import flash.display.Sprite;

import flash.events.Event;

/**
 * Convenience superclass to use for sprites that need to update every frame.
 * (One must be very careful to remove all ENTER_FRAME listeners when not needed, as they
 * will prevent an object from being garbage collected!)
 */
public class FrameSprite extends Sprite
{
    public function FrameSprite ()
    {
        addEventListener(Event.ADDED_TO_STAGE, handleAdded);
        addEventListener(Event.REMOVED_FROM_STAGE, handleRemoved);
    }

    /**
     * Called when we're added to the stage.
     */
    protected function handleAdded (... ignored) :void
    {
        addEventListener(Event.ENTER_FRAME, handleFrame);
        handleFrame(); // update immediately
    }

    /**
     * Called when we're added to the stage.
     */
    protected function handleRemoved (... ignored) :void
    {
        removeEventListener(Event.ENTER_FRAME, handleFrame);
    }

    /**
     * Called to update our visual appearance prior to the flash player each frame.
     */
    protected function handleFrame (... ignored) :void
    {
        // nothing here. Override in yor subclass.
    }
}

}

//
// $Id$

package com.threerings.flash {

import flash.display.Sprite;

import flash.events.Event;

/**
 * Convenience superclass to use for sprites that need to update every frame.
 * (One must be very careful to remove all ENTER_FRAME listeners when not needed, as they
 * will prevent an object from being garbage collected!)
 */
public class FrameSprite extends Sprite
{
    public function FrameSprite ()
    {
        addEventListener(Event.ADDED_TO_STAGE, handleAdded);
        addEventListener(Event.REMOVED_FROM_STAGE, handleRemoved);
    }

    /**
     * Called when we're added to the stage.
     */
    protected function handleAdded (... ignored) :void
    {
        addEventListener(Event.ENTER_FRAME, handleFrame);
        handleFrame(); // update immediately
    }

    /**
     * Called when we're added to the stage.
     */
    protected function handleRemoved (... ignored) :void
    {
        removeEventListener(Event.ENTER_FRAME, handleFrame);
    }

    /**
     * Called to update our visual appearance prior to each frame.
     */
    protected function handleFrame (... ignored) :void
    {
        // nothing here. Override in yor subclass.
    }
}

}

Comment fixup.

Comment fixup.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@240 ed5b42cb-e716-0410-a449-f6a68f950b19