package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    import mx.olap.aggregators.MaxAggregator;

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {

        private static const FLAG_NONE                      : uint  = 0;
        private static const FLAG_INIT_LOCAL_TO_WORLD       : uint  = 1;
        private static const FLAG_INIT_WORLD_TO_LOCAL       : uint  = 2;
        private static const FLAG_SYNCHRONIZE_TRANSFORMS    : uint  = 4;
        private static const FLAG_LOCK_TRANSFORMS           : uint  = 8;

        private var _target					: ISceneNode;

        private var _invalidList			: Boolean;

        private var _nodeToId				: Dictionary;
        private var _idToNode				: Vector.<ISceneNode>
        private var _transforms				: Vector.<Matrix4x4>;
        private var _flags	                : Vector.<uint>;
        private var _localToWorldTransforms : Vector.<Matrix4x4>;
        private var _worldToLocalTransforms : Vector.<Matrix4x4>;
        private var _numChildren			: Vector.<uint>;
        private var _firstChildId			: Vector.<uint>;
        private var _parentId				: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene		: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateRootLocalToWorld(nodeId : uint = 0) : void
        {
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[nodeId];
            var rootFlags           : uint          = _flags[nodeId];

            if (rootTransform._hasChanged || !(rootFlags & FLAG_INIT_LOCAL_TO_WORLD))
            {
                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.lock();

                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.unlock();

                rootTransform._hasChanged = false;
                _flags[nodeId] |= FLAG_INIT_LOCAL_TO_WORLD;
                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);

                if (rootFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                {
                    var rootWorldToLocal : Matrix4x4 = _worldToLocalTransforms[nodeId]
                        || (_worldToLocalTransforms[nodeId] = new Matrix4x4());

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.lock();

                    rootWorldToLocal.copyFrom(rootLocalToWorld).invert();

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.unlock();
                }
            }
        }

        private function updateLocalToWorld(nodeId : uint = 0, targetNodeId : int = -1) : void
        {
            var numNodes 	: uint 	= _transforms.length;
            var subtreeMax  : uint  = nodeId;

            updateRootLocalToWorld(nodeId);

            if (nodeId == targetNodeId)
                return;

            while (nodeId < numNodes)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                if (lastChildId > subtreeMax)
                    subtreeMax = lastChildId;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childFlags          : uint          = _flags[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !(childFlags & FLAG_INIT_LOCAL_TO_WORLD);

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.lock();

                        childLocalToWorld
                            .copyFrom(childTransform)
                            .append(localToWorld);

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.unlock();

                        childTransform._hasChanged = false;
                        _flags[childId] |= FLAG_INIT_LOCAL_TO_WORLD;
                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);

                        if (childFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                        {
                            var childWorldToLocal : Matrix4x4 = _worldToLocalTransforms[childId]
                                || (_worldToLocalTransforms[childId] = new Matrix4x4());

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.lock();

                            childWorldToLocal.copyFrom(childLocalToWorld).invert();

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.unlock();
                        }

                        if (childId == targetNodeId)
                            return;
                    }
                }

                if (targetNodeId >= 0 && nodeId && nodeId >= subtreeMax)
                {
                    // jump to the first brother who has children
                    var parentId : uint = _parentId[nodeId];

                    nodeId = _firstChildId[parentId];
                    while (!_numChildren[nodeId] && nodeId < subtreeMax)
                        ++nodeId;

                    if (nodeId >= subtreeMax)
                        return ;

                    nodeId = _firstChildId[nodeId];
                }
                else
                    ++nodeId;
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : uint) : void
        {
            var dirtyRoot : int = -1;
            var tmpNodeId : int = nodeId;

            while (tmpNodeId >= 0)
            {
                if ((_transforms[tmpNodeId] as Matrix4x4)._hasChanged
                    || !(_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD))
                    dirtyRoot = tmpNodeId;

                tmpNodeId = _parentId[tmpNodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorld(dirtyRoot, nodeId);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _flags = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function getNodeId(node : ISceneNode) : uint
        {
            if (_invalidList || !(node in _nodeToId))
                updateTransformsList();

            return _nodeToId[node];
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _flags;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _flags = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _flags[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _flags[nodeId] = FLAG_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            var nodeId : uint = getNodeId(node);

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            var nodeId                  : uint      = getNodeId(node);
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            var flags : uint = _flags[nodeId];

            if (!(flags & FLAG_INIT_WORLD_TO_LOCAL))
            {
                _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.lock();

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.unlock();
            }

            return worldToLocalTransform;
        }

        public function setSharedLocalToWorldTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD)
                matrix.copyFrom(_localToWorldTransforms[nodeId]);
            _localToWorldTransforms[nodeId] = matrix;
        }

        public function setSharedWorldToLocalTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_WORLD_TO_LOCAL)
                matrix.copyFrom(_worldToLocalTransforms[nodeId]);
            _worldToLocalTransforms[nodeId] = matrix;
        }

        public function synchronizeTransforms(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_SYNCHRONIZE_TRANSFORMS
                : _flags[nodeId] & ~FLAG_SYNCHRONIZE_TRANSFORMS;
        }

        public function lockTransformsBeforeUpdate(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_LOCK_TRANSFORMS
                : _flags[nodeId] & ~FLAG_LOCK_TRANSFORMS;
        }
    }
}

package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    import mx.olap.aggregators.MaxAggregator;

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {

        private static const FLAG_NONE                      : uint  = 0;
        private static const FLAG_INIT_LOCAL_TO_WORLD       : uint  = 1;
        private static const FLAG_INIT_WORLD_TO_LOCAL       : uint  = 2;
        private static const FLAG_SYNCHRONIZE_TRANSFORMS    : uint  = 4;
        private static const FLAG_LOCK_TRANSFORMS           : uint  = 8;

        private var _target					: ISceneNode;

        private var _invalidList			: Boolean;

        private var _nodeToId				: Dictionary;
        private var _idToNode				: Vector.<ISceneNode>
        private var _transforms				: Vector.<Matrix4x4>;
        private var _flags	                : Vector.<uint>;
        private var _localToWorldTransforms : Vector.<Matrix4x4>;
        private var _worldToLocalTransforms : Vector.<Matrix4x4>;
        private var _numChildren			: Vector.<uint>;
        private var _firstChildId			: Vector.<uint>;
        private var _parentId				: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene		: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateRootLocalToWorld(nodeId : uint = 0) : void
        {
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[nodeId];
            var rootFlags           : uint          = _flags[nodeId];

            if (rootTransform._hasChanged || !(rootFlags & FLAG_INIT_LOCAL_TO_WORLD))
            {
                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.lock();

                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.unlock();

                rootTransform._hasChanged = false;
                rootFlags = (rootFlags | FLAG_INIT_LOCAL_TO_WORLD) & ~FLAG_INIT_WORLD_TO_LOCAL;

                if (rootFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                {
                    var rootWorldToLocal : Matrix4x4 = _worldToLocalTransforms[nodeId]
                        || (_worldToLocalTransforms[nodeId] = new Matrix4x4());

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.lock();

                    rootWorldToLocal.copyFrom(rootLocalToWorld).invert();
                    rootFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.unlock();
                }

                _flags[nodeId] = rootFlags;

                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
            }
        }

        private function updateLocalToWorld(nodeId : uint = 0, targetNodeId : int = -1) : void
        {
            var numNodes 	: uint 	= _transforms.length;
            var subtreeMax  : uint  = nodeId;

            updateRootLocalToWorld(nodeId);

            if (nodeId == targetNodeId)
                return;

            while (nodeId < numNodes)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                if (lastChildId > subtreeMax)
                    subtreeMax = lastChildId;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childFlags          : uint          = _flags[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !(childFlags & FLAG_INIT_LOCAL_TO_WORLD);

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.lock();

                        childLocalToWorld
                            .copyFrom(childTransform)
                            .append(localToWorld);

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.unlock();

                        childTransform._hasChanged = false;
                        childFlags = (childFlags | FLAG_INIT_LOCAL_TO_WORLD)
                            & ~FLAG_INIT_WORLD_TO_LOCAL;

                        if (childFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                        {
                            var childWorldToLocal : Matrix4x4 = _worldToLocalTransforms[childId]
                                || (_worldToLocalTransforms[childId] = new Matrix4x4());

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.lock();

                            childWorldToLocal.copyFrom(childLocalToWorld).invert();
                            childFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.unlock();
                        }

                        _flags[childId] = childFlags;

                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);

                        if (childId == targetNodeId)
                            return;
                    }
                }

                if (targetNodeId >= 0 && nodeId && nodeId >= subtreeMax)
                {
                    // jump to the first brother who has children
                    var parentId : uint = _parentId[nodeId];

                    nodeId = _firstChildId[parentId];
                    while (!_numChildren[nodeId] && nodeId < subtreeMax)
                        ++nodeId;

                    if (nodeId >= subtreeMax)
                        return ;

                    nodeId = _firstChildId[nodeId];
                }
                else
                    ++nodeId;
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : uint) : void
        {
            var dirtyRoot : int = -1;
            var tmpNodeId : int = nodeId;

            while (tmpNodeId >= 0)
            {
                if ((_transforms[tmpNodeId] as Matrix4x4)._hasChanged
                    || !(_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD))
                    dirtyRoot = tmpNodeId;

                tmpNodeId = _parentId[tmpNodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorld(dirtyRoot, nodeId);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _flags = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function getNodeId(node : ISceneNode) : uint
        {
            if (_invalidList || !(node in _nodeToId))
                updateTransformsList();

            return _nodeToId[node];
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _flags;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _flags = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _flags[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _flags[nodeId] = FLAG_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            var nodeId : uint = getNodeId(node);

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            var nodeId                  : uint      = getNodeId(node);
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            var flags : uint = _flags[nodeId];

            if (!(flags & FLAG_INIT_WORLD_TO_LOCAL))
            {
                _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.lock();

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.unlock();
            }

            return worldToLocalTransform;
        }

        public function setSharedLocalToWorldTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD)
                matrix.copyFrom(_localToWorldTransforms[nodeId]);
            _localToWorldTransforms[nodeId] = matrix;
        }

        public function setSharedWorldToLocalTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_WORLD_TO_LOCAL)
                matrix.copyFrom(_worldToLocalTransforms[nodeId]);
            _worldToLocalTransforms[nodeId] = matrix;
        }

        public function synchronizeTransforms(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_SYNCHRONIZE_TRANSFORMS
                : _flags[nodeId] & ~FLAG_SYNCHRONIZE_TRANSFORMS;
        }

        public function lockTransformsBeforeUpdate(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_LOCK_TRANSFORMS
                : _flags[nodeId] & ~FLAG_LOCK_TRANSFORMS;
        }
    }
}

fix TransformController.updateRootLocalToWorld() and TransformController.updateLocalToWorld() to update the node's flags properly in order to make sure world to local is not still marked as init. when local to world has changed the ISceneNode.localToWorldChanged signal will also be called after world to local has been updated (if required) in order to avoid a race condition when calling TransformController.getWorldToLocalTransform(node) in one of this signal callbacks


/*
 *  FRESteamWorks.h
 *  This file is part of FRESteamWorks.
 *
 *  Created by David ´Oldes´ Oliva on 3/29/12.
 *  Contributors: Ventero <http://github.com/Ventero>
 *  Copyright (c) 2012 Amanita Design. All rights reserved.
 *  Copyright (c) 2012-2013 Level Up Labs, LLC. All rights reserved.
 */

package
{
	import com.amanitadesign.steam.FRESteamWorks;
	import com.amanitadesign.steam.SteamConstants;
	import com.amanitadesign.steam.SteamEvent;
	import com.amanitadesign.steam.WorkshopConstants;

	import flash.desktop.NativeApplication;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.display.StageDisplayState;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.ByteArray;

	public class FRESteamWorksTest extends Sprite
	{
		public var Steamworks:FRESteamWorks = new FRESteamWorks();
		public var tf:TextField;

		private var _buttonPos:int = 5;
		private var _appId:uint;

		public function FRESteamWorksTest()
		{
			tf = new TextField();
			tf.x = 160;
			tf.width = stage.stageWidth - tf.x;
			tf.height = stage.stageHeight;
			addChild(tf);

			addButton("Check stats/achievements", checkAchievements);
			addButton("Toggle achievement", toggleAchievement);
			addButton("Toggle cloud enabled", toggleCloudEnabled);
			addButton("Toggle file", toggleFile);
			addButton("Publish file", publishFile);
			addButton("Toggle fullscreen", toggleFullscreen);
			addButton("Show Friends overlay", activateOverlay);
			addButton("List subscribed files", enumerateSubscribedFiles);

			Steamworks.addEventListener(SteamEvent.STEAM_RESPONSE, onSteamResponse);
			NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit);
			try {
				//Steamworks.useCrashHandler(480, "1.0", "Feb 20 2013", "21:42:20");
				if(!Steamworks.init()){
					log("STEAMWORKS API is NOT available");
					return;
				}

				log("STEAMWORKS API is available\n");
				log("User ID: " + Steamworks.getUserID());
				_appId = Steamworks.getAppID();
				log("App ID: " + _appId);
				log("Persona name: " + Steamworks.getPersonaName());
				log("isCloudEnabledForApp() == "+Steamworks.isCloudEnabledForApp());
				log("getFileCount() == "+Steamworks.getFileCount());
				log("fileExists('test.txt') == "+Steamworks.fileExists('test.txt'));
				log("getDLCCount() == " + Steamworks.getDLCCount());

				Steamworks.resetAllStats(true);
			} catch(e:Error) {
				log("*** ERROR ***");
				log(e.message);
				log(e.getStackTrace());
			}

		}
		private function log(value:String):void{
			tf.appendText(value+"\n");
			tf.scrollV = tf.maxScrollV;
		}

		public function writeFileToCloud(fileName:String, data:String):Boolean {
			var dataOut:ByteArray = new ByteArray();
			dataOut.writeUTFBytes(data);
			return Steamworks.fileWrite(fileName, dataOut);
		}

		public function readFileFromCloud(fileName:String):String {
			var dataIn:ByteArray = new ByteArray();
			var result:String;
			dataIn.position = 0;
			dataIn.length = Steamworks.getFileSize(fileName);

			if(dataIn.length>0 && Steamworks.fileRead(fileName,dataIn)){
				result = dataIn.readUTFBytes(dataIn.length);
			}
			return result;
		}

		private function checkAchievements(e:Event = null):void {
			if(!Steamworks.isReady) return;

			// current stats and achievement ids are from steam example app
			log("isAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.isAchievement("ACH_WIN_ONE_GAME"));
			log("isAchievement('ACH_TRAVEL_FAR_SINGLE') == "+Steamworks.isAchievement("ACH_TRAVEL_FAR_SINGLE"));
			log("setStatFloat('FeetTraveled') == "+Steamworks.setStatFloat('FeetTraveled', 21.3));
			log("setStatInt('NumGames', 2) == "+Steamworks.setStatInt('NumGames', 2));
			Steamworks.storeStats();
			log("getStatInt('NumGames') == "+Steamworks.getStatInt('NumGames'));
			log("getStatFloat('FeetTraveled') == "+Steamworks.getStatFloat('FeetTraveled'));
		}

		private function toggleAchievement(e:Event = null):void{
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.isAchievement("ACH_WIN_ONE_GAME");
			log("isAchievement('ACH_WIN_ONE_GAME') == " + result);
			if(!result) {
				log("setAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.setAchievement("ACH_WIN_ONE_GAME"));
			} else {
				log("clearAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.clearAchievement("ACH_WIN_ONE_GAME"));
			}
		}

		private function toggleCloudEnabled(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var enabled:Boolean = Steamworks.isCloudEnabledForApp();
			log("isCloudEnabledForApp() == " + enabled);
			log("setCloudEnabledForApp(" + !enabled + ") == " + Steamworks.setCloudEnabledForApp(!enabled));
			log("isCloudEnabledForApp() == " + Steamworks.isCloudEnabledForApp());
		}

		private function toggleFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.fileExists('test.txt');
			log("fileExists('test.txt') == " + result);
			if(result){
				log("readFileFromCloud('test.txt') == "+readFileFromCloud('test.txt') );
				log("fileDelete('test.txt') == "+Steamworks.fileDelete('test.txt'));
			} else {
				log("writeFileToCloud('test.txt','click') == "+writeFileToCloud('test.txt','click'));
			}
		}

		private function publishFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var res:Boolean = Steamworks.publishWorkshopFile("test.txt", "", _appId,
				"Test.txt", "Test.txt", WorkshopConstants.VISIBILITY_Private,
				["TestTag"], WorkshopConstants.FILETYPE_Community);
			log("publishWorkshopFile('test.txt' ...) == " + res);
		}

		private function toggleFullscreen(e:Event = null):void {
			if(stage.displayState == StageDisplayState.NORMAL)
				stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
			else
				stage.displayState = StageDisplayState.NORMAL;
		}

		private function activateOverlay(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("isOverlayEnabled() == " + Steamworks.isOverlayEnabled());
			log("activateGameOverlay('Friends') == " + Steamworks.activateGameOverlay("Friends"));
			log("isOverlayEnabled() == " + Steamworks.isOverlayEnabled());
		}

		private function enumerateSubscribedFiles(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("enumerateUserSubscribedFiles(0) == " + Steamworks.enumerateUserSubscribedFiles(0));
		}

		private var id:String;
		private function onSteamResponse(e:SteamEvent):void{
			switch(e.req_type){
				case SteamConstants.RESPONSE_OnUserStatsStored:
					log("RESPONSE_OnUserStatsStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnUserStatsReceived:
					log("RESPONSE_OnUserStatsReceived: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnAchievementStored:
					log("RESPONSE_OnAchievementStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnPublishWorkshopFile:
					log("RESPONSE_OnPublishWorkshopFile: " + e.response);
					var file:String = Steamworks.publishWorkshopFileResult();
					log("File published as " + file);
					log("subscribePublishedFile(...) == " + Steamworks.subscribePublishedFile(file));
					break;
				case SteamConstants.RESPONSE_OnEnumerateUserSubscribedFiles:
					log("RESPONSE_OnEnumerateUserSubscribedFiles: " + e.response);
					var result:SubscribedFilesResult = Steamworks.enumerateUserSubscribedFilesResult();
					log("User subscribed files: " + result.resultsReturned + "/" + result.totalResults);
					for(var i:int = 0; i < result.resultsReturned; i++) {
						id = result.publishedFileId[i];
						var apiCall:Boolean = Steamworks.getPublishedFileDetails(result.publishedFileId[i]);
						log(i + ": " + result.publishedFileId[i] + " (" + result.timeSubscribed[i] + ") - " + apiCall);
					}
					break;
				case SteamConstants.RESPONSE_OnGetPublishedFileDetails:
					log("RESPONSE_OnGetPublishedFileDetails: " + e.response);
					var res:FileDetailsResult = Steamworks.getPublishedFileDetailsResult(id);
					log("Result for " + id + ": " + res);
					if(res)
						log("File: " + res.fileName + ", handle: " + res.fileHandle);
					break;
				default:
					log("STEAMresponse type:"+e.req_type+" response:"+e.response);
			}
		}

		private function onExit(e:Event):void{
			log("Exiting application, cleaning up");
			Steamworks.dispose();
		}

		private function addButton(label:String, callback:Function):void {
			var button:Sprite = new Sprite();
			button.graphics.beginFill(0xaaaaaa);
			button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
			button.graphics.endFill();
			button.buttonMode = true;
			button.useHandCursor = true;
			button.addEventListener(MouseEvent.CLICK, callback);
			button.x = 5;
			button.y = _buttonPos;
			_buttonPos += button.height + 5;

			button.addEventListener(MouseEvent.MOUSE_OVER, function(e:MouseEvent):void {
				button.graphics.clear();
				button.graphics.beginFill(0xccccccc);
				button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
				button.graphics.endFill();
			});

			button.addEventListener(MouseEvent.MOUSE_OUT, function(e:MouseEvent):void {
				button.graphics.clear();
				button.graphics.beginFill(0xaaaaaa);
				button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
				button.graphics.endFill();
			});

			var text:TextField = new TextField();
			text.text = label;
			text.width = 140;
			text.height = 25;
			text.x = 5;
			text.y = 5;
			text.mouseEnabled = false;

			button.addChild(text);
			addChild(button);
		}
	}
}

/*
 *  FRESteamWorks.h
 *  This file is part of FRESteamWorks.
 *
 *  Created by David ´Oldes´ Oliva on 3/29/12.
 *  Contributors: Ventero <http://github.com/Ventero>
 *  Copyright (c) 2012 Amanita Design. All rights reserved.
 *  Copyright (c) 2012-2013 Level Up Labs, LLC. All rights reserved.
 */

package
{
	import com.amanitadesign.steam.FRESteamWorks;
	import com.amanitadesign.steam.SteamConstants;
	import com.amanitadesign.steam.SteamEvent;
	import com.amanitadesign.steam.WorkshopConstants;

	import flash.desktop.NativeApplication;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.display.StageDisplayState;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.ByteArray;

	public class FRESteamWorksTest extends Sprite
	{
		public var Steamworks:FRESteamWorks = new FRESteamWorks();
		public var tf:TextField;

		private var _buttonPos:int = 5;
		private var _appId:uint;

		public function FRESteamWorksTest()
		{
			tf = new TextField();
			tf.x = 160;
			tf.width = stage.stageWidth - tf.x;
			tf.height = stage.stageHeight;
			addChild(tf);

			addButton("Check stats/achievements", checkAchievements);
			addButton("Toggle achievement", toggleAchievement);
			addButton("Toggle cloud enabled", toggleCloudEnabled);
			addButton("Toggle file", toggleFile);
			addButton("Publish file", publishFile);
			addButton("Toggle fullscreen", toggleFullscreen);
			addButton("Show Friends overlay", activateOverlay);
			addButton("List subscribed files", enumerateSubscribedFiles);

			Steamworks.addEventListener(SteamEvent.STEAM_RESPONSE, onSteamResponse);
			NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit);
			try {
				//Steamworks.useCrashHandler(480, "1.0", "Feb 20 2013", "21:42:20");
				if(!Steamworks.init()){
					log("STEAMWORKS API is NOT available");
					return;
				}

				log("STEAMWORKS API is available\n");
				log("User ID: " + Steamworks.getUserID());
				_appId = Steamworks.getAppID();
				log("App ID: " + _appId);
				log("Persona name: " + Steamworks.getPersonaName());
				log("isCloudEnabledForApp() == "+Steamworks.isCloudEnabledForApp());
				log("getFileCount() == "+Steamworks.getFileCount());
				log("fileExists('test.txt') == "+Steamworks.fileExists('test.txt'));
				log("getDLCCount() == " + Steamworks.getDLCCount());

				Steamworks.resetAllStats(true);
			} catch(e:Error) {
				log("*** ERROR ***");
				log(e.message);
				log(e.getStackTrace());
			}

		}
		private function log(value:String):void{
			tf.appendText(value+"\n");
			tf.scrollV = tf.maxScrollV;
		}

		public function writeFileToCloud(fileName:String, data:String):Boolean {
			var dataOut:ByteArray = new ByteArray();
			dataOut.writeUTFBytes(data);
			return Steamworks.fileWrite(fileName, dataOut);
		}

		public function readFileFromCloud(fileName:String):String {
			var dataIn:ByteArray = new ByteArray();
			var result:String;
			dataIn.position = 0;
			dataIn.length = Steamworks.getFileSize(fileName);

			if(dataIn.length>0 && Steamworks.fileRead(fileName,dataIn)){
				result = dataIn.readUTFBytes(dataIn.length);
			}
			return result;
		}

		private function checkAchievements(e:Event = null):void {
			if(!Steamworks.isReady) return;

			// current stats and achievement ids are from steam example app
			log("isAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.isAchievement("ACH_WIN_ONE_GAME"));
			log("isAchievement('ACH_TRAVEL_FAR_SINGLE') == "+Steamworks.isAchievement("ACH_TRAVEL_FAR_SINGLE"));
			log("setStatFloat('FeetTraveled') == "+Steamworks.setStatFloat('FeetTraveled', 21.3));
			log("setStatInt('NumGames', 2) == "+Steamworks.setStatInt('NumGames', 2));
			Steamworks.storeStats();
			log("getStatInt('NumGames') == "+Steamworks.getStatInt('NumGames'));
			log("getStatFloat('FeetTraveled') == "+Steamworks.getStatFloat('FeetTraveled'));
		}

		private function toggleAchievement(e:Event = null):void{
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.isAchievement("ACH_WIN_ONE_GAME");
			log("isAchievement('ACH_WIN_ONE_GAME') == " + result);
			if(!result) {
				log("setAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.setAchievement("ACH_WIN_ONE_GAME"));
			} else {
				log("clearAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.clearAchievement("ACH_WIN_ONE_GAME"));
			}
		}

		private function toggleCloudEnabled(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var enabled:Boolean = Steamworks.isCloudEnabledForApp();
			log("isCloudEnabledForApp() == " + enabled);
			log("setCloudEnabledForApp(" + !enabled + ") == " + Steamworks.setCloudEnabledForApp(!enabled));
			log("isCloudEnabledForApp() == " + Steamworks.isCloudEnabledForApp());
		}

		private function toggleFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.fileExists('test.txt');
			log("fileExists('test.txt') == " + result);
			if(result){
				log("readFileFromCloud('test.txt') == "+readFileFromCloud('test.txt') );
				log("fileDelete('test.txt') == "+Steamworks.fileDelete('test.txt'));
			} else {
				log("writeFileToCloud('test.txt','click') == "+writeFileToCloud('test.txt','click'));
			}
		}

		private function publishFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var res:Boolean = Steamworks.publishWorkshopFile("test.txt", "", _appId,
				"Test.txt", "Test.txt", WorkshopConstants.VISIBILITY_Private,
				["TestTag"], WorkshopConstants.FILETYPE_Community);
			log("publishWorkshopFile('test.txt' ...) == " + res);
		}

		private function toggleFullscreen(e:Event = null):void {
			if(stage.displayState == StageDisplayState.NORMAL)
				stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
			else
				stage.displayState = StageDisplayState.NORMAL;
		}

		private function activateOverlay(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("isOverlayEnabled() == " + Steamworks.isOverlayEnabled());
			log("activateGameOverlay('Friends') == " + Steamworks.activateGameOverlay("Friends"));
			log("isOverlayEnabled() == " + Steamworks.isOverlayEnabled());
		}

		private function enumerateSubscribedFiles(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("enumerateUserSubscribedFiles(0) == " + Steamworks.enumerateUserSubscribedFiles(0));
		}

		private var id:String;
		private var handle:String;
		private function onSteamResponse(e:SteamEvent):void{
			var apiCall:Boolean;
			switch(e.req_type){
				case SteamConstants.RESPONSE_OnUserStatsStored:
					log("RESPONSE_OnUserStatsStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnUserStatsReceived:
					log("RESPONSE_OnUserStatsReceived: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnAchievementStored:
					log("RESPONSE_OnAchievementStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnPublishWorkshopFile:
					log("RESPONSE_OnPublishWorkshopFile: " + e.response);
					var file:String = Steamworks.publishWorkshopFileResult();
					log("File published as " + file);
					log("subscribePublishedFile(...) == " + Steamworks.subscribePublishedFile(file));
					break;
				case SteamConstants.RESPONSE_OnEnumerateUserSubscribedFiles:
					log("RESPONSE_OnEnumerateUserSubscribedFiles: " + e.response);
					var result:SubscribedFilesResult = Steamworks.enumerateUserSubscribedFilesResult();
					log("User subscribed files: " + result.resultsReturned + "/" + result.totalResults);
					for(var i:int = 0; i < result.resultsReturned; i++) {
						id = result.publishedFileId[i];
						apiCall = Steamworks.getPublishedFileDetails(result.publishedFileId[i]);
						log(i + ": " + result.publishedFileId[i] + " (" + result.timeSubscribed[i] + ") - " + apiCall);
					}
					break;
				case SteamConstants.RESPONSE_OnGetPublishedFileDetails:
					log("RESPONSE_OnGetPublishedFileDetails: " + e.response);
					var res:FileDetailsResult = Steamworks.getPublishedFileDetailsResult(id);
					log("Result for " + id + ": " + res);
					if(res) {
						log("File: " + res.fileName + ", handle: " + res.fileHandle);
						handle = res.fileHandle;
						apiCall = Steamworks.UGCDownload(res.fileHandle, 0);
						log("UGCDownload(...) == " + apiCall);
					}
					break;
				case SteamConstants.RESPONSE_OnUGCDownload:
					log("RESPONSE_OnUGCDownload: " + e.response);
					var ugcResult:DownloadUGCResult = Steamworks.getUGCDownloadResult(handle);
					log("Result for " + handle + ": " + ugcResult);
					if(ugcResult) {
						log("File: " + ugcResult.fileName + ", handle: " + ugcResult.fileHandle + ", size: " + ugcResult.size);
						var ba:ByteArray = new ByteArray();
						ba.length = ugcResult.size;
						apiCall = Steamworks.UGCRead(ugcResult.fileHandle, ba.length, 0, ba);
						log("UGCRead(...) == " + apiCall);
						if(apiCall) {
							log("Result length: " + ba.position + "//" + ba.length);
							log("Result: " + ba.readUTFBytes(ugcResult.size));
						}
						break;
					}
				default:
					log("STEAMresponse type:"+e.req_type+" response:"+e.response);
			}
		}

		private function onExit(e:Event):void{
			log("Exiting application, cleaning up");
			Steamworks.dispose();
		}

		private function addButton(label:String, callback:Function):void {
			var button:Sprite = new Sprite();
			button.graphics.beginFill(0xaaaaaa);
			button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
			button.graphics.endFill();
			button.buttonMode = true;
			button.useHandCursor = true;
			button.addEventListener(MouseEvent.CLICK, callback);
			button.x = 5;
			button.y = _buttonPos;
			_buttonPos += button.height + 5;

			button.addEventListener(MouseEvent.MOUSE_OVER, function(e:MouseEvent):void {
				button.graphics.clear();
				button.graphics.beginFill(0xccccccc);
				button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
				button.graphics.endFill();
			});

			button.addEventListener(MouseEvent.MOUSE_OUT, function(e:MouseEvent):void {
				button.graphics.clear();
				button.graphics.beginFill(0xaaaaaa);
				button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
				button.graphics.endFill();
			});

			var text:TextField = new TextField();
			text.text = label;
			text.width = 140;
			text.height = 25;
			text.x = 5;
			text.y = 5;
			text.mouseEnabled = false;

			button.addChild(text);
			addChild(button);
		}
	}
}

Add UGCDownload/UGCRead tests

Add UGCDownload/UGCRead tests


//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

/**
 * Equivalent to java.lang.Integer.
 */
// Unfortunately, I think this is necessary.
// I was going to remove this class and just make the streaming stuff
// autotranslate between int <--> java.lang.Integer and
// Number <--> java.lang.Double. However, a Number object that refers
// to an integer value is actually an int. Yes, it's totally fucked.
public class Integer
    implements Equalable, Boxed
{
    public static function valueOf (val :int) :Integer
    {
        return new Integer(val);
    }

    /**
     * Access the immutable value.
     */
    public function get value () :int
    {
        return _value;
    }

    /**
     * Constructor.
     */
    public function Integer (intValue :int)
    {
        _value = intValue;
    }

    // from Equalable
    public function equals (other :Object) :Boolean
    {
        return (other is Integer) && (_value === (other as Integer).value);
    }

    // from Boxed
    public function unbox () :Object
    {
        return _value;
    }

    // cannot use the override keyword on toString() because actionscript is stupid
    public function toString () :String
    {
        return _value.toString();
    }

    /**
     * Compares to int values in an overflow safe manner.
     */
    public static function compare (val1 :int, val2 :int) :int
    {
        return (val1 > val2) ? 1 : (val1 == val2 ? 0 : -1);
    }

    protected var _value :int;

}
}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

/**
 * Equivalent to java.lang.Integer.
 */
// Unfortunately, I think this is necessary.
// I was going to remove this class and just make the streaming stuff
// autotranslate between int <--> java.lang.Integer and
// Number <--> java.lang.Double. However, a Number object that refers
// to an integer value is actually an int. Yes, it's totally fucked.
public class Integer
    implements Equalable, Boxed
{
    public static function valueOf (val :int) :Integer
    {
        return new Integer(val);
    }

    /**
     * Access the immutable value.
     */
    public function get value () :int
    {
        return _value;
    }

    /**
     * Constructor.
     */
    public function Integer (intValue :int)
    {
        _value = intValue;
    }

    // from Equalable
    public function equals (other :Object) :Boolean
    {
        return (other is Integer) && (_value === (other as Integer).value);
    }

    // from Boxed
    public function unbox () :Object
    {
        return _value;
    }

    // cannot use the override keyword on toString() because actionscript is stupid
    public function toString () :String
    {
        return _value.toString();
    }

    /**
     * Compares two int values in an overflow safe manner.
     */
    public static function compare (val1 :int, val2 :int) :int
    {
        return (val1 > val2) ? 1 : (val1 == val2 ? 0 : -1);
    }

    protected var _value :int;

}
}

Fix typo.

Fix typo.

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@5754 542714f4-19e9-0310-aa3c-eee0fc999fb1


package com.kaltura.kdpfl.plugin.component
{
	import com.kaltura.kdpfl.model.ConfigProxy;
	import com.kaltura.kdpfl.model.MediaProxy;
	import com.kaltura.kdpfl.model.SequenceProxy;
	import com.kaltura.kdpfl.model.type.EnableType;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.types.KalturaMediaType;
	import com.kaltura.vo.KalturaMediaEntry;

	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.TimerEvent;
	import flash.net.URLRequest;
	import flash.utils.Timer;

	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.patterns.mediator.Mediator;

	public class BitrateDetectionMediator extends Mediator
	{
		public static var NAME:String = "bitrateDetectionMediator";
		/**
		 * Flag indicating autoPlay flashvar was true
		 */
		private var _wasAutoPlay:Boolean = false;
		/**
		 * Flag indicating singleAutoPlay was true
		 */
		private var _wasSingleAutoPlay:Boolean = false;
		/**
		 * The url loader
		 */
		private var _loader:Loader;
		/**
		 * average download speed
		 */
		private var _avgSpeed:Number = 0;
		/**
		 * counter for progress events, will be used to calculate average speed
		 */
		private var _progressCount:int;
		/**
		 * timer for the download process
		 */
		private var _downloadTimer:Timer;
		/**
		 * startTime of the download process
		 */
		private var _startTime:Number;
		/**
		 * Indicating if player has already played
		 */
		private var _playedPlayed:Boolean = false;

		private var _configProxy:ConfigProxy;

		private var _prevTime:Number;
		private var _prevBytesLoaded:int;
		private var _forceBitrate:int;
		private var _viewComp:bitrateDetectionPluginCode;

		public function BitrateDetectionMediator(viewComponentObject:Object = null , forceBitrate:int = 0)
		{
			_forceBitrate = forceBitrate;
			_viewComp = viewComponentObject as bitrateDetectionPluginCode;
			super(NAME, viewComponentObject);
		}

		override public function listNotificationInterests():Array
		{
			return [
				NotificationType.DO_SWITCH,
				NotificationType.KDP_EMPTY,
				NotificationType.KDP_READY,
				NotificationType.PLAYER_PLAYED,
				NotificationType.MEDIA_READY
				];
		}

		private var _bandwidth:Number = 0;
		private var _bandwidthByUser:Number = 0;

		override public function handleNotification(notification:INotification):void
		{
			switch (notification.getName())
			{
				//in case the user switched the flavor manually
				case NotificationType.DO_SWITCH:
					_bandwidthByUser = int(notification.getBody())
					break;
				case NotificationType.MEDIA_READY:
					if(_bandwidthByUser)
					{
						sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: _bandwidthByUser});
						return;
					}
					if(_bandwidth)
						sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: _bandwidth});
					break;
				case NotificationType.KDP_EMPTY:
				case NotificationType.KDP_READY:
					var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
					if (!mediaProxy.vo.entry ||
						((mediaProxy.vo.entry is KalturaMediaEntry) && (int(mediaProxy.vo.entry.mediaType)==KalturaMediaType.IMAGE)))
						return;

					if (_viewComp.runPreCheck)
					{
						if (mediaProxy.vo.kalturaMediaFlavorArray && mediaProxy.vo.kalturaMediaFlavorArray.length)
						{
							var highBR:int = mediaProxy.vo.kalturaMediaFlavorArray[mediaProxy.vo.kalturaMediaFlavorArray.length - 1].bitrate;
							if ((highBR <= mediaProxy.vo.preferedFlavorBR) || (highBR - mediaProxy.vo.preferedFlavorBR)<=(highBR * 0.2))
							{
								trace ("***** no BW CHECK***");
								//// if the last preferred bitrate is higher or equals (with 20% error range) to the current highest bitrate,
								//no need to perform BW check
								return;
							}
						}
					}
					_configProxy = facade.retrieveProxy(ConfigProxy.NAME) as ConfigProxy;
					startDownload();
					break;
				case NotificationType.PLAYER_PLAYED:
					if(_bandwidth)
						return;
					_playedPlayed = true;
					break;
			}
		}

		/**
		 * Start a download process to find the preferred bitrate
		 *
		 */
		public function startDownload() : void
		{
			trace ("bitrate detection: start download");
			if (!_viewComp.downloadUrl)
				return;

			var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
			//disable autoPlay - will change it back once the bitrate detection will finish
			if (_configProxy.vo.flashvars.autoPlay == "true")
			{
				trace ("bitrate detection: was auto play");
				_configProxy.vo.flashvars.autoPlay = "false";
				_wasAutoPlay = true;
			}
			if (mediaProxy.vo.singleAutoPlay)
			{
				mediaProxy.vo.singleAutoPlay = false;
				_wasSingleAutoPlay = true;
			}
			_startTime = ( new Date( ) ).getTime( );
			_progressCount = 0;
			_prevTime = _startTime;
			_prevBytesLoaded = 0;
			_loader = new Loader( );
			_loader.contentLoaderInfo.addEventListener( Event.COMPLETE, downloadComplete );
			_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
			_loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
			_downloadTimer = new Timer (_viewComp.downloadTimeoutMS, 1);
			_downloadTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
			sendNotification(NotificationType.ENABLE_GUI, {guiEnabled: false, enableType: EnableType.FULL});
			sendNotification( NotificationType.SWITCHING_CHANGE_STARTED, {newIndex: -2,  newBitrate: null});
			_loader.load( new URLRequest( _viewComp.downloadUrl + "?" + ( Math.random( ) * 100000 )) );
			_downloadTimer.start();
		}

		/**
		 * on download progress- calculate average speed
		 * @param event
		 *
		 */
		private function onProgress (event:ProgressEvent):void
		{
			var curTime:Number = ( new Date( ) ).getTime( );
			_progressCount++;
			if (event.bytesLoaded!=0)
			{
				var totalTime:Number =( curTime - _startTime ) / 1000;
				var totalKB:Number = event.bytesLoaded / 1024;
				_avgSpeed = totalKB / totalTime;
			}
			_prevTime = curTime;
			_prevBytesLoaded = event.bytesLoaded;
		}

		/**
		 *  download complete handler - set the preferred bitrate, enable GUI
		 *
		 */
		private function downloadComplete( e:Event = null):void
		{
			_downloadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
			_loader.contentLoaderInfo.removeEventListener( ProgressEvent.PROGRESS, onProgress );

			var bitrateVal:int = _avgSpeed * 8;
			trace("*** preferred bitrate for bitrate detection plugin:", bitrateVal);

			//for debugging - force a bitrate value to override the calculated one
			if(_forceBitrate > 0)
			{
				bitrateVal = _forceBitrate;
			}

			sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: bitrateVal});

			_bandwidth = bitrateVal;

			finishDownloadProcess();
		}

		/**
		 * default timer time has passed, stop listening to progress event and continue the flow
		 * @param event
		 *
		 */
		private function onTimerComplete (event:TimerEvent):void {
			downloadComplete();
		}

		/**
		 * I/O error getting the sample file, release the UI
		 * @param event
		 *
		 */
		private function ioErrorHandler(event:IOErrorEvent) : void
		{
			//Bypass: ignore #2124 error (loaded file is an unknown type)
			if (!event.text || event.text.indexOf("Error #2124")==-1)
			{
				trace ("bitrate detection i/o error:", event.text);
				finishDownloadProcess();
			}
		}

		/**
		 * enable back the GUI.
		 * call Do_Play if we were in auto play and set back relevant flashvars
		 *
		 */
		private function finishDownloadProcess():void {
			//if we changed variables, set them back
			if (_wasAutoPlay || _wasSingleAutoPlay) {
				var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
				var sequenceProxy:SequenceProxy = facade.retrieveProxy(SequenceProxy.NAME) as SequenceProxy;
				if (_wasAutoPlay)
				{
					_configProxy.vo.flashvars.autoPlay = "true";
				}
				else
				{
					mediaProxy.vo.singleAutoPlay = true;
				}
				//play if we should have played
				if (!_playedPlayed && !mediaProxy.vo.isMediaDisabled && !sequenceProxy.vo.isInSequence)
				{
					sendNotification(NotificationType.DO_PLAY);
				}
			}

			sendNotification(NotificationType.ENABLE_GUI, {guiEnabled: true, enableType: EnableType.FULL});
		}

	}

}

package com.kaltura.kdpfl.plugin.component
{
	import com.kaltura.kdpfl.model.ConfigProxy;
	import com.kaltura.kdpfl.model.MediaProxy;
	import com.kaltura.kdpfl.model.SequenceProxy;
	import com.kaltura.kdpfl.model.type.EnableType;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.types.KalturaMediaType;
	import com.kaltura.vo.KalturaMediaEntry;

	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.TimerEvent;
	import flash.net.URLRequest;
	import flash.utils.Timer;

	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.patterns.mediator.Mediator;

	public class BitrateDetectionMediator extends Mediator
	{
		public static var NAME:String = "bitrateDetectionMediator";
		/**
		 * Flag indicating autoPlay flashvar was true
		 */
		private var _wasAutoPlay:Boolean = false;
		/**
		 * Flag indicating singleAutoPlay was true
		 */
		private var _wasSingleAutoPlay:Boolean = false;
		/**
		 * The url loader
		 */
		private var _loader:Loader;
		/**
		 * average download speed
		 */
		private var _avgSpeed:Number = 0;
		/**
		 * counter for progress events, will be used to calculate average speed
		 */
		private var _progressCount:int;
		/**
		 * timer for the download process
		 */
		private var _downloadTimer:Timer;
		/**
		 * startTime of the download process
		 */
		private var _startTime:Number;
		/**
		 * Indicating if player has already played
		 */
		private var _playedPlayed:Boolean = false;

		private var _configProxy:ConfigProxy;

		private var _prevTime:Number;
		private var _prevBytesLoaded:int;
		private var _forceBitrate:int;
		private var _viewComp:bitrateDetectionPluginCode;

		public function BitrateDetectionMediator(viewComponentObject:Object = null , forceBitrate:int = 0)
		{
			_forceBitrate = forceBitrate;
			_viewComp = viewComponentObject as bitrateDetectionPluginCode;
			super(NAME, viewComponentObject);
		}

		override public function listNotificationInterests():Array
		{
			return [
				NotificationType.DO_SWITCH,
				NotificationType.KDP_EMPTY,
				NotificationType.KDP_READY,
				NotificationType.PLAYER_PLAYED,
				NotificationType.MEDIA_READY
				];
		}

		private var _bandwidth:Number = 0;
		private var _bandwidthByUser:Number = 0;

		override public function handleNotification(notification:INotification):void
		{
			switch (notification.getName())
			{
				//in case the user switched the flavor manually
				case NotificationType.DO_SWITCH:
					_bandwidthByUser = int(notification.getBody())
					break;
				case NotificationType.MEDIA_READY:
					if(_bandwidthByUser)
					{
						sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: _bandwidthByUser});
						return;
					}
					if(_bandwidth)
						sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: _bandwidth});
					break;
				case NotificationType.KDP_EMPTY:
				case NotificationType.KDP_READY:
					var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
					if (!mediaProxy.vo.entry ||
						((mediaProxy.vo.entry is KalturaMediaEntry) && (int(mediaProxy.vo.entry.mediaType)==KalturaMediaType.IMAGE)))
						return;

					if (_viewComp.runPreCheck)
					{
						if (mediaProxy.vo.kalturaMediaFlavorArray && mediaProxy.vo.kalturaMediaFlavorArray.length)
						{
							var highBR:int = mediaProxy.vo.kalturaMediaFlavorArray[mediaProxy.vo.kalturaMediaFlavorArray.length - 1].bitrate;
							if (0.8 * highBR <= mediaProxy.vo.preferedFlavorBR)
							{
								trace ("***** no BW CHECK***");
								//// if the last preferred bitrate is higher or equals (with 20% error range) to the current highest bitrate,
								//no need to perform BW check
								return;
							}
						}
					}
					_configProxy = facade.retrieveProxy(ConfigProxy.NAME) as ConfigProxy;
					startDownload();
					break;
				case NotificationType.PLAYER_PLAYED:
					if(_bandwidth)
						return;
					_playedPlayed = true;
					break;
			}
		}

		/**
		 * Start a download process to find the preferred bitrate
		 *
		 */
		public function startDownload() : void
		{
			trace ("bitrate detection: start download");
			if (!_viewComp.downloadUrl)
				return;

			var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
			//disable autoPlay - will change it back once the bitrate detection will finish
			if (_configProxy.vo.flashvars.autoPlay == "true")
			{
				trace ("bitrate detection: was auto play");
				_configProxy.vo.flashvars.autoPlay = "false";
				_wasAutoPlay = true;
			}
			if (mediaProxy.vo.singleAutoPlay)
			{
				mediaProxy.vo.singleAutoPlay = false;
				_wasSingleAutoPlay = true;
			}
			_startTime = ( new Date( ) ).getTime( );
			_progressCount = 0;
			_prevTime = _startTime;
			_prevBytesLoaded = 0;
			_loader = new Loader( );
			_loader.contentLoaderInfo.addEventListener( Event.COMPLETE, downloadComplete );
			_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
			_loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
			_downloadTimer = new Timer (_viewComp.downloadTimeoutMS, 1);
			_downloadTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
			sendNotification(NotificationType.ENABLE_GUI, {guiEnabled: false, enableType: EnableType.FULL});
			sendNotification( NotificationType.SWITCHING_CHANGE_STARTED, {newIndex: -2,  newBitrate: null});
			_loader.load( new URLRequest( _viewComp.downloadUrl + "?" + ( Math.random( ) * 100000 )) );
			_downloadTimer.start();
		}

		/**
		 * on download progress- calculate average speed
		 * @param event
		 *
		 */
		private function onProgress (event:ProgressEvent):void
		{
			var curTime:Number = ( new Date( ) ).getTime( );
			_progressCount++;
			if (event.bytesLoaded!=0)
			{
				var totalTime:Number =( curTime - _startTime ) / 1000;
				var totalKB:Number = event.bytesLoaded / 1024;
				_avgSpeed = totalKB / totalTime;
			}
			_prevTime = curTime;
			_prevBytesLoaded = event.bytesLoaded;
		}

		/**
		 *  download complete handler - set the preferred bitrate, enable GUI
		 *
		 */
		private function downloadComplete( e:Event = null):void
		{
			_downloadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
			_loader.contentLoaderInfo.removeEventListener( ProgressEvent.PROGRESS, onProgress );

			var bitrateVal:int = _avgSpeed * 8;
			trace("*** preferred bitrate for bitrate detection plugin:", bitrateVal);

			//for debugging - force a bitrate value to override the calculated one
			if(_forceBitrate > 0)
			{
				bitrateVal = _forceBitrate;
			}

			sendNotification(NotificationType.CHANGE_PREFERRED_BITRATE, {bitrate: bitrateVal});

			_bandwidth = bitrateVal;

			finishDownloadProcess();
		}

		/**
		 * default timer time has passed, stop listening to progress event and continue the flow
		 * @param event
		 *
		 */
		private function onTimerComplete (event:TimerEvent):void {
			downloadComplete();
		}

		/**
		 * I/O error getting the sample file, release the UI
		 * @param event
		 *
		 */
		private function ioErrorHandler(event:IOErrorEvent) : void
		{
			//Bypass: ignore #2124 error (loaded file is an unknown type)
			if (!event.text || event.text.indexOf("Error #2124")==-1)
			{
				trace ("bitrate detection i/o error:", event.text);
				finishDownloadProcess();
			}
		}

		/**
		 * enable back the GUI.
		 * call Do_Play if we were in auto play and set back relevant flashvars
		 *
		 */
		private function finishDownloadProcess():void {
			//if we changed variables, set them back
			if (_wasAutoPlay || _wasSingleAutoPlay) {
				var mediaProxy:MediaProxy = facade.retrieveProxy(MediaProxy.NAME) as MediaProxy;
				var sequenceProxy:SequenceProxy = facade.retrieveProxy(SequenceProxy.NAME) as SequenceProxy;
				if (_wasAutoPlay)
				{
					_configProxy.vo.flashvars.autoPlay = "true";
				}
				else
				{
					mediaProxy.vo.singleAutoPlay = true;
				}
				//play if we should have played
				if (!_playedPlayed && !mediaProxy.vo.isMediaDisabled && !sequenceProxy.vo.isInSequence)
				{
					sendNotification(NotificationType.DO_PLAY);
				}
			}

			sendNotification(NotificationType.ENABLE_GUI, {guiEnabled: true, enableType: EnableType.FULL});
		}

	}

}

fix condition

qnd: fix condition

git-svn-id: 3f608e5a9a704dd448217c0a64c508e7f145cfa1@91100 6b8eccd3-e8c5-4e7d-8186-e12b5326b719


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

class menuColouridiomes extends MoviieClip

	{
		public var cat, es, en ,fr:MovieClip;
		public var arrayLang_arr:Array = new Array ("cat", "es", "en", "fr");

		function menuColourIdiomes ():Void

		{

		this.onRollOver = this.mOver;
		this.onRollOut = this.mOut;
		this.onRelease =this.mOver;

		}

		private function mOver():Void

		{

                arrayLang_arr[i].colorTo("#95C60D", 0.3, "easeOutCirc");

		}

		private function mOut():Void

		{

                arrayLang_arr[i].colorTo("#010101", 0.3, "easeInCirc");

		}

	}

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

class menuColouridiomes extends MoviieClip

	{
		public var cat, es, en ,fr:MovieClip;
		public var arrayLang_arr:Array = new Array ("cat", "es", "en", "fr");

		function menuColourIdiomes ():Void

		{

		this.onRollOver = this.mOver;
		this.onRollOut = this.mOut;
		this.onRelease =this.mOver;

		}

		private function mOver()

		{

                arrayLang_arr[i].colorTo("#95C60D", 0.3, "easeOutCirc");

		}

		private function mOut()

		{

                arrayLang_arr[i].colorTo("#010101", 0.3, "easeInCirc");

		}

	}

Update menuColourIdiomes.as

Update menuColourIdiomes.as


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You 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 org.apache.flex.html.beads.controllers
{
	import flash.display.DisplayObject;
	import flash.geom.Point;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IBeadController;
	import org.apache.flex.core.ISelectionModel;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.html.beads.IDropDownListView;

    /**
     *  The DropDownListController class is the controller for
     *  org.apache.flex.html.DropDownList.  Controllers
     *  watch for events from the interactive portions of a View and
     *  update the data model or dispatch a semantic event.
     *  This controller watches for the click event and displays the
     *  dropdown/popup, and watches the dropdown/popup for change events
     *  and updates the selection model accordingly.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class DropDownListController implements IBead, IBeadController
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function DropDownListController()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("click", clickHandler);
		}

        private function clickHandler(event:Event):void
        {
            var viewBead:IDropDownListView = _strand.getBeadByType(IDropDownListView) as IDropDownListView;
            viewBead.popUpVisible = true;
            var selectionModel:ISelectionModel = _strand.getBeadByType(ISelectionModel) as ISelectionModel;
            var popUpModel:ISelectionModel = viewBead.popUp.getBeadByType(ISelectionModel) as ISelectionModel;
            popUpModel.dataProvider = selectionModel.dataProvider;
            popUpModel.selectedIndex = selectionModel.selectedIndex;
			DisplayObject(viewBead.popUp).width = DisplayObject(_strand).width;
            var pt:Point = new Point(DisplayObject(_strand).x, DisplayObject(_strand).y + DisplayObject(_strand).height);
            pt = DisplayObject(_strand).parent.localToGlobal(pt);
			DisplayObject(viewBead.popUp).x = pt.x;
			DisplayObject(viewBead.popUp).y = pt.y;
            IEventDispatcher(viewBead.popUp).addEventListener("change", changeHandler);
        }

        private function changeHandler(event:Event):void
        {
            var viewBead:IDropDownListView = _strand.getBeadByType(IDropDownListView) as IDropDownListView;
            viewBead.popUpVisible = false;
            var selectionModel:ISelectionModel = _strand.getBeadByType(ISelectionModel) as ISelectionModel;
            var popUpModel:ISelectionModel = viewBead.popUp.getBeadByType(ISelectionModel) as ISelectionModel;
            selectionModel.selectedIndex = popUpModel.selectedIndex;
			IEventDispatcher(_strand).dispatchEvent(new Event("change"));
        }

	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You 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 org.apache.flex.html.beads.controllers
{
	import flash.display.DisplayObject;
	import flash.geom.Point;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IBeadController;
	import org.apache.flex.core.ISelectionModel;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.html.beads.IDropDownListView;

    /**
     *  The DropDownListController class is the controller for
     *  org.apache.flex.html.DropDownList.  Controllers
     *  watch for events from the interactive portions of a View and
     *  update the data model or dispatch a semantic event.
     *  This controller watches for the click event and displays the
     *  dropdown/popup, and watches the dropdown/popup for change events
     *  and updates the selection model accordingly.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class DropDownListController implements IBead, IBeadController
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function DropDownListController()
		{
		}

		private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
			_strand = value;
            IEventDispatcher(value).addEventListener("click", clickHandler);
		}

        private function clickHandler(event:Event):void
        {
            var viewBead:IDropDownListView = _strand.getBeadByType(IDropDownListView) as IDropDownListView;
            viewBead.popUpVisible = true;
            var selectionModel:ISelectionModel = _strand.getBeadByType(ISelectionModel) as ISelectionModel;
            var popUpModel:ISelectionModel = viewBead.popUp.getBeadByType(ISelectionModel) as ISelectionModel;
            DisplayObject(viewBead.popUp).width = DisplayObject(_strand).width;
            popUpModel.dataProvider = selectionModel.dataProvider;
            popUpModel.selectedIndex = selectionModel.selectedIndex;
            var pt:Point = new Point(DisplayObject(_strand).x, DisplayObject(_strand).y + DisplayObject(_strand).height);
            pt = DisplayObject(_strand).parent.localToGlobal(pt);
			DisplayObject(viewBead.popUp).x = pt.x;
			DisplayObject(viewBead.popUp).y = pt.y;
            IEventDispatcher(viewBead.popUp).addEventListener("change", changeHandler);
        }

        private function changeHandler(event:Event):void
        {
            var viewBead:IDropDownListView = _strand.getBeadByType(IDropDownListView) as IDropDownListView;
            viewBead.popUpVisible = false;
            var selectionModel:ISelectionModel = _strand.getBeadByType(ISelectionModel) as ISelectionModel;
            var popUpModel:ISelectionModel = viewBead.popUp.getBeadByType(ISelectionModel) as ISelectionModel;
            selectionModel.selectedIndex = popUpModel.selectedIndex;
			IEventDispatcher(_strand).dispatchEvent(new Event("change"));
        }

	}
}

fix sizing of dropdownlist popup

fix sizing of dropdownlist popup


package aerys.minko.scene.controller
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.Signal;

	import flash.display.BitmapData;
	import flash.utils.getQualifiedClassName;

	/**
	 * Controllers work on scene nodes to modify and update them. They offer the best
	 * way to add any kind of behavior on one or multiple scene nodes. They can be used
	 * to create animations, add physics, artificial intelligence...
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class AbstractController
	{
		private var _targetType		: Class					= null;
		private var _targets		: Vector.<ISceneNode>	= new <ISceneNode>[];

		private var _targetAdded	: Signal				= new Signal('AbstractController.targetAdded');
		private var _targetRemoved	: Signal				= new Signal('AbstractController.targetRemoved');

		/**
		 * The number of scene nodes targeted by this very controller.
		 * @return
		 *
		 */
		public function get numTargets() : uint
		{
			return _targets.length;
		}

		/**
		 * The signal executed when a target is added to the controller.
		 * @return
		 *
		 */
		public function get targetAdded() : Signal
		{
			return _targetAdded;
		}

		/**
		 * The signal executed when a target is removed from the controller.
		 * @return
		 *
		 */
		public function get targetRemoved() : Signal
		{
			return _targetRemoved;
		}

		public function AbstractController(targetType 		: Class 	= null)
		{
			_targetType = targetType || ISceneNode;
		}

		/**
		 * Add a target to the controller.
		 * @param target
		 *
		 */
		minko_scene function addTarget(target : ISceneNode) : void
		{
			if (_targetType && !(target is _targetType))
			{
				throw new Error(
					"Controller '" + getQualifiedClassName(this)
					+ " cannot target objects from class '"
					+ getQualifiedClassName(target) + "'."
				);
			}

			_targets.push(target);

			_targetAdded.execute(this, target);
		}

		/**
		 * Remove a target from the controller.
		 * @param target
		 *
		 */
		minko_scene function removeTarget(target : ISceneNode) : void
		{
			var index : int = _targets.indexOf(target);
			var numTargets : int = _targets.length - 1;

			if (index < 0)
				throw new Error();

			_targets[index] = _targets[numTargets];
			_targets.length = numTargets;

			_targetRemoved.execute(this, target);
		}

		/**
		 * Get the target at the specified index.
		 * @param index
		 * @return
		 *
		 */
		public function getTarget(index : uint) : ISceneNode
		{
			return _targets[index];
		}

		public function clone() : AbstractController
		{
			throw new Error("The method AbstractController.clone() must be overriden.");
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.Signal;

	import flash.display.BitmapData;
	import flash.utils.getQualifiedClassName;

	/**
	 * Controllers work on scene nodes to modify and update them. They offer the best
	 * way to add any kind of behavior on one or multiple scene nodes. They can be used
	 * to create animations, add physics, artificial intelligence...
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class AbstractController
	{
		private var _targetType		: Class					= null;
		private var _targets		: Vector.<ISceneNode>	= new <ISceneNode>[];

		private var _targetAdded	: Signal				= new Signal('AbstractController.targetAdded');
		private var _targetRemoved	: Signal				= new Signal('AbstractController.targetRemoved');

		/**
		 * The number of scene nodes targeted by this very controller.
		 * @return
		 *
		 */
		public function get numTargets() : uint
		{
			return _targets.length;
		}

		/**
		 * The signal executed when a target is added to the controller.
		 * @return
		 *
		 */
		public function get targetAdded() : Signal
		{
			return _targetAdded;
		}

		/**
		 * The signal executed when a target is removed from the controller.
		 * @return
		 *
		 */
		public function get targetRemoved() : Signal
		{
			return _targetRemoved;
		}

		public function AbstractController(targetType 		: Class 	= null)
		{
			_targetType = targetType || ISceneNode;
		}

		/**
		 * Add a target to the controller.
		 * @param target
		 *
		 */
		minko_scene function addTarget(target : ISceneNode) : void
		{
			if (_targetType && !(target is _targetType))
			{
				throw new Error(
					'Controller \'' + getQualifiedClassName(this)
					+ ' cannot target objects from class \''
					+ getQualifiedClassName(target) + '\'.'
				);
			}

			_targets.push(target);

			_targetAdded.execute(this, target);
		}

		/**
		 * Remove a target from the controller.
		 * @param target
		 *
		 */
		minko_scene function removeTarget(target : ISceneNode) : void
		{
			var index : int = _targets.indexOf(target);
			var numTargets : int = _targets.length - 1;

			if (index < 0)
				throw new Error();

			_targets[index] = _targets[numTargets];
			_targets.length = numTargets;

			_targetRemoved.execute(this, target);
		}

		/**
		 * Get the target at the specified index.
		 * @param index
		 * @return
		 *
		 */
		public function getTarget(index : uint) : ISceneNode
		{
			return _targets[index];
		}

		public function clone() : AbstractController
		{
			throw new Error('The method AbstractController.clone() must be overriden.');
		}
	}
}

fix minor coding style issue in AbstractController


package aerys.minko.scene.controller.camera
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.CameraDataProvider;
	import aerys.minko.scene.node.Camera;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.data.DataBindings;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	public final class CameraController extends AbstractController
	{
		private var _camera		: Camera	= null;

		public function CameraController()
		{
			super(Camera);

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

		private function targetAddedHandler(controller	: CameraController,
											target		: Camera) : void
		{
			if (_camera != null)
				throw new Error();

			_camera = target;
			_camera.addedToScene.add(addedToSceneHandler);
			_camera.removedFromScene.add(removedFromSceneHandler);
			_camera.worldToLocal.changed.add(worldToLocalChangedHandler);
		}

		private function targetRemovedHandler(controller	: CameraController,
											  target		: Camera) : void
		{
			_camera.addedToScene.remove(addedToSceneHandler);
			_camera.removedFromScene.remove(removedFromSceneHandler);
			_camera.worldToLocal.changed.remove(worldToLocalChangedHandler);
			_camera = null;
		}

		private function addedToSceneHandler(camera : Camera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			sceneBindings.addProvider(camera.cameraData);
			sceneBindings.addCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.addCallback('viewportHeight', viewportSizeChanged);

			updateProjection();
		}

		private function removedFromSceneHandler(camera : Camera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			sceneBindings.removeProvider(camera.cameraData);
			sceneBindings.removeCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.removeCallback('viewportHeight', viewportSizeChanged);
		}

		private function worldToLocalChangedHandler(worldToLocal : Matrix4x4, propertyName : String) : void
		{
			var cameraData	: CameraDataProvider	= _camera.cameraData;

			cameraData.worldToScreen.lock()
				.copyFrom(_camera.worldToLocal)
				.append(cameraData.projection)
				.unlock();

			cameraData.screenToWorld.lock()
				.copyFrom(cameraData.screenToView)
				.append(_camera.localToWorld)
				.unlock();
		}

		private function viewportSizeChanged(bindings : DataBindings, key : String, newValue : Object) : void
		{
			updateProjection();
		}

		private function updateProjection() : void
		{
			var cameraData		: CameraDataProvider	= _camera.cameraData;
			var screenToView	: Matrix4x4				= cameraData.screenToView;
			var sceneBindings	: DataBindings			= Scene(_camera.root).bindings;
			var viewportWidth	: Number				= sceneBindings.getProperty('viewportWidth');
			var viewportHeight	: Number				= sceneBindings.getProperty('viewportHeight');
			var ratio			: Number				= viewportWidth / viewportHeight;

			cameraData.projection.perspectiveFoV(cameraData.fieldOfView, ratio, cameraData.zNear, cameraData.zFar);

			screenToView.lock()
				.copyFrom(cameraData.projection)
				.invert()
				.unlock();

			cameraData.screenToWorld.lock()
				.copyFrom(cameraData.screenToView)
				.append(_camera.localToWorld)
				.unlock();

			cameraData.worldToScreen.lock()
				.copyFrom(_camera.worldToLocal)
				.append(cameraData.projection)
				.unlock();
		}
	}
}

package aerys.minko.scene.controller.camera
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.CameraDataProvider;
	import aerys.minko.scene.node.Camera;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.data.DataBindings;
	import aerys.minko.type.data.IDataProvider;
	import aerys.minko.type.math.Matrix4x4;

	public final class CameraController extends AbstractController
	{
		private var _camera		: Camera	= null;

		public function CameraController()
		{
			super(Camera);

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

		private function targetAddedHandler(controller	: CameraController,
											target		: Camera) : void
		{
			if (_camera != null)
				throw new Error();

			_camera = target;
			_camera.addedToScene.add(addedToSceneHandler);
			_camera.removedFromScene.add(removedFromSceneHandler);
			_camera.worldToLocal.changed.add(worldToLocalChangedHandler);
		}

		private function targetRemovedHandler(controller	: CameraController,
											  target		: Camera) : void
		{
			_camera.addedToScene.remove(addedToSceneHandler);
			_camera.removedFromScene.remove(removedFromSceneHandler);
			_camera.worldToLocal.changed.remove(worldToLocalChangedHandler);
			_camera = null;
		}

		private function addedToSceneHandler(camera : Camera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			sceneBindings.addProvider(camera.cameraData);
			sceneBindings.addCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.addCallback('viewportHeight', viewportSizeChanged);
			camera.cameraData.changed.add(cameraPropertyChangedHandler);

			updateProjection();
		}

		private function removedFromSceneHandler(camera : Camera, scene : Scene) : void
		{
			var sceneBindings : DataBindings = scene.bindings;

			sceneBindings.removeProvider(camera.cameraData);
			sceneBindings.removeCallback('viewportWidth', viewportSizeChanged);
			sceneBindings.removeCallback('viewportHeight', viewportSizeChanged);
			camera.cameraData.changed.remove(cameraPropertyChangedHandler);
		}

		private function worldToLocalChangedHandler(worldToLocal : Matrix4x4, propertyName : String) : void
		{
			var cameraData	: CameraDataProvider	= _camera.cameraData;

			cameraData.worldToScreen.lock()
				.copyFrom(_camera.worldToLocal)
				.append(cameraData.projection)
				.unlock();

			cameraData.screenToWorld.lock()
				.copyFrom(cameraData.screenToView)
				.append(_camera.localToWorld)
				.unlock();
		}

		private function viewportSizeChanged(bindings : DataBindings, key : String, newValue : Object) : void
		{
			updateProjection();
		}

		private function cameraPropertyChangedHandler(provider : IDataProvider, property : String) : void
		{
			updateProjection();
		}

		private function updateProjection() : void
		{
			var cameraData		: CameraDataProvider	= _camera.cameraData;
			var screenToView	: Matrix4x4				= cameraData.screenToView;
			var sceneBindings	: DataBindings			= Scene(_camera.root).bindings;
			var viewportWidth	: Number				= sceneBindings.getProperty('viewportWidth');
			var viewportHeight	: Number				= sceneBindings.getProperty('viewportHeight');
			var ratio			: Number				= viewportWidth / viewportHeight;

			cameraData.projection.perspectiveFoV(cameraData.fieldOfView, ratio, cameraData.zNear, cameraData.zFar);

			screenToView.lock()
				.copyFrom(cameraData.projection)
				.invert()
				.unlock();

			cameraData.screenToWorld.lock()
				.copyFrom(cameraData.screenToView)
				.append(_camera.localToWorld)
				.unlock();

			cameraData.worldToScreen.lock()
				.copyFrom(_camera.worldToLocal)
				.append(cameraData.projection)
				.unlock();
		}
	}
}

update projection when the camera properties are updated