package com.ozg.adcore.vpaid
{
   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.external.ExternalInterface;
   import com.ozg.events.vpaidEvent;
   import flash.events.TimerEvent;
   import com.ozg.events.helperEvents;
   import flash.system.Security;
   import flash.net.navigateToURL;
   import flash.net.URLRequest;
   import flash.net.URLLoader;

   public class vpaidLinearFullPage extends Sprite implements IVPAID
   {

      public function vpaidLinearFullPage()
      {
         super();
         Security.allowDomain("*");
         mouseEnabled = false;

      }

      private static const VPAID_VERSION:String = "1.1";

      protected var creativeLoadTimer:Timer;

      protected var creativeLoadTimeout:Number = 15;

      protected var creativePath:String;

      protected var clickUrl:String;

      protected var timeRemaining:Number;

      protected var adPlayerVolume:Number;

      protected var initWidth:Number;

      protected var initHeight:Number;

      private var viewMode:String;

      private var adContainerSprite:Sprite;

      protected var isLinearAd:Boolean = true;

	  private var clickThru:String;

	  private var impressionURL:String;

      public function getVPAID() : Object
      {
         return this;
      }

      public function get adLinear() : Boolean
      {
         return this.isLinearAd;
      }

      public function get adExpanded() : Boolean
      {
         return false;
      }

      public function get adRemainingTime() : Number
      {
         return this.timeRemaining;
      }

      public function get adVolume() : Number
      {
         return this.adPlayerVolume;
      }

      public function set adVolume(param1:Number) : void
      {
         this.adPlayerVolume = param1;
      }

      public function handshakeVersion(param1:String) : String
      {
         this.log("The player supports VPAID version " + param1 + " and the ad supports " + VPAID_VERSION);
         return VPAID_VERSION;
      }

      protected function log(param1:String) : void
      {

         var _loc2_:Object = {"message":param1};
         dispatchEvent(new vpaidEvent(vpaidEvent.AdLog,_loc2_));
      }

      public function initAd(param1:Number, param2:Number, param3:String, param4:Number, param5:String, param6:String) : void
      {
         this.handleData(param5);
         this.resizeAd(param1,param2,param3);
         this.loadAd();
      }

      protected function handleData(param1:String) : void
      {
         var xmlData:XML = null;
         var creativeData:String = param1;
         if(creativeData != null)
         {
            try
            {
               xmlData = new XML(creativeData);
               this.creativePath = xmlData.setting;
				this.creativeLoadTimeout = xmlData.duration;
				this.clickThru = xmlData.clickThru;
				this.impressionURL = xmlData.impressionURL;
				initWidth = xmlData.creativeWidth;
				initHeight = xmlData.creativeHeight;
				handleVpaidEvent('AdImpression');
				var s:SendAndLoadExample = new SendAndLoadExample();
				s.sendData(impressionURL,'no message');

            }
            catch(e:Error)
            {
               onError();
            }
         }
         else
         {
            this.onError();
         }
         if(creativeData != null)
         {
            return;
         }
      }

      protected function loadAd() : void
      {
         dispatchEvent(new vpaidEvent(vpaidEvent.AdLoaded));
      }

      public function startAd() : void
      {
         this.log("Beginning the display of the VPAID ad");
         dispatchEvent(new vpaidEvent(vpaidEvent.AdStarted));
         this.creativeLoadTimer = new Timer(1000,this.creativeLoadTimeout);
         this.creativeLoadTimer.addEventListener(TimerEvent.TIMER,this.onTimer);
         this.creativeLoadTimer.addEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
         this.creativeLoadTimer.start();
         this.addJsCallbacks();
         this.addEventListeners();
         this.adContainerSprite = new adContainer(this.initWidth,this.initHeight);
         addChild(this.adContainerSprite);

      }

      private function timerComplete(param1:TimerEvent) : void
      {
         this.stopAd();
      }

      private function addJsCallbacks() : void
      {
         ExternalInterface.addCallback("handleVpaidEvent",this.handleVpaidEvent);
         ExternalInterface.addCallback("getPlayerVolume",this.getAdPlayerVolume);
		 ExternalInterface.call("initVpaid",creativePath,this.initWidth,this.initHeight);
      }

      private function getAdPlayerVolume() : void
      {
         //js will come here
      }

	  public function comingHTML() : void
      {
         ExternalInterface.call("console.log","i am talking with js");
      }

      private function addEventListeners() : void
      {
         addEventListener(vpaidEvent.AdUserClose,this.userClose);
      }

      private function userClose(param1:vpaidEvent) : void
      {
         this.stopAd();
      }

      protected function handleVpaidEvent(param1:String) : void
      {
		// ExternalInterface.call('console.log','param : '+param1);
         switch(param1)
         {
            case "AdImpression":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdImpression));
               break;
            case "AdVideoStart":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoStart));
               break;
            case "AdVideoFirstQuartile":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoFirstQuartile));
               break;
            case "AdVideoMidpoint":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoMidpoint));
               break;
            case "AdVideoThirdQuartile":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoThirdQuartile));
               break;
            case "AdVideoComplete":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoComplete));
               break;
            case "AdStopped":
               this.stopAd();
               break;
            case "AdUserClose":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdUserClose));
               break;
            case "AdClickThru":
               this.onAdClick();
               break;
            case "AdError":
               this.onError();
               break;
            case "CreativeLoaded":
               this.onCreativeLoad();
               break;
         }
      }

      private function onCreativeLoad() : void
      {
         if(this.creativeLoadTimer)
         {
            this.creativeLoadTimer.stop();
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER,this.onTimer);
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
            this.creativeLoadTimer = null;
         }
         this.adContainerSprite.dispatchEvent(new helperEvents(helperEvents.RemoveLoader));
      }

      private function onError() : void
      {
         dispatchEvent(new vpaidEvent(vpaidEvent.AdError));
         this.stopAd();
      }

      protected function onTimer(param1:TimerEvent) : void
      {
         this.timeRemaining--;
      }

	  public function onAdClick(event:Object = null) : void
	  {
		 var data:Object = { "playerHandles":true };
		 dispatchEvent(new vpaidEvent(vpaidEvent.AdClickThru, data));

		  ExternalInterface.call("function setWMWindow() {window.open('" + this.clickThru + "');}");
		 stopAd();

	   }

      public function stopAd() : void
      {
         if(this.creativeLoadTimer)
         {
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER,this.onTimer);
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
            this.creativeLoadTimer = null;
         }
         this.log("Stopping the display of the VPAID Ad");
         ExternalInterface.call("closeVpaid");
         dispatchEvent(new vpaidEvent(vpaidEvent.AdStopped));
      }

      public function resizeAd(param1:Number, param2:Number, param3:String) : void
      {
         this.log("resizeAd() width=" + param1 + ", height=" + param2 + ", viewMode=" + param3);
        // this.initWidth = param1;
        // this.initHeight = param2;
         this.viewMode = param3;
      }

      public function pauseAd() : void
      {
      }

      public function resumeAd() : void
      {
      }

      public function expandAd() : void
      {
      }

      public function collapseAd() : void
      {
      }

      public function skipAd() : void
      {
      }
   }
}

package com.ozg.adcore.vpaid
{
   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.external.ExternalInterface;
   import com.ozg.events.vpaidEvent;
   import flash.events.TimerEvent;
   import com.ozg.events.helperEvents;
   import flash.system.Security;
   import flash.net.navigateToURL;
   import flash.net.URLRequest;
   import flash.net.URLLoader;

   public class vpaidLinearFullPage extends Sprite implements IVPAID
   {

      public function vpaidLinearFullPage()
      {
         super();
         Security.allowDomain("*");
         mouseEnabled = false;

      }

      private static const VPAID_VERSION:String = "1.1";

      protected var creativeLoadTimer:Timer;

      protected var creativeLoadTimeout:Number = 15;

      protected var creativePath:String;

      protected var clickUrl:String;

      protected var timeRemaining:Number;

      protected var adPlayerVolume:Number;

      protected var initWidth:Number;

      protected var initHeight:Number;

      private var viewMode:String;

      private var adContainerSprite:Sprite;

      protected var isLinearAd:Boolean = true;

      private var clickThru:String;

      private var impressionURL:String;

      public function getVPAID() : Object
      {
         return this;
      }

      public function get adLinear() : Boolean
      {
         return this.isLinearAd;
      }

      public function get adExpanded() : Boolean
      {
         return false;
      }

      public function get adRemainingTime() : Number
      {
         return this.timeRemaining;
      }

      public function get adVolume() : Number
      {
         return this.adPlayerVolume;
      }

      public function set adVolume(param1:Number) : void
      {
         this.adPlayerVolume = param1;
      }

      public function handshakeVersion(param1:String) : String
      {
         this.log("The player supports VPAID version " + param1 + " and the ad supports " + VPAID_VERSION);
         return VPAID_VERSION;
      }

      protected function log(param1:String) : void
      {

         var _loc2_:Object = {"message":param1};
         dispatchEvent(new vpaidEvent(vpaidEvent.AdLog,_loc2_));
      }

      public function initAd(param1:Number, param2:Number, param3:String, param4:Number, param5:String, param6:String) : void
      {
         this.handleData(param5);
         this.resizeAd(param1,param2,param3);
         this.loadAd();
      }

      protected function handleData(param1:String) : void
      {
         var xmlData:XML = null;
         var creativeData:String = param1;
         if(creativeData != null)
         {
            try
            {
		                xmlData = new XML(creativeData);
		                this.creativePath = xmlData.setting;
			        this.creativeLoadTimeout = xmlData.duration;
				this.clickThru = xmlData.clickThru;
				this.impressionURL = xmlData.impressionURL;
				initWidth = xmlData.creativeWidth;
				initHeight = xmlData.creativeHeight;
				handleVpaidEvent('AdImpression');
				var s:SendAndLoadExample = new SendAndLoadExample();
				s.sendData(impressionURL,'no message');

            }
            catch(e:Error)
            {
               			onError();
            }
         }
         else
         {
            this.onError();
         }
         if(creativeData != null)
         {
            return;
         }
      }

      protected function loadAd() : void
      {
         dispatchEvent(new vpaidEvent(vpaidEvent.AdLoaded));
      }

      public function startAd() : void
      {
         this.log("Beginning the display of the VPAID ad");
         dispatchEvent(new vpaidEvent(vpaidEvent.AdStarted));
         this.creativeLoadTimer = new Timer(1000,this.creativeLoadTimeout);
         this.creativeLoadTimer.addEventListener(TimerEvent.TIMER,this.onTimer);
         this.creativeLoadTimer.addEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
         this.creativeLoadTimer.start();
         this.addJsCallbacks();
         this.addEventListeners();
         this.adContainerSprite = new adContainer(this.initWidth,this.initHeight);
         addChild(this.adContainerSprite);

      }

      private function timerComplete(param1:TimerEvent) : void
      {
         this.stopAd();
      }

      private function addJsCallbacks() : void
      {
         ExternalInterface.addCallback("handleVpaidEvent",this.handleVpaidEvent);
         ExternalInterface.addCallback("getPlayerVolume",this.getAdPlayerVolume);
         ExternalInterface.call("initVpaid",creativePath,this.initWidth,this.initHeight);
      }

      private function getAdPlayerVolume() : void
      {
         //js will come here
      }

      private function addEventListeners() : void
      {
         addEventListener(vpaidEvent.AdUserClose,this.userClose);
      }

      private function userClose(param1:vpaidEvent) : void
      {
         this.stopAd();
      }

      protected function handleVpaidEvent(param1:String) : void
      {
		// ExternalInterface.call('console.log','param : '+param1);
         switch(param1)
         {
            case "AdImpression":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdImpression));
               break;
            case "AdVideoStart":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoStart));
               break;
            case "AdVideoFirstQuartile":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoFirstQuartile));
               break;
            case "AdVideoMidpoint":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoMidpoint));
               break;
            case "AdVideoThirdQuartile":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoThirdQuartile));
               break;
            case "AdVideoComplete":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdVideoComplete));
               break;
            case "AdStopped":
               this.stopAd();
               break;
            case "AdUserClose":
               dispatchEvent(new vpaidEvent(vpaidEvent.AdUserClose));
               break;
            case "AdClickThru":
               this.onAdClick();
               break;
            case "AdError":
               this.onError();
               break;
            case "CreativeLoaded":
               this.onCreativeLoad();
               break;
         }
      }

      private function onCreativeLoad() : void
      {
         if(this.creativeLoadTimer)
         {
            this.creativeLoadTimer.stop();
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER,this.onTimer);
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
            this.creativeLoadTimer = null;
         }
         this.adContainerSprite.dispatchEvent(new helperEvents(helperEvents.RemoveLoader));
      }

      private function onError() : void
      {
         dispatchEvent(new vpaidEvent(vpaidEvent.AdError));
         this.stopAd();
      }

      protected function onTimer(param1:TimerEvent) : void
      {
         this.timeRemaining--;
      }

	  public function onAdClick(event:Object = null) : void
	  {
		 var data:Object = { "playerHandles":true };
		 dispatchEvent(new vpaidEvent(vpaidEvent.AdClickThru, data));

		  ExternalInterface.call("function setWMWindow() {window.open('" + this.clickThru + "');}");
		 stopAd();

	   }

      public function stopAd() : void
      {
         if(this.creativeLoadTimer)
         {
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER,this.onTimer);
            this.creativeLoadTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,this.timerComplete);
            this.creativeLoadTimer = null;
         }
         this.log("Stopping the display of the VPAID Ad");
         ExternalInterface.call("closeVpaid");
         dispatchEvent(new vpaidEvent(vpaidEvent.AdStopped));
      }

      public function resizeAd(param1:Number, param2:Number, param3:String) : void
      {
         this.log("resizeAd() width=" + param1 + ", height=" + param2 + ", viewMode=" + param3);
        // this.initWidth = param1;
        // this.initHeight = param2;
         this.viewMode = param3;
      }

      public function pauseAd() : void
      {
      }

      public function resumeAd() : void
      {
      }

      public function expandAd() : void
      {
      }

      public function collapseAd() : void
      {
      }

      public function skipAd() : void
      {
      }
   }
}

Update vpaidLinearFullPage.as

Update vpaidLinearFullPage.as


package com.ozg.utils
{
   import flash.display.Sprite;
   import flash.utils.Timer;
   import flash.events.TimerEvent;
   import com.ozg.events.helperEvents;

   public class loadingAnimation extends Sprite
   {

      public function loadingAnimation(param1:Number = 16777215)
      {
         super();
         this.animationColor = param1;
         this.animation = new Sprite();
         addChild(this.animation);
         this.makeAnimation();
      }

      private var animation:Sprite;

      private var rotate:Number = 0;

      private var animationTimer:Timer;

      private var animationColor:Number;

      private var animationStopped:Boolean = false;

      public function stopAnimation() : void
      {
         if(this.animationStopped == false)
         {
            removeChild(this.animation);
            this.animationTimer.removeEventListener(TimerEvent.TIMER,this.rotateMe);
            this.animationTimer.stop();
            this.animationStopped = true;
         }
      }

      private function makeAnimation() : void
      {
         this.renderAnimation();
         addEventListener(helperEvents.RemoveLoader,this.removeLoaderHandler);
         this.animationTimer = new Timer(70);
         this.animationTimer.addEventListener(TimerEvent.TIMER,this.rotateMe);
         this.animationTimer.start();
      }

      private function removeLoaderHandler(param1:helperEvents) : void
      {
         this.stopAnimation();
      }

      private function rotateMe(param1:TimerEvent) : void
      {
         this.rotate = this.rotate + 30;
         if(this.rotate == 360)
         {
            this.rotate = 0;
         }
         this.renderAnimation(this.rotate);
      }

      private function renderAnimation(param1:Number = 0) : void
      {
         var _loc4_:Sprite = null;
         this.clearAnimation();
         var _loc2_:Sprite = new Sprite();
         var _loc3_:uint = 0;
         while(_loc3_ <= 12)
         {
            _loc4_ = this.getShape();
            _loc4_.rotation = _loc3_ * 30 + param1;
            _loc4_.alpha = 0 + 1 / 12 * _loc3_;
            _loc2_.addChild(_loc4_);
            _loc3_++;
         }
         this.animation.addChild(_loc2_);
      }

      private function clearAnimation() : void
      {
         if(this.animation.numChildren == 0)
         {
            return;
         }
         this.animation.removeChildAt(0);
      }

      private function getShape() : Sprite
      {
         var _loc1_:Sprite = new Sprite();
         _loc1_.graphics.beginFill(this.animationColor,1);
         _loc1_.graphics.moveTo(-1,-12);
         _loc1_.graphics.lineTo(2,-12);
         _loc1_.graphics.lineTo(1,-5);
         _loc1_.graphics.lineTo(0,-5);
         _loc1_.graphics.lineTo(-1,-12);
         _loc1_.graphics.endFill();
         return _loc1_;
      }
   }
}

package com.ozg.utils
{
   import flash.display.Sprite;

   public class loadingAnimation extends Sprite
   {

      public function loadingAnimation(param1:Number = 16777215)
      {
         super();

      }
   }
}

comment outs

comment outs


package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.Signal;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.enum.DataProviderUsage;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			    : Mesh;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

		private var _data				: DataProvider;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;

		public function get frustumCulling() : uint
		{
			return _frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			if (value != _frustumCulling)
			{
				_frustumCulling = value;
				if (value == FrustumCulling.DISABLED)
				{
					_data.computedVisibility = true;
					_mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);
				}
				else if (_mesh && _mesh.scene)
				{
					_mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
	            	testCulling();
				}
			}
		}

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return _data.computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_data = new DataProvider(
				{ computedVisibility : true },
				'MeshVisibilityDataProvider',
				DataProviderUsage.EXCLUSIVE
			);

			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

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

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

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

			if (target.root is Scene)
				addedHandler(target, target.root as Scene);

			if (_frustumCulling)
				testCulling();
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

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

			if (target.root is Scene)
				removedHandler(target, target.root as Scene);
		}

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

			if (!scene)
				return ;

			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

			if (_frustumCulling)
			{
	            meshLocalToWorldChangedHandler(mesh, mesh.getLocalToWorldTransform());
				mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
			}

            mesh.computedVisibilityChanged.add(computedVisiblityChangedHandler);
            mesh.bindings.addProvider(_data);
		}

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

			if (!scene)
				return ;

			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			if (_frustumCulling)
				mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);

			mesh.computedVisibilityChanged.remove(computedVisiblityChangedHandler);
            mesh.bindings.removeProvider(_data);
		}

        private function computedVisiblityChangedHandler(node               : ISceneNode,
                                                         computedVisibility : Boolean) : void
        {
            _data.computedVisibility = computedVisibility;
        }

		private function worldToScreenChangedHandler(bindings	    : DataBindings,
                                                     propertyName	: String,
													 bindingName	: String,
                                                     value			: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(mesh : Mesh, transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere || culling == FrustumCulling.DISABLED)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (_mesh && _mesh.geometry && _mesh.geometry.boundingBox && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _data.computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _data.computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();
		}
	}
}

package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.Signal;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.enum.DataProviderUsage;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			    : Mesh;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

		private var _data				: DataProvider;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;

		public function get frustumCulling() : uint
		{
			return _frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			if (value != _frustumCulling)
			{
				_frustumCulling = value;
				if (value == FrustumCulling.DISABLED)
				{
					_data.computedVisibility = true;
					if (_mesh.localToWorldTransformChanged.hasCallback(meshLocalToWorldChangedHandler))
					{
						_mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);
					}
				}
				else if (_mesh && _mesh.scene)
				{
					_mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
	            	testCulling();
				}
			}
		}

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return _data.computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_data = new DataProvider(
				{ computedVisibility : true },
				'MeshVisibilityDataProvider',
				DataProviderUsage.EXCLUSIVE
			);

			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

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

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

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

			if (target.root is Scene)
				addedHandler(target, target.root as Scene);

			if (_frustumCulling)
				testCulling();
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

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

			if (target.root is Scene)
				removedHandler(target, target.root as Scene);
		}

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

			if (!scene)
				return ;

			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

			if (_frustumCulling)
			{
	            meshLocalToWorldChangedHandler(mesh, mesh.getLocalToWorldTransform());
				mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
			}

            mesh.computedVisibilityChanged.add(computedVisiblityChangedHandler);
            mesh.bindings.addProvider(_data);
		}

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

			if (!scene)
				return ;

			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			if (_frustumCulling)
				mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);

			mesh.computedVisibilityChanged.remove(computedVisiblityChangedHandler);
            mesh.bindings.removeProvider(_data);
		}

        private function computedVisiblityChangedHandler(node               : ISceneNode,
                                                         computedVisibility : Boolean) : void
        {
            _data.computedVisibility = computedVisibility;
        }

		private function worldToScreenChangedHandler(bindings	    : DataBindings,
                                                     propertyName	: String,
													 bindingName	: String,
                                                     value			: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(mesh : Mesh, transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere || culling == FrustumCulling.DISABLED)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (_mesh && _mesh.geometry && _mesh.geometry.boundingBox && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _data.computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _data.computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();
		}
	}
}

Update src/aerys/minko/scene/controller/mesh/MeshVisibilityController.as

fix bug with set frustumCulling = FrustumCulling.DISABLED
example:
```
var m:Mesh = new Mesh(CubeGeometry.cubeGeometry, new BasicMaterial({ diffuseColor : 0xff00ff00 } ));
m.frustumCulling = FrustumCulling.DISABLED;
scene.addChild(m);
```
result:
Error: This callback does not exist.
at aerys.minko.type::Signal/remove()[\aerys\minko\type\Signal.as:98]
at aerys.minko.scene.controller.mesh::MeshVisibilityController/set frustumCulling()[\aerys\minko\scene\controller\mesh\MeshVisibilityController.as:62]
at aerys.minko.scene.node::Mesh/set frustumCulling()[\aerys\minko\scene\node\Mesh.as:155]


//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.presents.client {
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer;

import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;

import com.threerings.presents.dobj.CompoundEvent;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.net.BootstrapNotification;
import com.threerings.presents.net.CompoundDownstreamMessage;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.EventNotification;
import com.threerings.presents.net.FailureResponse;
import com.threerings.presents.net.ForwardEventRequest;
import com.threerings.presents.net.ObjectResponse;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.SubscribeRequest;
import com.threerings.presents.net.UnsubscribeRequest;
import com.threerings.presents.net.UnsubscribeResponse;
import com.threerings.presents.net.UpdateThrottleMessage;

/**
 * The client distributed object manager manages a set of proxy objects
 * which mirror the distributed objects maintained on the server.
 * Requests for modifications, etc. are forwarded to the server and events
 * are dispatched from the server to this client for objects to which this
 * client is subscribed.
 */
public class ClientDObjectMgr
    implements DObjectManager
{
    private static const log :Log = Log.getLog(ClientDObjectMgr);

    /**
     * Constructs a client distributed object manager.
     *
     * @param comm a communicator instance by which it can communicate
     * with the server.
     * @param client a reference to the client that is managing this whole
     * communications and event dispatch business.
     */
    public function ClientDObjectMgr (comm :Communicator, client :Client)
    {
        _comm = comm;
        _client = client;

        // register a flush interval
        _flushInterval = new Timer(FLUSH_INTERVAL);
        _flushInterval.addEventListener(TimerEvent.TIMER, flushObjects);
        _flushInterval.start();
    }

    // documentation inherited from interface DObjectManager
    public function isManager (object :DObject) :Boolean
    {
        // we are never authoritative in the present implementation
        return false;
    }

    // inherit documentation from the interface DObjectManager
    public function subscribeToObject (oid :int, target :Subscriber) :void
    {
        if (oid <= 0) {
            target.requestFailed(oid, new ObjectAccessError("Invalid oid " + oid + "."));
        } else {
            doSubscribe(oid, target);
        }
    }

    // inherit documentation from the interface DObjectManager
    public function unsubscribeFromObject (oid :int, target :Subscriber) :void
    {
        doUnsubscribe(oid, target);
    }

    // inherit documentation from the interface
    public function postEvent (event :DEvent) :void
    {
        // send a forward event request to the server
        _comm.postMessage(new ForwardEventRequest(event));
    }

    // inherit documentation from the interface
    public function removedLastSubscriber (
            obj :DObject, deathWish :Boolean) :void
    {
        // if this object has a registered flush delay, don't can it just
        // yet, just slip it onto the flush queue
        for each (var dclass :Class in _delays.keys()) {
            if (obj is dclass) {
                var expire :Number = getTimer() + Number(_delays.get(dclass));
                _flushes.put(obj.getOid(), new FlushRecord(obj, expire));
//                 log.info("Flushing " + obj.getOid() + " at " +
//                          new java.util.Date(expire));
                return;
            }
        }

        // if we didn't find a delay registration, flush immediately
        flushObject(obj);
    }

    /**
     * Registers an object flush delay.
     *
     * @see Client#registerFlushDelay
     */
    public function registerFlushDelay (objclass :Class, delay :Number) :void
    {
        _delays.put(objclass, delay);
    }

    /**
     * Called by the communicator when a downstream message arrives from
     * the network layer. We queue it up for processing and request some
     * processing time on the main thread.
     */
    public function processMessage (msg :DownstreamMessage) :void
    {
        // do the proper thing depending on the object
        if (msg is EventNotification) {
            var evt :DEvent = (msg as EventNotification).getEvent();
//             log.info("Dispatch event: " + evt);
            dispatchEvent(evt);

        } else if (msg is BootstrapNotification) {
            _client.gotBootstrap((msg as BootstrapNotification).getData(), this);

        } else if (msg is ObjectResponse) {
            registerObjectAndNotify((msg as ObjectResponse).getObject());

        } else if (msg is UnsubscribeResponse) {
            var oid :int = (msg as UnsubscribeResponse).getOid();
            if (_dead.remove(oid) == null) {
                log.warning("Received unsub ACK from unknown object [oid=" + oid + "].");
            }

        } else if (msg is FailureResponse) {
            notifyFailure((msg as FailureResponse).getOid(), (msg as FailureResponse).getMessage());

        } else if (msg is PongResponse) {
            _client.gotPong(msg as PongResponse);

        } else if (msg is UpdateThrottleMessage) {
            _client.setOutgoingMessageThrottle((msg as UpdateThrottleMessage).messagesPerSec);
        } else if (msg is CompoundDownstreamMessage) {
            for each (var submsg :DownstreamMessage in CompoundDownstreamMessage(msg).msgs) {
                processMessage(submsg);
            }
        }
    }

    /**
     * Called when a new event arrives from the server that should be
     * dispatched to subscribers here on the client.
     */
    protected function dispatchEvent (event :DEvent) :void
    {
        // if this is a compound event, we need to process its contained
        // events in order
        if (event is CompoundEvent) {
            var events :Array = (event as CompoundEvent).getEvents();
            var ecount :int = events.length;
            for (var ii :int = 0; ii < ecount; ii++) {
                dispatchEvent(events[ii] as DEvent);
            }
            return;
        }

        // look up the object on which we're dispatching this event
        var toid :int = event.getTargetOid();
        var target :DObject = (_ocache.get(toid) as DObject);
        if (target == null) {
            if (_dead.get(toid) == null) {
                log.warning("Unable to dispatch event on non-proxied " +
                    "object [event=" + event + "].");
            }
            return;
        }

        try {
            // apply the event to the object
            var notify :Boolean = event.applyToObject(target);

            // if this is an object destroyed event, we need to remove the
            // object from our object table
            if (event is ObjectDestroyedEvent) {
//                 log.info("Pitching destroyed object " +
//                          "[oid=" + toid + ", class=" +
//                          StringUtil.shortClassName(target) + "].");
                _ocache.remove(toid);
            }

            // have the object pass this event on to its listeners
            if (notify) {
                target.notifyListeners(event);
            }

        } catch (e :Error) {
            log.warning("Failure processing event", "event", event, "target", target, e);
        }
    }

    /**
     * Registers this object in our proxy cache and notifies the
     * subscribers that were waiting for subscription to this object.
     */
    protected function registerObjectAndNotify (obj :DObject) :void
    {
        // let the object know that we'll be managing it
        obj.setManager(this);

        var oid :int = obj.getOid();
        // stick the object into the proxy object table
        _ocache.put(oid, obj);

        // let the penders know that the object is available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Got object, but no one cares?! " +
                "[oid=" + oid + ", obj=" + obj + "].");
            return;
        }
        // log.debug("Got object: pendReq=" + req);

        for each (var target :Subscriber in req.targets) {
            // log.debug("Notifying subscriber: " + target);
            // add them as a subscriber
            obj.addSubscriber(target);
            // and let them know that the object is in
            target.objectAvailable(obj);
        }
    }

    /**
     * Notifies the subscribers that had requested this object (for subscription) that it is not
     * available.
     */
    protected function notifyFailure (oid :int, message :String) :void
    {
        // let the penders know that the object is not available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Failed to get object, but no one cares?! [oid=" + oid + "].");
            return;
        }

        for each (var target :Subscriber in req.targets) {
            // and let them know that the object is in
            target.requestFailed(oid, new ObjectAccessError(message));
        }
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doSubscribe (oid :int, target :Subscriber) :void
    {
        // log.info("doSubscribe: " + oid + ": " + target);

        // first see if we've already got the object in our table
        var obj :DObject = (_ocache.get(oid) as DObject);
        if (obj != null) {
            // clear the object out of the flush table if it's in there
            _flushes.remove(oid);
            // add the subscriber and call them back straight away
            obj.addSubscriber(target);
            target.objectAvailable(obj);
            return;
        }

        // see if we've already got an outstanding request for this object
        var req :PendingRequest = (_penders.get(oid) as PendingRequest);
        if (req != null) {
            // add this subscriber to the list of subscribers to be
            // notified when the request is satisfied
            req.addTarget(target);
            return;
        }

        // otherwise we need to create a new request
        req = new PendingRequest(oid);
        req.addTarget(target);
        _penders.put(oid, req);
        // log.info("Registering pending request [oid=" + oid + "].");

        // and issue a request to get things rolling
        _comm.postMessage(new SubscribeRequest(oid));
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doUnsubscribe (oid :int, target :Subscriber) :void
    {
        var dobj :DObject = (_ocache.get(oid) as DObject);
        if (dobj != null) {
            dobj.removeSubscriber(target);

        } else {
            log.info("Requested to remove subscriber from " +
                     "non-proxied object [oid=" + oid +
                     ", sub=" + target + "].");
        }
    }

    /**
     * Flushes a distributed object subscription, issuing an unsubscribe
     * request to the server.
     */
    protected function flushObject (obj :DObject) :void
    {
        // move this object into the dead pool so that we don't claim to
        // have it around anymore; once our unsubscribe message is
        // processed, it'll be 86ed
        var ooid :int = obj.getOid();
        _ocache.remove(ooid);
        _dead.put(ooid, obj);

        // ship off an unsubscribe message to the server; we'll remove the
        // object from our table when we get the unsub ack
        _comm.postMessage(new UnsubscribeRequest(ooid));
    }

    /**
     * Called periodically to flush any objects that have been lingering
     * due to a previously enacted flush delay.
     */
    protected function flushObjects (event :TimerEvent) :void
    {
        var now :Number = getTimer();
        for each (var oid :int in _flushes.keys()) {
            var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
            if (rec.expire <= now) {
                _flushes.remove(oid);
                flushObject(rec.obj);
            }
        }
    }

    /** A reference to the communicator that sends and receives messages
     * for this client. */
    protected var _comm :Communicator;

    /** A reference to our client instance. */
    protected var _client :Client;

    /** All of the distributed objects that are active on this client. */
    protected var _ocache :Map = Maps.newMapOf(int);

    /** Objects that have been marked for death. */
    protected var _dead :Map = Maps.newMapOf(int);

    /** Pending object subscriptions. */
    protected var _penders :Map = Maps.newMapOf(int);

    /** A mapping from distributed object class to flush delay. */
    protected var _delays :Map = Maps.newMapOf(int);

    /** A set of objects waiting to be flushed. */
    protected var _flushes :Map = Maps.newMapOf(int);

    /** Flushes objects every now and again. */
    protected var _flushInterval :Timer;

    /** Flush expired objects every 30 seconds. */
    protected static const FLUSH_INTERVAL :Number = 30 * 1000;
}
}

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;

class PendingRequest
{
    public var oid :int;
    public var targets :Array = new Array();

    public function PendingRequest (oid :int)
    {
        this.oid = oid;
    }

    public function addTarget (target :Subscriber) :void
    {
        targets.push(target);
    }
}

/** Used to manage pending object flushes. */
class FlushRecord
{
    /** The object to be flushed. */
    public var obj :DObject;

    /** The time at which we flush it. */
    public var expire :Number;

    public function FlushRecord (obj :DObject, expire :Number)
    {
        this.obj = obj;
        this.expire = expire;
    }
}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.presents.client {
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer;

import com.threerings.util.Log;
import com.threerings.util.Map;
import com.threerings.util.Maps;

import com.threerings.presents.dobj.CompoundEvent;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.net.BootstrapNotification;
import com.threerings.presents.net.CompoundDownstreamMessage;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.EventNotification;
import com.threerings.presents.net.FailureResponse;
import com.threerings.presents.net.ForwardEventRequest;
import com.threerings.presents.net.ObjectResponse;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.SubscribeRequest;
import com.threerings.presents.net.UnsubscribeRequest;
import com.threerings.presents.net.UnsubscribeResponse;
import com.threerings.presents.net.UpdateThrottleMessage;

/**
 * The client distributed object manager manages a set of proxy objects
 * which mirror the distributed objects maintained on the server.
 * Requests for modifications, etc. are forwarded to the server and events
 * are dispatched from the server to this client for objects to which this
 * client is subscribed.
 */
public class ClientDObjectMgr
    implements DObjectManager
{
    private static const log :Log = Log.getLog(ClientDObjectMgr);

    /**
     * Constructs a client distributed object manager.
     *
     * @param comm a communicator instance by which it can communicate
     * with the server.
     * @param client a reference to the client that is managing this whole
     * communications and event dispatch business.
     */
    public function ClientDObjectMgr (comm :Communicator, client :Client)
    {
        _comm = comm;
        _client = client;

        // register a flush interval
        _flushInterval = new Timer(FLUSH_INTERVAL);
        _flushInterval.addEventListener(TimerEvent.TIMER, flushObjects);
        _flushInterval.start();
    }

    // documentation inherited from interface DObjectManager
    public function isManager (object :DObject) :Boolean
    {
        // we are never authoritative in the present implementation
        return false;
    }

    // inherit documentation from the interface DObjectManager
    public function subscribeToObject (oid :int, target :Subscriber) :void
    {
        if (oid <= 0) {
            target.requestFailed(oid, new ObjectAccessError("Invalid oid " + oid + "."));
        } else {
            doSubscribe(oid, target);
        }
    }

    // inherit documentation from the interface DObjectManager
    public function unsubscribeFromObject (oid :int, target :Subscriber) :void
    {
        doUnsubscribe(oid, target);
    }

    // inherit documentation from the interface
    public function postEvent (event :DEvent) :void
    {
        // send a forward event request to the server
        _comm.postMessage(new ForwardEventRequest(event));
    }

    // inherit documentation from the interface
    public function removedLastSubscriber (
            obj :DObject, deathWish :Boolean) :void
    {
        // if this object has a registered flush delay, don't can it just
        // yet, just slip it onto the flush queue
        for each (var dclass :Class in _delays.keys()) {
            if (obj is dclass) {
                var expire :Number = getTimer() + Number(_delays.get(dclass));
                _flushes.put(obj.getOid(), new FlushRecord(obj, expire));
//                 log.info("Flushing " + obj.getOid() + " at " +
//                          new java.util.Date(expire));
                return;
            }
        }

        // if we didn't find a delay registration, flush immediately
        flushObject(obj);
    }

    /**
     * Registers an object flush delay.
     *
     * @see Client#registerFlushDelay
     */
    public function registerFlushDelay (objclass :Class, delay :Number) :void
    {
        _delays.put(objclass, delay);
    }

    /**
     * Called by the communicator when a downstream message arrives from
     * the network layer. We queue it up for processing and request some
     * processing time on the main thread.
     */
    public function processMessage (msg :DownstreamMessage) :void
    {
        // do the proper thing depending on the object
        if (msg is EventNotification) {
            var evt :DEvent = (msg as EventNotification).getEvent();
//             log.info("Dispatch event: " + evt);
            dispatchEvent(evt);

        } else if (msg is BootstrapNotification) {
            _client.gotBootstrap((msg as BootstrapNotification).getData(), this);

        } else if (msg is ObjectResponse) {
            registerObjectAndNotify((msg as ObjectResponse).getObject());

        } else if (msg is UnsubscribeResponse) {
            var oid :int = (msg as UnsubscribeResponse).getOid();
            if (_dead.remove(oid) == null) {
                log.warning("Received unsub ACK from unknown object [oid=" + oid + "].");
            }

        } else if (msg is FailureResponse) {
            notifyFailure((msg as FailureResponse).getOid(), (msg as FailureResponse).getMessage());

        } else if (msg is PongResponse) {
            _client.gotPong(msg as PongResponse);

        } else if (msg is UpdateThrottleMessage) {
            _client.setOutgoingMessageThrottle((msg as UpdateThrottleMessage).messagesPerSec);
        } else if (msg is CompoundDownstreamMessage) {
            for each (var submsg :DownstreamMessage in CompoundDownstreamMessage(msg).msgs) {
                processMessage(submsg);
            }
        }
    }

    /**
     * Called when a new event arrives from the server that should be
     * dispatched to subscribers here on the client.
     */
    protected function dispatchEvent (event :DEvent) :void
    {
        // if this is a compound event, we need to process its contained
        // events in order
        if (event is CompoundEvent) {
            var events :Array = (event as CompoundEvent).getEvents();
            var ecount :int = events.length;
            for (var ii :int = 0; ii < ecount; ii++) {
                dispatchEvent(events[ii] as DEvent);
            }
            return;
        }

        // look up the object on which we're dispatching this event
        var toid :int = event.getTargetOid();
        var target :DObject = (_ocache.get(toid) as DObject);
        if (target == null) {
            if (_dead.get(toid) == null) {
                log.warning("Unable to dispatch event on non-proxied " +
                    "object [event=" + event + "].");
            }
            return;
        }

        try {
            // apply the event to the object
            var notify :Boolean = event.applyToObject(target);

            // if this is an object destroyed event, we need to remove the
            // object from our object table
            if (event is ObjectDestroyedEvent) {
//                 log.info("Pitching destroyed object " +
//                          "[oid=" + toid + ", class=" +
//                          StringUtil.shortClassName(target) + "].");
                _ocache.remove(toid);
            }

            // have the object pass this event on to its listeners
            if (notify) {
                target.notifyListeners(event);
            }

        } catch (e :Error) {
            log.warning("Failure processing event", "event", event, "target", target, e);
        }
    }

    /**
     * Registers this object in our proxy cache and notifies the
     * subscribers that were waiting for subscription to this object.
     */
    protected function registerObjectAndNotify (obj :DObject) :void
    {
        // let the object know that we'll be managing it
        obj.setManager(this);

        var oid :int = obj.getOid();
        // stick the object into the proxy object table
        _ocache.put(oid, obj);

        // let the penders know that the object is available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Got object, but no one cares?! " +
                "[oid=" + oid + ", obj=" + obj + "].");
            return;
        }
        // log.debug("Got object: pendReq=" + req);

        for each (var target :Subscriber in req.targets) {
            // log.debug("Notifying subscriber: " + target);
            // add them as a subscriber
            obj.addSubscriber(target);
            // and let them know that the object is in
            target.objectAvailable(obj);
        }
    }

    /**
     * Notifies the subscribers that had requested this object (for subscription) that it is not
     * available.
     */
    protected function notifyFailure (oid :int, message :String) :void
    {
        // let the penders know that the object is not available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Failed to get object, but no one cares?! [oid=" + oid + "].");
            return;
        }

        for each (var target :Subscriber in req.targets) {
            // and let them know that the object is in
            target.requestFailed(oid, new ObjectAccessError(message));
        }
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doSubscribe (oid :int, target :Subscriber) :void
    {
        // log.info("doSubscribe: " + oid + ": " + target);

        // first see if we've already got the object in our table
        var obj :DObject = (_ocache.get(oid) as DObject);
        if (obj != null) {
            // clear the object out of the flush table if it's in there
            _flushes.remove(oid);
            // add the subscriber and call them back straight away
            obj.addSubscriber(target);
            target.objectAvailable(obj);
            return;
        }

        // see if we've already got an outstanding request for this object
        var req :PendingRequest = (_penders.get(oid) as PendingRequest);
        if (req != null) {
            // add this subscriber to the list of subscribers to be
            // notified when the request is satisfied
            req.addTarget(target);
            return;
        }

        // otherwise we need to create a new request
        req = new PendingRequest(oid);
        req.addTarget(target);
        _penders.put(oid, req);
        // log.info("Registering pending request [oid=" + oid + "].");

        // and issue a request to get things rolling
        _comm.postMessage(new SubscribeRequest(oid));
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doUnsubscribe (oid :int, target :Subscriber) :void
    {
        var dobj :DObject = (_ocache.get(oid) as DObject);
        if (dobj != null) {
            dobj.removeSubscriber(target);

        } else {
            log.info("Requested to remove subscriber from " +
                     "non-proxied object [oid=" + oid +
                     ", sub=" + target + "].");
        }
    }

    /**
     * Flushes a distributed object subscription, issuing an unsubscribe
     * request to the server.
     */
    protected function flushObject (obj :DObject) :void
    {
        // move this object into the dead pool so that we don't claim to
        // have it around anymore; once our unsubscribe message is
        // processed, it'll be 86ed
        var ooid :int = obj.getOid();
        _ocache.remove(ooid);
        obj.setManager(null);
        _dead.put(ooid, obj);

        // ship off an unsubscribe message to the server; we'll remove the
        // object from our table when we get the unsub ack
        _comm.postMessage(new UnsubscribeRequest(ooid));
    }

    /**
     * Called periodically to flush any objects that have been lingering
     * due to a previously enacted flush delay.
     */
    protected function flushObjects (event :TimerEvent) :void
    {
        var now :Number = getTimer();
        for each (var oid :int in _flushes.keys()) {
            var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
            if (rec.expire <= now) {
                _flushes.remove(oid);
                flushObject(rec.obj);
            }
        }
    }

    /** A reference to the communicator that sends and receives messages
     * for this client. */
    protected var _comm :Communicator;

    /** A reference to our client instance. */
    protected var _client :Client;

    /** All of the distributed objects that are active on this client. */
    protected var _ocache :Map = Maps.newMapOf(int);

    /** Objects that have been marked for death. */
    protected var _dead :Map = Maps.newMapOf(int);

    /** Pending object subscriptions. */
    protected var _penders :Map = Maps.newMapOf(int);

    /** A mapping from distributed object class to flush delay. */
    protected var _delays :Map = Maps.newMapOf(int);

    /** A set of objects waiting to be flushed. */
    protected var _flushes :Map = Maps.newMapOf(int);

    /** Flushes objects every now and again. */
    protected var _flushInterval :Timer;

    /** Flush expired objects every 30 seconds. */
    protected static const FLUSH_INTERVAL :Number = 30 * 1000;
}
}

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;

class PendingRequest
{
    public var oid :int;
    public var targets :Array = new Array();

    public function PendingRequest (oid :int)
    {
        this.oid = oid;
    }

    public function addTarget (target :Subscriber) :void
    {
        targets.push(target);
    }
}

/** Used to manage pending object flushes. */
class FlushRecord
{
    /** The object to be flushed. */
    public var obj :DObject;

    /** The time at which we flush it. */
    public var expire :Number;

    public function FlushRecord (obj :DObject, expire :Number)
    {
        this.obj = obj;
        this.expire = expire;
    }
}

Clear out the manager on the DObject when it's no longer active

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


////////////////////////////////////////////////////////////////////////////////
//
//  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 promises.cases
{

import flash.events.TimerEvent;
import flash.net.URLRequest;
import flash.utils.Timer;

import flashx.textLayout.debug.assert;

import org.apache.flex.promises.Promise;
import org.apache.flex.promises.interfaces.IThenable;
import org.flexunit.asserts.assertEquals;
import org.flexunit.async.Async;

public class PromisesJIRATests
{

	//--------------------------------------------------------------------------
	//
	//    Variables
	//
	//--------------------------------------------------------------------------

	private var expected_:*;

	private var promise_:IThenable;

	private var got_:*;

	private var timer_:Timer;

	//--------------------------------------------------------------------------
	//
	//    Methods
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//    parseGot_
	//----------------------------------

	private function parseGot_(value:*):void {
		this.got_ = value;
	}

	//----------------------------------
	//    setUp
	//----------------------------------

	[Before(async)]
	public function setUp():void
	{
		this.timer_ = new Timer(100, 1);
	}

	//----------------------------------
	//    tearDown
	//----------------------------------

	[After(async)]
	public function tearDown():void
	{
		this.promise_ = null;

		if (this.timer_)
		{
			this.timer_.stop();
			this.timer_ = null;
		}
	}

	//----------------------------------
	//    verifyGotType_
	//----------------------------------

	private function verifyGotType_(event:TimerEvent, result:*):void {
		assertEquals(typeof this.expected_, this.got_);
	}

	//--------------------------------------------------------------------------
	//
	//    Tests
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//    test_FLEX34753
	//----------------------------------

	[Test(async)]
	public function test_FLEX34753():void
	{
		Async.handleEvent(this, timer_, TimerEvent.TIMER_COMPLETE, verifyGotType_);

		timer_.start();

		promise_ = new Promise(function (fulfill:Function = null, reject:Function = null):*
		{
			var urlRequest:URLRequest = new URLRequest('http://flex.apache.org');

			fulfill(urlRequest);
		});

		expected_ = '[object URLRequest]';

		promise_.then(parseGot_);
	}

}}

////////////////////////////////////////////////////////////////////////////////
//
//  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 promises.cases
{

import flash.events.TimerEvent;
import flash.net.URLRequest;
import flash.utils.Timer;

import org.apache.flex.promises.Promise;
import org.apache.flex.promises.interfaces.IThenable;
import org.flexunit.asserts.assertEquals;
import org.flexunit.async.Async;

public class PromisesJIRATests
{

	//--------------------------------------------------------------------------
	//
	//    Variables
	//
	//--------------------------------------------------------------------------

	private var expected_:*;

	private var promise_:IThenable;

	private var got_:*;

	private var timer_:Timer;

	//--------------------------------------------------------------------------
	//
	//    Methods
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//    parseGot_
	//----------------------------------

	private function parseGot_(value:*):void {
		this.got_ = value;
	}

	//----------------------------------
	//    setUp
	//----------------------------------

	[Before(async)]
	public function setUp():void
	{
		this.timer_ = new Timer(100, 1);
	}

	//----------------------------------
	//    tearDown
	//----------------------------------

	[After(async)]
	public function tearDown():void
	{
		this.promise_ = null;

		if (this.timer_)
		{
			this.timer_.stop();
			this.timer_ = null;
		}
	}

	//----------------------------------
	//    verifyGotType_
	//----------------------------------

	private function verifyGotType_(event:TimerEvent, result:*):void {
		assertEquals(this.expected_, this.got_.toString());
	}

	//--------------------------------------------------------------------------
	//
	//    Tests
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//    test_FLEX34753
	//----------------------------------

	[Test(async)]
	public function test_FLEX34753():void
	{
		Async.handleEvent(this, timer_, TimerEvent.TIMER_COMPLETE, verifyGotType_);

		timer_.start();

		promise_ = new Promise(function (fulfill:Function = null, reject:Function = null):*
		{
			var urlRequest:URLRequest = new URLRequest('http://flex.apache.org');

			fulfill(urlRequest);
		});

		expected_ = '[object URLRequest]';

		promise_.then(parseGot_);
	}

}}

Fix test for FLEX-34753

Fix test for FLEX-34753

Signed-off-by: Erik de Bruin <c195b508e6f0c57aeafdb5e96947e2b79e6a4b40@ixsoftware.nl>


package org.arisgames.editor.components
{
import com.ninem.controls.TreeBrowser;
import com.ninem.events.TreeBrowserEvent;

import flash.events.MouseEvent;

import mx.containers.Panel;
import mx.controls.Alert;
import mx.controls.Button;
import mx.controls.DataGrid;
import mx.core.ClassFactory;
import mx.events.DynamicEvent;
import mx.events.FlexEvent;
import mx.managers.PopUpManager;
import mx.rpc.Responder;

import org.arisgames.editor.data.arisserver.Media;
import org.arisgames.editor.data.businessobjects.ObjectPaletteItemBO;
import org.arisgames.editor.models.GameModel;
import org.arisgames.editor.services.AppServices;
import org.arisgames.editor.util.AppConstants;
import org.arisgames.editor.util.AppDynamicEventManager;
import org.arisgames.editor.util.AppUtils;

public class ItemEditorMediaPickerView extends Panel
{
    // Data Object
	public var delegate:Object;
    private var objectPaletteItem:ObjectPaletteItemBO;
    private var isIconPicker:Boolean = false;
	private var isNPC:Boolean = false;
	private var uploadFormVisable:Boolean = false;

    // Media Data
    [Bindable] public var xmlData:XML;

    // GUI
    [Bindable] public var treeBrowser:TreeBrowser;
    [Bindable] public var closeButton:Button;
	[Bindable] public var selectButton:Button;

	// Tree Icons
	[Bindable]
	[Embed(source="assets/img/separator.png")]
	public var separatorIcon:Class; //If these names are changed, make sure it is also changed in AppConstants (MEDIATREEICON_*)

	[Bindable]
	[Embed(source="assets/img/upload.png")]
	public var uploadIcon:Class; //If these names are changed, make sure it is also changed in AppConstants (MEDIATREEICON_*)

	private var mediaUploader:ItemEditorMediaPickerUploadFormMX;

    /**
     * Constructor
     */
    public function ItemEditorMediaPickerView()
    {
        super();
        this.addEventListener(FlexEvent.CREATION_COMPLETE, handleInit);
    }

    private function handleInit(event:FlexEvent):void
    {
        trace("in ItemEditorMediaPickerView's handleInit");
        var cf:ClassFactory = new ClassFactory(ItemEditorMediaPickerCustomEditorMX);
        cf.properties = {objectPaletteItem: this.objectPaletteItem};
        treeBrowser.detailRenderer = cf;
        treeBrowser.addEventListener(TreeBrowserEvent.NODE_SELECTED, onNodeSelected);
        closeButton.addEventListener(MouseEvent.CLICK, handleCloseButton);
		selectButton.addEventListener(MouseEvent.CLICK, handleSelectButton);
        // Load Game's Media Into XML
        AppServices.getInstance().getMediaForGame(GameModel.getInstance().game.gameId, new Responder(handleLoadingOfMediaIntoXML, handleFault));
    }

    private function printXMLData():void
    {
        trace("XML Data = '" + xmlData.toXMLString() + "'");
    }

    public function setObjectPaletteItem(opi:ObjectPaletteItemBO):void
    {
        trace("setting objectPaletteItem with name = '" + opi.name + "' in ItemEditorPlaqueView");
        objectPaletteItem = opi;
    }

    public function isInIconPickerMode():Boolean
    {
        return isIconPicker;
    }

    public function setIsIconPicker(isIconPickerMode:Boolean):void
    {
        trace("setting isIconPicker mode to: " + isIconPickerMode);
        this.isIconPicker = isIconPickerMode;
        this.updateTitleBasedOnMode();
    }

    private function updateTitleBasedOnMode():void
    {
        if (isIconPicker)
        {
            title = "Icon Picker";
        }
        else
        {
            title = "Media Picker";
        }
    }

    private function handleCloseButton(evt:MouseEvent):void
    {
        trace("ItemEditorMediaPickerView: handleCloseButton()");
		PopUpManager.removePopUp(this);
    }

	private function handleSelectButton(evt:MouseEvent):void
	{
		trace("Select Button clicked...");

		var m:Media = new Media();
		m.mediaId = treeBrowser.selectedItem.@mediaId;
		m.name = treeBrowser.selectedItem.@name;
		m.type = treeBrowser.selectedItem.@type;
		m.urlPath = treeBrowser.selectedItem.@urlPath;
		m.fileName = treeBrowser.selectedItem.@fileName;
		m.isDefault = treeBrowser.selectedItem.@isDefault;

		if (delegate.hasOwnProperty("didSelectMediaItem")){
			delegate.didSelectMediaItem(this, m);
		}

		PopUpManager.removePopUp(this);
	}

    private function handleLoadingOfMediaIntoXML(obj:Object):void
    {
		if(this.objectPaletteItem.objectType == AppConstants.CONTENTTYPE_CHARACTER_DATABASE){
			this.isNPC = true;
			trace("This is an NPC, so disallow Audio/Visual media");
		}

        trace("In handleLoadingOfMediaIntoXML() Result called with obj = " + obj + "; Result = " + obj.result);
        if (obj.result.returnCode != 0)
        {
            trace("Bad loading of media attempt... let's see what happened.  Error = '" + obj.result.returnCodeDescription + "'");
            var msg:String = obj.result.returnCodeDescription;
            Alert.show("Error Was: " + msg, "Error While Loading Media");
			return;
        }

        //Init the XML Data
		xmlData = new XML('<nodes label="' + AppConstants.MEDIATYPE + '"/>');

		if (!this.isIconPicker && !this.isNPC) {
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_IMAGE + '"/>');
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_AUDIO + '"/>');
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_VIDEO + '"/>');
		}

		this.printXMLData();

		var media:Array = obj.result.data as Array;
        trace("Number of Media Objects returned from server = '" + media.length + "'");

		//Puts media items in xml readable format
		var numImageDefaults:Number = 0;
		var numAudioDefaults:Number = 0;
		var numVideoDefaults:Number = 0;
		var numIconDefaults:Number = 0;
		for (var k:Number = 0; k <= 1; k++) //Iterates over list twice; once for uploaded items, again for default
		{
			for (var j:Number = 0; j < media.length; j++)
			{
				var o:Object = media[j];

				if((k==0 && !o.is_default) || (k==1 && o.is_default)){
					var m:Media = new Media();

					m.mediaId = o.media_id;
					m.name = o.name;
					m.type = o.type;
					m.urlPath = o.url_path;
					m.fileName = o.file_name;
					m.isDefault = o.is_default; //for both default files and uploaded files

					var node:String = "<node label='" + AppUtils.filterStringToXMLEscapeCharacters(m.name) + "' mediaId='" + m.mediaId + "' type='" + m.type + "' urlPath='" + m.urlPath + "' fileName='" + m.fileName + "' isDefault='" + m.isDefault + "'/>";

					switch (m.type)
					{
						case AppConstants.MEDIATYPE_IMAGE:
							if(!this.isNPC){
								if (!this.isIconPicker) {
									//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
									if(k > numImageDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
									xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild(node);
									numImageDefaults+=k;
								}
							}
							else{
								if (!this.isIconPicker) {
									if(k > numIconDefaults) xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
									xmlData.appendChild(node);
									numIconDefaults+=k;
								}
							}
							break;
						case AppConstants.MEDIATYPE_AUDIO:
							if (!this.isIconPicker && !this.isNPC) {
								//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
								if(k > numAudioDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild(node);
								numAudioDefaults+=k;
							}
							break;
						case AppConstants.MEDIATYPE_VIDEO:
							if (!this.isIconPicker && !this.isNPC) {
								//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
								if(k > numVideoDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild(node);
								numVideoDefaults+=k;
							}
							break;
						case AppConstants.MEDIATYPE_ICON:
							if (this.isIconPicker) {
								trace("hello?:"+node);
								if(k > numIconDefaults) xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.appendChild(node);
								numIconDefaults+=k;
							}
							break;
						default:
							trace("Default statement reached in load media.  This SHOULD NOT HAPPEN.  The offending mediaId = '" + m.mediaId + "' and type = '" + m.type + "'");
							break;
					}
				}
			}

			//Add "Upload new" in between uploaded and default pictures
			if(k==0 && (!this.isIconPicker && !this.isNPC)){
				xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
				xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
				xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
			}
			else if(k == 0){
				xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
			}
		}
        trace("ItemEditorMediaPickerView: handleLoadingOfMediaIntoXML: Just finished loading Media Objects into XML.  Here's what the new XML looks like:");
        this.printXMLData();

        trace("Finished with handleLoadingOfMediaIntoXML().");
    }

    public function handleFault(obj:Object):void
    {
        trace("Fault called: " + obj.message);
        Alert.show("Error occurred: " + obj.message, "Problems Loading Media");
    }

    private function onNodeSelected(event:TreeBrowserEvent):void
    {
        if (!event.isBranch)
        {
            if (event.item.@label == AppConstants.MEDIATYPE_UPLOADNEW)
            {
                if (uploadFormVisable == false) {
					trace("ItemEditorMediaPickerView: onNodeSelected label == MEDIATYPE_UPLOADNEW, display form");
					selectButton.enabled = false;
					uploadFormVisable = true;
	                this.displayMediaUploader();
					PopUpManager.removePopUp(this);
				}
				else trace("ItemEditorMediaPickerView: ignored second TreeBrowserEvent == MEDIATYPE_UPLOADNEW");

            }
			else if(event.item.@label == AppConstants.MEDIATYPE_SEPARATOR){
				//Do Nothing
				trace("Separator Selected");
				selectButton.enabled = false;
			}
            else
            {
                trace("ItemEditorMediaPickerView: onNodeSelected is a media item");
				selectButton.enabled = true;
            }
        }
		else {
			selectButton.enabled = false;
		}
    }

    private function displayMediaUploader():void
    {
        trace("itemEditorMediaPickerPickerView: displayMediaUploader");
        mediaUploader = new ItemEditorMediaPickerUploadFormMX();
		mediaUploader.isIconPicker = this.isIconPicker;
		mediaUploader.delegate = this;
        PopUpManager.addPopUp(mediaUploader, AppUtils.getInstance().getMainView(), true);
		PopUpManager.centerPopUp(mediaUploader);
    }

	public function didUploadMedia(uploader:ItemEditorMediaPickerUploadFormMX, m:Media):void{
		trace("ItemEditorMediaPicker: didUploadMedia");

		uploadFormVisable = false;

		if (delegate.hasOwnProperty("didSelectMediaItem")){
			delegate.didSelectMediaItem(this, m);
		}

		PopUpManager.removePopUp(uploader);
	}

}
}

package org.arisgames.editor.components
{
import com.ninem.controls.TreeBrowser;
import com.ninem.events.TreeBrowserEvent;

import flash.events.MouseEvent;

import mx.containers.Panel;
import mx.controls.Alert;
import mx.controls.Button;
import mx.controls.DataGrid;
import mx.core.ClassFactory;
import mx.events.DynamicEvent;
import mx.events.FlexEvent;
import mx.managers.PopUpManager;
import mx.rpc.Responder;

import org.arisgames.editor.data.arisserver.Media;
import org.arisgames.editor.data.businessobjects.ObjectPaletteItemBO;
import org.arisgames.editor.models.GameModel;
import org.arisgames.editor.services.AppServices;
import org.arisgames.editor.util.AppConstants;
import org.arisgames.editor.util.AppDynamicEventManager;
import org.arisgames.editor.util.AppUtils;

public class ItemEditorMediaPickerView extends Panel
{
    // Data Object
	public var delegate:Object;
    private var objectPaletteItem:ObjectPaletteItemBO;
    private var isIconPicker:Boolean = false;
	private var isNPC:Boolean = false;
	private var uploadFormVisable:Boolean = false;

    // Media Data
    [Bindable] public var xmlData:XML;

    // GUI
    [Bindable] public var treeBrowser:TreeBrowser;
    [Bindable] public var closeButton:Button;
	[Bindable] public var selectButton:Button;

	// Tree Icons
	[Bindable]
	[Embed(source="assets/img/separator.png")]
	public var separatorIcon:Class; //If these names are changed, make sure it is also changed in AppConstants (MEDIATREEICON_*)

	[Bindable]
	[Embed(source="assets/img/upload.png")]
	public var uploadIcon:Class; //If these names are changed, make sure it is also changed in AppConstants (MEDIATREEICON_*)

	private var mediaUploader:ItemEditorMediaPickerUploadFormMX;

    /**
     * Constructor
     */
    public function ItemEditorMediaPickerView()
    {
        super();
        this.addEventListener(FlexEvent.CREATION_COMPLETE, handleInit);
    }

    private function handleInit(event:FlexEvent):void
    {
        trace("in ItemEditorMediaPickerView's handleInit");
        var cf:ClassFactory = new ClassFactory(ItemEditorMediaPickerCustomEditorMX);
        cf.properties = {objectPaletteItem: this.objectPaletteItem};
        treeBrowser.detailRenderer = cf;
        treeBrowser.addEventListener(TreeBrowserEvent.NODE_SELECTED, onNodeSelected);
        closeButton.addEventListener(MouseEvent.CLICK, handleCloseButton);
		selectButton.addEventListener(MouseEvent.CLICK, handleSelectButton);
        // Load Game's Media Into XML
        AppServices.getInstance().getMediaForGame(GameModel.getInstance().game.gameId, new Responder(handleLoadingOfMediaIntoXML, handleFault));
    }

    private function printXMLData():void
    {
        trace("XML Data = '" + xmlData.toXMLString() + "'");
    }

    public function setObjectPaletteItem(opi:ObjectPaletteItemBO):void
    {
        trace("setting objectPaletteItem with name = '" + opi.name + "' in ItemEditorPlaqueView");
        objectPaletteItem = opi;
    }

    public function isInIconPickerMode():Boolean
    {
        return isIconPicker;
    }

    public function setIsIconPicker(isIconPickerMode:Boolean):void
    {
        trace("setting isIconPicker mode to: " + isIconPickerMode);
        this.isIconPicker = isIconPickerMode;
        this.updateTitleBasedOnMode();
    }

    private function updateTitleBasedOnMode():void
    {
        if (isIconPicker)
        {
            title = "Icon Picker";
        }
        else
        {
            title = "Media Picker";
        }
    }

    private function handleCloseButton(evt:MouseEvent):void
    {
        trace("ItemEditorMediaPickerView: handleCloseButton()");
		PopUpManager.removePopUp(this);
    }

	private function handleSelectButton(evt:MouseEvent):void
	{
		trace("Select Button clicked...");

		var m:Media = new Media();
		m.mediaId = treeBrowser.selectedItem.@mediaId;
		m.name = treeBrowser.selectedItem.@name;
		m.type = treeBrowser.selectedItem.@type;
		m.urlPath = treeBrowser.selectedItem.@urlPath;
		m.fileName = treeBrowser.selectedItem.@fileName;
		m.isDefault = treeBrowser.selectedItem.@isDefault;

		if (delegate.hasOwnProperty("didSelectMediaItem")){
			delegate.didSelectMediaItem(this, m);
		}

		PopUpManager.removePopUp(this);
	}

    private function handleLoadingOfMediaIntoXML(obj:Object):void
    {
		if(this.objectPaletteItem.objectType == AppConstants.CONTENTTYPE_CHARACTER_DATABASE){
			this.isNPC = true;
			trace("This is an NPC, so disallow Audio/Visual media");
		}

        trace("In handleLoadingOfMediaIntoXML() Result called with obj = " + obj + "; Result = " + obj.result);
        if (obj.result.returnCode != 0)
        {
            trace("Bad loading of media attempt... let's see what happened.  Error = '" + obj.result.returnCodeDescription + "'");
            var msg:String = obj.result.returnCodeDescription;
            Alert.show("Error Was: " + msg, "Error While Loading Media");
			return;
        }

        //Init the XML Data
		xmlData = new XML('<nodes label="' + AppConstants.MEDIATYPE + '"/>');

		if (!this.isIconPicker && !this.isNPC) {
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_IMAGE + '"/>');
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_AUDIO + '"/>');
			xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_VIDEO + '"/>');
		}

		this.printXMLData();

		var media:Array = obj.result.data as Array;
        trace("Number of Media Objects returned from server = '" + media.length + "'");

		//Puts media items in xml readable format
		var numImageDefaults:Number = 0;
		var numAudioDefaults:Number = 0;
		var numVideoDefaults:Number = 0;
		var numIconDefaults:Number = 0;
		for (var k:Number = 0; k <= 1; k++) //Iterates over list twice; once for uploaded items, again for default
		{
			//Add "Upload new" in between uploaded and default pictures
			if(k==0 && (!this.isIconPicker && !this.isNPC)){
				xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
				xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
				xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
			}
			else if(k == 0){
				xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_UPLOADNEW + '" icon="'+ AppConstants.MEDIATREEICON_UPLOAD +'"/>');
			}
			for (var j:Number = 0; j < media.length; j++)
			{
				var o:Object = media[j];

				if((k==0 && !o.is_default) || (k==1 && o.is_default)){
					var m:Media = new Media();

					m.mediaId = o.media_id;
					m.name = o.name;
					m.type = o.type;
					m.urlPath = o.url_path;
					m.fileName = o.file_name;
					m.isDefault = o.is_default; //for both default files and uploaded files

					var node:String = "<node label='" + AppUtils.filterStringToXMLEscapeCharacters(m.name) + "' mediaId='" + m.mediaId + "' type='" + m.type + "' urlPath='" + m.urlPath + "' fileName='" + m.fileName + "' isDefault='" + m.isDefault + "'/>";

					switch (m.type)
					{
						case AppConstants.MEDIATYPE_IMAGE:
							if(!this.isNPC){
								if (!this.isIconPicker) {
									//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
									if(k > numImageDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
									xmlData.node.(@label == AppConstants.MEDIATYPE_IMAGE).appendChild(node);
									numImageDefaults+=k;
								}
							}
							else{
								if (!this.isIconPicker) {
									if(k > numIconDefaults) xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
									xmlData.appendChild(node);
									numIconDefaults+=k;
								}
							}
							break;
						case AppConstants.MEDIATYPE_AUDIO:
							if (!this.isIconPicker && !this.isNPC) {
								//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
								if(k > numAudioDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.node.(@label == AppConstants.MEDIATYPE_AUDIO).appendChild(node);
								numAudioDefaults+=k;
							}
							break;
						case AppConstants.MEDIATYPE_VIDEO:
							if (!this.isIconPicker && !this.isNPC) {
								//Only add "-----------" (AppConstants.MEDIATYPE_SEPARATOR) if there are also defaults to choose from
								if(k > numVideoDefaults) xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.node.(@label == AppConstants.MEDIATYPE_VIDEO).appendChild(node);
								numVideoDefaults+=k;
							}
							break;
						case AppConstants.MEDIATYPE_ICON:
							if (this.isIconPicker) {
								trace("hello?:"+node);
								if(k > numIconDefaults) xmlData.appendChild('<node label="' + AppConstants.MEDIATYPE_SEPARATOR + '" icon="'+ AppConstants.MEDIATREEICON_SEPARATOR +'"/>');
								xmlData.appendChild(node);
								numIconDefaults+=k;
							}
							break;
						default:
							trace("Default statement reached in load media.  This SHOULD NOT HAPPEN.  The offending mediaId = '" + m.mediaId + "' and type = '" + m.type + "'");
							break;
					}
				}
			}
		}
        trace("ItemEditorMediaPickerView: handleLoadingOfMediaIntoXML: Just finished loading Media Objects into XML.  Here's what the new XML looks like:");
        this.printXMLData();

        trace("Finished with handleLoadingOfMediaIntoXML().");
    }

    public function handleFault(obj:Object):void
    {
        trace("Fault called: " + obj.message);
        Alert.show("Error occurred: " + obj.message, "Problems Loading Media");
    }

    private function onNodeSelected(event:TreeBrowserEvent):void
    {
        if (!event.isBranch)
        {
            if (event.item.@label == AppConstants.MEDIATYPE_UPLOADNEW)
            {
                if (uploadFormVisable == false) {
					trace("ItemEditorMediaPickerView: onNodeSelected label == MEDIATYPE_UPLOADNEW, display form");
					selectButton.enabled = false;
					uploadFormVisable = true;
	                this.displayMediaUploader();
					PopUpManager.removePopUp(this);
				}
				else trace("ItemEditorMediaPickerView: ignored second TreeBrowserEvent == MEDIATYPE_UPLOADNEW");

            }
			else if(event.item.@label == AppConstants.MEDIATYPE_SEPARATOR){
				//Do Nothing
				trace("Separator Selected");
				selectButton.enabled = false;
			}
            else
            {
                trace("ItemEditorMediaPickerView: onNodeSelected is a media item");
				selectButton.enabled = true;
            }
        }
		else {
			selectButton.enabled = false;
		}
    }

    private function displayMediaUploader():void
    {
        trace("itemEditorMediaPickerPickerView: displayMediaUploader");
        mediaUploader = new ItemEditorMediaPickerUploadFormMX();
		mediaUploader.isIconPicker = this.isIconPicker;
		mediaUploader.delegate = this;
        PopUpManager.addPopUp(mediaUploader, AppUtils.getInstance().getMainView(), true);
		PopUpManager.centerPopUp(mediaUploader);
    }

	public function didUploadMedia(uploader:ItemEditorMediaPickerUploadFormMX, m:Media):void{
		trace("ItemEditorMediaPicker: didUploadMedia");

		uploadFormVisable = false;

		if (delegate.hasOwnProperty("didSelectMediaItem")){
			delegate.didSelectMediaItem(this, m);
		}

		PopUpManager.removePopUp(uploader);
	}

}
}

Put Upload New button at top of media picker list