package
{
  import org.flixel.FlxG;
  import org.flixel.FlxObject;
  import org.flixel.FlxPoint;
  import org.flixel.FlxSprite;
  import org.flixel.FlxTimer;
  import org.flixel.FlxU;
  import org.flixel.system.FlxAnim;

  // Player that has more complex running and jumping abilities.
  // It makes use of an animation delegate and has a simple state
  // tracking system. It also takes into account custom offsets.
  // It also allows for custom camera tracking.
  // This class is meant to be very configurable and has many hooks.

  public class Player extends FlxSprite
  {

    public var currently:uint;
    public static const STILL:uint = 0;
    public static const RUNNING:uint = 1;
    public static const LANDING:uint = 2;
    public static const RISING:uint = 101;
    public static const FALLING:uint = 102;

    // Note this is not always cleared.
    public var nextAction:uint;
    public static const NO_ACTION:uint = 0;
    public static const JUMP:uint = 1;
    public static const STOP:uint = 2;
    public static const START:uint = 3;

    public var controlled:Boolean;

    public var animDelegate:IPlayerAnimationDelegate;

    public var naturalForces:FlxPoint = new FlxPoint(1000, 500);
    public var pVelocity:FlxPoint;
    public var accelFactor:Number = 0.5;
    public var jumpMaxVelocity:FlxPoint;
    public var jumpAccel:FlxPoint;
    public var jumpAccelDecay:FlxPoint;
    // This should be small. Negative creates some drag.
    public var jumpAccelDecayFactor:Number = -0.001;
    public var oDrag:FlxPoint;
    public var jumpMinDuration:Number = 0.2;
    public var jumpMaxDuration:Number = 0.5;
    private var jumpTimer:FlxTimer;

    public var tailOffset:FlxPoint;
    public var headOffset:FlxPoint;

    public var cameraFocus:FlxObject;
    public var updateFocus:Boolean;
    // Basically, 1/n traveled per tween.
    public var cameraSpeed:Number = 30;

    public function Player(X:Number=0, Y:Number=0, SimpleGraphic:Class=null)
    {
      super(X, Y, SimpleGraphic);

      this.finished = true;

      this.currently = FALLING;
      this.nextAction = NO_ACTION;

      this.controlled = true;

      this.pVelocity = this.velocity;
      this.jumpMaxVelocity = new FlxPoint();
      this.jumpAccel = new FlxPoint();
      this.jumpAccelDecay = new FlxPoint();
      this.oDrag = new FlxPoint();
      jumpTimer = new FlxTimer();
      jumpTimer.stop();

      this.tailOffset = new FlxPoint();
      this.headOffset = new FlxPoint();

      this.cameraFocus = new FlxObject(this.x, this.y, this.width, this.height);
      this.updateFocus = true;

      FlxG.watch(this, 'currently', 'Currently');
      FlxG.watch(this, 'nextAction', 'Next Action');
      FlxG.watch(this.velocity, 'x', 'X Velocity');
      FlxG.watch(this.velocity, 'y', 'Y Velocity');
      FlxG.watch(this.acceleration, 'x', 'X Accel');
      FlxG.watch(this.acceleration, 'y', 'Y Accel');

    }

    public function init():void
    {
      this.drag.x = this.naturalForces.x;
      this.acceleration.y = this.naturalForces.y;
      this.oDrag.x = this.drag.x;
      this.jumpAccelDecay.x = this.oDrag.x * 2;
      // This prevents the "being dragged into the air" feeling.
      this.jumpAccelDecay.y = FlxG.framerate * this.jumpMinDuration;
      if (this.animDelegate == null)
      {
        throw new Error('Player animation delegate is required.');
      }
      this.animDelegate.playerIsFalling();
    }

    // Flixel Methods
    // --------------
    override public function update():void
    {
      if (!this.controlled) return;

      // Horizontal
      // - Revert to still. (Our acceleration updates funny.)
      if (!this.inMidAir())
      {
        this.acceleration.x = 0;
      }
      // - Basically handle switching direction, and running or being still
      // when not in the air. Note the player still runs in midair, but run
      // will behave differently.
      if (FlxG.keys.LEFT)
      {
        if (this.facing == FlxObject.RIGHT)
        {
          this.face(FlxObject.LEFT);
        }
        run(-1);
      }
      else if (FlxG.keys.RIGHT)
      {
        if (this.facing == FlxObject.LEFT)
        {
          this.face(FlxObject.RIGHT);
        }
        run();
      }
      else if (!this.inMidAir())
      {
        if (this.acceleration.x == 0)
        {
          this.nextAction = (this.velocity.x == 0) ? START : STOP;
        }
        if (this.velocity.x == 0)
        {
          this.currently = STILL;
        }
      }

      // Vertical
      // - Constrain jump and decay the jump force.
      if (!jumpTimer.finished)
      {
        // Negative is up, positive is down.
        this.velocity.y = FlxU.max(this.velocity.y, this.jumpMaxVelocity.y);
        this.acceleration.y += (this.naturalForces.y - this.acceleration.y) / this.jumpAccelDecay.y;
      }
      // - Basically handle starting and ending of jump, and starting of
      // falling. The tracking of pVelocity is an extra complexity. The
      // possibility of hitting the ceiling during jump is another one.
      if (
        FlxG.keys.justPressed('UP') && jumpTimer.finished &&
        this.isTouching(FlxObject.FLOOR)
      )
      {
        // Try to jump.
        jumpStart();
      }
      else if (FlxG.keys.justReleased('UP'))
      {
        jumpEnd();
      }
      else if (this.isTouching(FlxObject.UP) && this.currently == RISING)
      {
        // Start falling.
        this.currently = FALLING;
        this.pVelocity = null;
      }
      else if (this.velocity.y > 0)
      {
        if (this.currently == FALLING)
        {
          this.pVelocity = this.velocity;
        }
        else
        {
          this.currently = FALLING;
        }
      }
      // - Handle ending of falling.
      if (this.justFell())
      {
        this.currently = LANDING;
      }

      // - Handle focus.
      if (this.updateFocus)
      {
        this.cameraFocus.x += (this.x - this.cameraFocus.x) / this.cameraSpeed;
        this.cameraFocus.y += (this.y - this.cameraFocus.y) / this.cameraSpeed;
      }
    }
    // Animations get updated after movement.
    override protected function updateAnimation():void
    {
      this.animDelegate.playerWillUpdateAnimation();
      if (this.currently == STILL)
      {
        this.animDelegate.playerIsStill();
      }
      else if (this.currently == RUNNING)
      {
        this.animDelegate.playerIsRunning();
      }
      else if (this.currently == LANDING)
      {
        this.animDelegate.playerIsLanding();
      }
      else if (this.currently == RISING)
      {
        this.animDelegate.playerIsRising();
      }
      else if (this.currently == FALLING)
      {
        this.animDelegate.playerIsFalling();
      }
      super.updateAnimation();
      this.animDelegate.playerDidUpdateAnimation();
    }

    // Update API
    // ----------
    public function justFell():Boolean
    {
      var did:Boolean =
        this.justTouched(FlxObject.DOWN)
        && this.currently == FALLING
        && this.pVelocity != null;

      return did;
    }
    public function inMidAir():Boolean
    {
      return this.currently >= RISING;
    }
    public function face(direction:uint):void
    {
      if (
        this.velocity.x != 0 &&
        this.nextAction != STOP &&
        this.facing != direction
      )
      {
        this.nextAction = STOP;
        if (!this.inMidAir())
        {
          this.animDelegate.playerWillStop();
        }
      }
      else if (this.finished)
      {
        this.nextAction = START;
        if (!this.inMidAir())
        {
          this.animDelegate.playerWillStart();
        }
        if (direction == FlxObject.RIGHT)
        {
          this.offset.x = this.tailOffset.x;
          this.facing = FlxObject.RIGHT;
        }
        else if (direction == FlxObject.LEFT)
        {
          this.offset.x = 0;
          this.facing = FlxObject.LEFT;
        }
      }
    }
    public function currentAnimation():FlxAnim
    {
      return this._curAnim;
    }

    // Update Routines
    // ---------------
    private function run(direction:int=1):void
    {
      var factor:Number = this.accelFactor;
      if (this.inMidAir())
      {
        factor = this.jumpAccelDecayFactor;
      }
      else if (this.currently != RUNNING)
      {
        this.currently = RUNNING;
      }
      this.acceleration.x = this.drag.x * factor * direction;
    }
    private function jumpStart():void
    {
      var jumpMaxDuration:Number = this.jumpMinDuration +
        FlxU.abs(this.velocity.x/this.maxVelocity.x) * (this.jumpMaxDuration-this.jumpMinDuration);
      this.animDelegate.playerWillJump();
      this.y--;
      this.currently = RISING;
      this.acceleration.y = this.jumpAccel.y;
      this.acceleration.x = 0;
      this.drag.x = this.jumpAccelDecay.x;
      jumpTimer.start(jumpMaxDuration, 1,
        function(timer:FlxTimer):void {
          jumpEnd();
        });
    }
    private function jumpEnd():void
    {
      this.acceleration.y = this.naturalForces.y;
      this.drag.x = this.oDrag.x;
      jumpTimer.stop();
    }
  }
}

package
{
  import org.flixel.FlxG;
  import org.flixel.FlxObject;
  import org.flixel.FlxPoint;
  import org.flixel.FlxSprite;
  import org.flixel.FlxTimer;
  import org.flixel.FlxU;
  import org.flixel.system.FlxAnim;

  // Player that has more complex running and jumping abilities.
  // It makes use of an animation delegate and has a simple state
  // tracking system. It also takes into account custom offsets.
  // It also allows for custom camera tracking.
  // This class is meant to be very configurable and has many hooks.

  public class Player extends FlxSprite
  {

    public var currently:uint;
    public static const STILL:uint = 0;
    public static const RUNNING:uint = 1;
    public static const LANDING:uint = 2;
    public static const RISING:uint = 101;
    public static const FALLING:uint = 102;

    // Note this is not always cleared.
    public var nextAction:uint;
    public static const NO_ACTION:uint = 0;
    public static const JUMP:uint = 1;
    public static const STOP:uint = 2;
    public static const START:uint = 3;

    public var controlled:Boolean;

    public var animDelegate:IPlayerAnimationDelegate;

    public var naturalForces:FlxPoint = new FlxPoint(1000, 500);
    public var pVelocity:FlxPoint;
    public var accelFactor:Number = 0.5;
    public var jumpMaxVelocity:FlxPoint;
    public var jumpAccel:FlxPoint;
    public var jumpAccelDecay:FlxPoint;
    // This should be small. Negative creates some drag.
    public var jumpAccelDecayFactor:Number = -0.001;
    public var oDrag:FlxPoint;
    public var jumpMinDuration:Number = 0.2;
    public var jumpMaxDuration:Number = 0.5;
    private var jumpTimer:FlxTimer;

    public var tailOffset:FlxPoint;
    public var headOffset:FlxPoint;

    public var cameraFocus:FlxObject;
    public var updateFocus:Boolean;
    // Basically, 1/n traveled per tween.
    public var cameraSpeed:Number = 30;

    public function Player(X:Number=0, Y:Number=0, SimpleGraphic:Class=null)
    {
      super(X, Y, SimpleGraphic);

      this.finished = true;

      this.currently = FALLING;
      this.nextAction = NO_ACTION;

      this.controlled = true;

      this.pVelocity = this.velocity;
      this.jumpMaxVelocity = new FlxPoint();
      this.jumpAccel = new FlxPoint();
      this.jumpAccelDecay = new FlxPoint();
      this.oDrag = new FlxPoint();
      jumpTimer = new FlxTimer();
      jumpTimer.stop();

      this.tailOffset = new FlxPoint();
      this.headOffset = new FlxPoint();

      this.cameraFocus = new FlxObject(this.x, this.y, this.width, this.height);
      this.updateFocus = true;

      FlxG.watch(this, 'currently', 'Currently');
      FlxG.watch(this, 'nextAction', 'Next Action');
      FlxG.watch(this.velocity, 'x', 'X Velocity');
      FlxG.watch(this.velocity, 'y', 'Y Velocity');
      FlxG.watch(this.acceleration, 'x', 'X Accel');
      FlxG.watch(this.acceleration, 'y', 'Y Accel');

    }

    public function init():void
    {
      this.drag.x = this.naturalForces.x;
      this.acceleration.y = this.naturalForces.y;
      this.oDrag.x = this.drag.x;
      this.jumpAccelDecay.x = this.oDrag.x * 2;
      // This prevents the "being dragged into the air" feeling.
      this.jumpAccelDecay.y = FlxG.framerate * this.jumpMinDuration;
      if (this.animDelegate == null)
      {
        throw new Error('Player animation delegate is required.');
      }
      this.animDelegate.playerIsFalling();
    }

    // Flixel Methods
    // --------------
    override public function update():void
    {
      if (!this.controlled) return;

      // Horizontal
      // - Revert to still. (Our acceleration updates funny.)
      if (!this.inMidAir())
      {
        this.acceleration.x = 0;
      }
      // - Basically handle switching direction, and running or being still
      // when not in the air. Note the player still runs in midair, but run
      // will behave differently.
      if (FlxG.keys.LEFT)
      {
        if (this.facing == FlxObject.RIGHT)
        {
          this.face(FlxObject.LEFT);
        }
        run(-1);
      }
      else if (FlxG.keys.RIGHT)
      {
        if (this.facing == FlxObject.LEFT)
        {
          this.face(FlxObject.RIGHT);
        }
        run();
      }
      else if (!this.inMidAir())
      {
        if (this.acceleration.x == 0)
        {
          this.nextAction = (this.velocity.x == 0) ? START : STOP;
        }
        if (this.velocity.x == 0)
        {
          this.currently = STILL;
        }
      }

      // Vertical
      // - Constrain jump and decay the jump force.
      if (!jumpTimer.finished)
      {
        // Negative is up, positive is down.
        this.velocity.y = FlxU.max(this.velocity.y, this.jumpMaxVelocity.y);
        this.acceleration.y += (this.naturalForces.y - this.acceleration.y) / this.jumpAccelDecay.y;
      }
      // - Basically handle starting and ending of jump, and starting of
      // falling. The tracking of pVelocity is an extra complexity. The
      // possibility of hitting the ceiling during jump is another one.
      if (
        FlxG.keys.justPressed('UP') && jumpTimer.finished &&
        this.isTouching(FlxObject.FLOOR)
      )
      {
        // Try to jump.
        jumpStart();
      }
      else if (FlxG.keys.justReleased('UP'))
      {
        jumpEnd();
      }
      else if (this.isTouching(FlxObject.UP) && this.currently == RISING)
      {
        // Start falling.
        this.currently = FALLING;
        this.pVelocity = null;
      }
      else if (this.velocity.y > 0)
      {
        if (this.currently == FALLING)
        {
          this.pVelocity = this.velocity;
        }
        else
        {
          this.currently = FALLING;
        }
      }
      // - Handle ending of falling.
      if (this.justFell())
      {
        this.currently = LANDING;
      }

      // - Handle focus.
      if (this.updateFocus)
      {
        this.cameraFocus.x += FlxU.round((this.x - this.cameraFocus.x) / this.cameraSpeed);
        this.cameraFocus.y += FlxU.round((this.y - this.cameraFocus.y) / this.cameraSpeed);
      }
    }
    // Animations get updated after movement.
    override protected function updateAnimation():void
    {
      this.animDelegate.playerWillUpdateAnimation();
      if (this.currently == STILL)
      {
        this.animDelegate.playerIsStill();
      }
      else if (this.currently == RUNNING)
      {
        this.animDelegate.playerIsRunning();
      }
      else if (this.currently == LANDING)
      {
        this.animDelegate.playerIsLanding();
      }
      else if (this.currently == RISING)
      {
        this.animDelegate.playerIsRising();
      }
      else if (this.currently == FALLING)
      {
        this.animDelegate.playerIsFalling();
      }
      super.updateAnimation();
      this.animDelegate.playerDidUpdateAnimation();
    }

    // Update API
    // ----------
    public function justFell():Boolean
    {
      var did:Boolean =
        this.justTouched(FlxObject.DOWN)
        && this.currently == FALLING
        && this.pVelocity != null;

      return did;
    }
    public function inMidAir():Boolean
    {
      return this.currently >= RISING;
    }
    public function face(direction:uint):void
    {
      if (
        this.velocity.x != 0 &&
        this.nextAction != STOP &&
        this.facing != direction
      )
      {
        this.nextAction = STOP;
        if (!this.inMidAir())
        {
          this.animDelegate.playerWillStop();
        }
      }
      else if (this.finished)
      {
        this.nextAction = START;
        if (!this.inMidAir())
        {
          this.animDelegate.playerWillStart();
        }
        if (direction == FlxObject.RIGHT)
        {
          this.offset.x = this.tailOffset.x;
          this.facing = FlxObject.RIGHT;
        }
        else if (direction == FlxObject.LEFT)
        {
          this.offset.x = 0;
          this.facing = FlxObject.LEFT;
        }
      }
    }
    public function currentAnimation():FlxAnim
    {
      return this._curAnim;
    }

    // Update Routines
    // ---------------
    private function run(direction:int=1):void
    {
      var factor:Number = this.accelFactor;
      if (this.inMidAir())
      {
        factor = this.jumpAccelDecayFactor;
      }
      else if (this.currently != RUNNING)
      {
        this.currently = RUNNING;
      }
      this.acceleration.x = this.drag.x * factor * direction;
    }
    private function jumpStart():void
    {
      var jumpMaxDuration:Number = this.jumpMinDuration +
        FlxU.abs(this.velocity.x/this.maxVelocity.x) * (this.jumpMaxDuration-this.jumpMinDuration);
      this.animDelegate.playerWillJump();
      this.y--;
      this.currently = RISING;
      this.acceleration.y = this.jumpAccel.y;
      this.acceleration.x = 0;
      this.drag.x = this.jumpAccelDecay.x;
      jumpTimer.start(jumpMaxDuration, 1,
        function(timer:FlxTimer):void {
          jumpEnd();
        });
    }
    private function jumpEnd():void
    {
      this.acceleration.y = this.naturalForces.y;
      this.drag.x = this.oDrag.x;
      jumpTimer.stop();
    }
  }
}

Fix bug with camera follow stopping glitchiness.


package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
           crossdomain policy. */
        private const DEFAULT_TOR_ADDR:Object = {
            host: "173.255.221.44",
            port: 9001
        };
        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "173.255.221.44",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 1;

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

        // Socket to facilitator.
        private var s_f:Socket;

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

        private var fac_addr:Object;

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

        /* Badge with a client counter */
        [Embed(source="badge_con_counter.png")]
        private var BadgeImage:Class;
        private var client_count_tf:TextField;
        private var client_count_fmt:TextFormat;

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

        public function update_client_count():void
        {
            if (String(total_proxy_pairs).length == 1)
                client_count_tf.text = "0" + String(total_proxy_pairs);
            else
                client_count_tf.text = String(total_proxy_pairs);
        }

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

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

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

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

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

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

            puts("Parameters loaded.");

            if (this.loaderInfo.parameters["debug"])
                addChild(output_text);
            else {
                addChild(new BadgeImage());
                /* Tried unsuccessfully to add counter to badge. */
                /* For now, need two addChilds :( */
                addChild(client_count_tf);
            }

            fac_spec = this.loaderInfo.parameters["facilitator"];
            if (fac_spec) {
                puts("Facilitator spec: \"" + fac_spec + "\"");
                fac_addr = parse_addr_spec(fac_spec);
                if (!fac_addr) {
                    puts("Error: Facilitator spec must be in the form \"host:port\".");
                    return;
                }
            } else {
                fac_addr = DEFAULT_FACILITATOR_ADDR;
            }

            main();
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function main():void
        {
            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            s_f = new Socket();

            s_f.addEventListener(Event.CONNECT, fac_connected);
            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Facilitator: closed connection.");
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
            });
            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
            s_f.connect(fac_addr.host, fac_addr.port);
        }

        private function fac_connected(e:Event):void
        {
            puts("Facilitator: connected.");

            s_f.addEventListener(ProgressEvent.SOCKET_DATA, fac_data);

            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
        }

        private function fac_data(e:ProgressEvent):void
        {
            var client_spec:String;
            var client_addr:Object;
            var proxy_pair:Object;

            client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8");
            puts("Facilitator: got \"" + client_spec + "\".");

            client_addr = parse_addr_spec(client_spec);
            if (!client_addr) {
                puts("Error: Client spec must be in the form \"host:port\".");
                return;
            }

            num_proxy_pairs++;
            total_proxy_pairs++;
            proxy_pair = new ProxyPair(this, client_addr, DEFAULT_TOR_ADDR);
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
            });
            proxy_pair.connect();

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

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

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

            return addr;
        }
    }
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.Socket;
import flash.utils.ByteArray;

/* An instance of a client-relay connection. */
class ProxyPair extends EventDispatcher
{
    // Address ({host, port}) of client.
    private var addr_c:Object;
    // Address ({host, port}) of relay.
    private var addr_r:Object;

    // Socket to client.
    private var s_c:Socket;
    // Socket to relay.
    private var s_r:Socket;

    // Parent swfcat, for UI updates.
    private var ui:swfcat;

    public function log(msg:String):void
    {
        ui.puts(id() + ": " + msg)
    }

    // String describing this pair for output.
    public function id():String
    {
        return "<" + this.addr_c.host + ":" + this.addr_c.port +
            "," + this.addr_r.host + ":" + this.addr_r.port + ">";
    }

    public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object)
    {
        this.ui = ui;
        this.addr_c = addr_c;
        this.addr_r = addr_r;
    }

    public function connect():void
    {
        s_r = new Socket();

        s_r.addEventListener(Event.CONNECT, tor_connected);
        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Tor: closed.");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Tor: I/O error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Tor: security error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });

        log("Tor: connecting to " + addr_r.host + ":" + addr_r.port + ".");
        s_r.connect(addr_r.host, addr_r.port);
    }

    private function tor_connected(e:Event):void
    {
        log("Tor: connected.");

        s_c = new Socket();

        s_c.addEventListener(Event.CONNECT, client_connected);
        s_c.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Client: closed.");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Client: I/O error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Client: security error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });

        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
        s_c.connect(addr_c.host, addr_c.port);
    }

    private function client_connected(e:Event):void
    {
        log("Client: connected.");

        s_r.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_r.readBytes(bytes, 0, e.bytesLoaded);
            log("Tor: read " + bytes.length + ".");
            s_c.writeBytes(bytes);
        });
        s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
            var bytes:ByteArray = new ByteArray();
            s_c.readBytes(bytes, 0, e.bytesLoaded);
            log("Client: read " + bytes.length + ".");
            s_r.writeBytes(bytes);
        });
    }
}

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;
    import flash.utils.setTimeout;

    public class swfcat extends Sprite
    {
        /* David's relay (nickname 3VXRyxz67OeRoqHn) that also serves a
           crossdomain policy. */
        private const DEFAULT_TOR_ADDR:Object = {
            host: "173.255.221.44",
            port: 9001
        };
        private const DEFAULT_FACILITATOR_ADDR:Object = {
            host: "173.255.221.44",
            port: 9002
        };

        private const MAX_NUM_PROXY_PAIRS:uint = 1;

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

        // Bytes per second.
        public const RATE_LIMIT:Number = 10 * 1024;
        // Seconds.
        private const RATE_LIMIT_HISTORY:Number = 5.0;

        // Socket to facilitator.
        private var s_f:Socket;

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

        private var fac_addr:Object;

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

        public var rate_limit:RateLimit;

        /* Badge with a client counter */
        [Embed(source="badge_con_counter.png")]
        private var BadgeImage:Class;
        private var client_count_tf:TextField;
        private var client_count_fmt:TextFormat;

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

        public function update_client_count():void
        {
            if (String(total_proxy_pairs).length == 1)
                client_count_tf.text = "0" + String(total_proxy_pairs);
            else
                client_count_tf.text = String(total_proxy_pairs);
        }

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

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

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

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

            rate_limit = new RateLimit(RATE_LIMIT * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);

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

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

            puts("Parameters loaded.");

            if (this.loaderInfo.parameters["debug"])
                addChild(output_text);
            else {
                addChild(new BadgeImage());
                /* Tried unsuccessfully to add counter to badge. */
                /* For now, need two addChilds :( */
                addChild(client_count_tf);
            }

            fac_spec = this.loaderInfo.parameters["facilitator"];
            if (fac_spec) {
                puts("Facilitator spec: \"" + fac_spec + "\"");
                fac_addr = parse_addr_spec(fac_spec);
                if (!fac_addr) {
                    puts("Error: Facilitator spec must be in the form \"host:port\".");
                    return;
                }
            } else {
                fac_addr = DEFAULT_FACILITATOR_ADDR;
            }

            main();
        }

        /* The main logic begins here, after start-up issues are taken care of. */
        private function main():void
        {
            if (num_proxy_pairs >= MAX_NUM_PROXY_PAIRS) {
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
                return;
            }

            s_f = new Socket();

            s_f.addEventListener(Event.CONNECT, fac_connected);
            s_f.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Facilitator: closed connection.");
                setTimeout(main, FACILITATOR_POLL_INTERVAL);
            });
            s_f.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Facilitator: I/O error: " + e.text + ".");
            });
            s_f.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Facilitator: security error: " + e.text + ".");
            });

            puts("Facilitator: connecting to " + fac_addr.host + ":" + fac_addr.port + ".");
            s_f.connect(fac_addr.host, fac_addr.port);
        }

        private function fac_connected(e:Event):void
        {
            puts("Facilitator: connected.");

            s_f.addEventListener(ProgressEvent.SOCKET_DATA, fac_data);

            s_f.writeUTFBytes("GET / HTTP/1.0\r\n\r\n");
        }

        private function fac_data(e:ProgressEvent):void
        {
            var client_spec:String;
            var client_addr:Object;
            var proxy_pair:Object;

            client_spec = s_f.readMultiByte(e.bytesLoaded, "utf-8");
            puts("Facilitator: got \"" + client_spec + "\".");

            client_addr = parse_addr_spec(client_spec);
            if (!client_addr) {
                puts("Error: Client spec must be in the form \"host:port\".");
                return;
            }

            num_proxy_pairs++;
            total_proxy_pairs++;
            proxy_pair = new ProxyPair(this, client_addr, DEFAULT_TOR_ADDR);
            proxy_pair.addEventListener(Event.COMPLETE, function(e:Event):void {
                proxy_pair.log("Complete.");
                num_proxy_pairs--;
            });
            proxy_pair.connect();

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

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

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

            return addr;
        }
    }
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.Socket;
import flash.utils.ByteArray;
import flash.utils.clearTimeout;
import flash.utils.getTimer;
import flash.utils.setTimeout;

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

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

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

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

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

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

        return amount <= capacity;
    }

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

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

/* An instance of a client-relay connection. */
class ProxyPair extends EventDispatcher
{
    // Address ({host, port}) of client.
    private var addr_c:Object;
    // Address ({host, port}) of relay.
    private var addr_r:Object;

    // Socket to client.
    private var s_c:Socket;
    // Socket to relay.
    private var s_r:Socket;

    // Parent swfcat, for UI updates and rate meter.
    private var ui:swfcat;

    // Pending byte read counts for relay and client sockets.
    private var r2c_schedule:Array;
    private var c2r_schedule:Array;
    // Callback id.
    private var flush_id:uint;

    public function log(msg:String):void
    {
        ui.puts(id() + ": " + msg)
    }

    // String describing this pair for output.
    public function id():String
    {
        return "<" + this.addr_c.host + ":" + this.addr_c.port +
            "," + this.addr_r.host + ":" + this.addr_r.port + ">";
    }

    public function ProxyPair(ui:swfcat, addr_c:Object, addr_r:Object)
    {
        this.ui = ui;
        this.addr_c = addr_c;
        this.addr_r = addr_r;

        this.c2r_schedule = [];
        this.r2c_schedule = [];
    }

    public function connect():void
    {
        s_r = new Socket();

        s_r.addEventListener(Event.CONNECT, tor_connected);
        s_r.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Tor: closed.");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_r.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Tor: I/O error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_r.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Tor: security error: " + e.text + ".");
            if (s_c.connected)
                s_c.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });

        log("Tor: connecting to " + addr_r.host + ":" + addr_r.port + ".");
        s_r.connect(addr_r.host, addr_r.port);
    }

    private function tor_connected(e:Event):void
    {
        log("Tor: connected.");

        s_c = new Socket();

        s_c.addEventListener(Event.CONNECT, client_connected);
        s_c.addEventListener(Event.CLOSE, function (e:Event):void {
            log("Client: closed.");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
            log("Client: I/O error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });
        s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
            log("Client: security error: " + e.text + ".");
            if (s_r.connected)
                s_r.close();
            dispatchEvent(new Event(Event.COMPLETE));
        });

        log("Client: connecting to " + addr_c.host + ":" + addr_c.port + ".");
        s_c.connect(addr_c.host, addr_c.port);
    }

    private function relay_to_client(e:ProgressEvent):void
    {
        r2c_schedule.push(e.bytesLoaded);
        flush();
    }

    private function client_to_relay(e:ProgressEvent):void
    {
        c2r_schedule.push(e.bytesLoaded);
        flush();
    }

    private function client_connected(e:Event):void
    {
        log("Client: connected.");

        s_r.addEventListener(ProgressEvent.SOCKET_DATA, relay_to_client);
        s_c.addEventListener(ProgressEvent.SOCKET_DATA, client_to_relay);
    }

    private function transfer_chunk(s_from:Socket, s_to:Socket, n:uint,
        label:String):void
    {
        var bytes:ByteArray;

        bytes = new ByteArray();
        s_from.readBytes(bytes, 0, n);
        s_to.writeBytes(bytes);
        ui.rate_limit.update(n);
        log(label + ": read " + bytes.length + ".");
    }

    /* Send as much data as the rate limit currently allows. */
    private function flush():void
    {
        while (!ui.rate_limit.is_limited() &&
               (r2c_schedule.length > 0 || c2r_schedule.length > 0)) {
            if (r2c_schedule.length > 0)
                transfer_chunk(s_r, s_c, r2c_schedule.shift(), "Tor");
            if (c2r_schedule.length > 0)
                transfer_chunk(s_c, s_r, c2r_schedule.shift(), "Client");
        }
        if (flush_id)
            clearTimeout(flush_id);
        flush_id = undefined;
        /* Call again when safe, if necessary. */
        if (r2c_schedule.length > 0 || c2r_schedule.length > 0)
            flush_id = setTimeout(flush, ui.rate_limit.when() * 1000);
    }
}

Add a leaky bucket bandwidth limiter.

Add a leaky bucket bandwidth limiter.


//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.flex {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;

import flash.geom.Rectangle;

import mx.controls.Menu;
import mx.core.mx_internal;
import mx.core.Application;
import mx.core.ScrollPolicy;
import mx.events.MenuEvent;

import mx.utils.ObjectUtil;

import com.dougmccune.controls.ScrollableArrowMenu;

import com.threerings.util.CommandEvent;

use namespace mx_internal;

/**
 * A pretty standard menu that can submit CommandEvents if menu items
 * have "command" and possibly "arg" properties. Commands are submitted to
 * controllers for processing. Alternatively, you may specify
 * "callback" properties that specify a function closure to call, with the
 * "arg" property containing either a single arg or an array of args.
 *
 * Example dataProvider array:
 * [ { label: "Go home", icon: homeIconClass,
 *     command: Controller.GO_HOME, arg: homeId },
 *   { type: "separator"},
     { label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
 *   { label: "Other places", children: subMenuArray }
 * ];
 *
 * See "Defining menu structure and data" in the Flex manual for the
 * full list.
 */
public class CommandMenu extends ScrollableArrowMenu
{
    public function CommandMenu ()
    {
        super();
        verticalScrollPolicy = ScrollPolicy.OFF;

        addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
    }

    /**
     * Factory method to create a command menu.
     *
     * @param items an array of menu items.
     */
    public static function createMenu (items :Array) :CommandMenu
    {
        var menu :CommandMenu = new CommandMenu();
        menu.owner = DisplayObjectContainer(Application.application);
        menu.tabEnabled = false;
        menu.showRoot = true;
        Menu.popUpMenu(menu, null, items);
        return menu;
    }

    /**
     * Actually pop up the menu. This can be used instead of show().
     */
    public function popUp (
        trigger :DisplayObject, popUpwards :Boolean = false) :void
    {
        var r :Rectangle = trigger.getBounds(trigger.stage);

        if (popUpwards) {
            show(r.x, 0);
            // then, reposition the y once we know our size
            y = r.y - getExplicitOrMeasuredHeight();

        } else {
            // position it below the trigger
            show(r.x, r.y + r.height);
        }
    }

    /**
     * Just like our superclass's show(), except that when invoked
     * with no args, causes the menu to show at the current mouse location
     * instead of the top-left corner of the application.
     */
    override public function show (xShow :Object = null, yShow :Object = null) :void
    {
        if (xShow == null) {
            xShow = DisplayObject(Application.application).mouseX;
        }
        if (yShow == null) {
            yShow = DisplayObject(Application.application).mouseY;
        }
        super.show(xShow, yShow);
    }

    /**
     * Callback for MenuEvent.ITEM_CLICK.
     */
    protected function itemClicked (event :MenuEvent) :void
    {
        var arg :Object = getItemProp(event.item, "arg");
        var cmdOrFn :Object = getItemProp(event.item, "command");
        if (cmdOrFn == null) {
            cmdOrFn = getItemProp(event.item, "callback");
        }
        if (cmdOrFn != null) {
            event.stopImmediatePropagation();
            CommandEvent.dispatch(mx_internal::parentDisplayObject, cmdOrFn, arg)
        }
        // else: no warning. There may be non-command menu items mixed in.
    }

    /**
     * Get the specified property for the specified item, if any.
     * Somewhat similar to bits in the DefaultDataDescriptor.
     */
    protected function getItemProp (item :Object, prop :String) :Object
    {
        try {
            if (item is XML) {
                return String((item as XML).attribute(prop));

            } else if (prop in item) {
                return item[prop];
            }

        } catch (e :Error) {
            // alas; fall through
        }
        return null;
    }
}
}

//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// 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.flex {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;

import flash.geom.Rectangle;

import mx.controls.Menu;
import mx.core.mx_internal;
import mx.core.Application;
import mx.core.ScrollPolicy;
import mx.events.MenuEvent;

import mx.utils.ObjectUtil;

import flexlib.controls.ScrollableArrowMenu;

import com.threerings.util.CommandEvent;

use namespace mx_internal;

/**
 * A pretty standard menu that can submit CommandEvents if menu items
 * have "command" and possibly "arg" properties. Commands are submitted to
 * controllers for processing. Alternatively, you may specify
 * "callback" properties that specify a function closure to call, with the
 * "arg" property containing either a single arg or an array of args.
 *
 * Example dataProvider array:
 * [ { label: "Go home", icon: homeIconClass,
 *     command: Controller.GO_HOME, arg: homeId },
 *   { type: "separator"},
     { label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
 *   { label: "Other places", children: subMenuArray }
 * ];
 *
 * See "Defining menu structure and data" in the Flex manual for the
 * full list.
 */
public class CommandMenu extends ScrollableArrowMenu
{
    public function CommandMenu ()
    {
        super();
        verticalScrollPolicy = ScrollPolicy.OFF;

        addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
    }

    /**
     * Factory method to create a command menu.
     *
     * @param items an array of menu items.
     */
    public static function createMenu (items :Array) :CommandMenu
    {
        var menu :CommandMenu = new CommandMenu();
        menu.owner = DisplayObjectContainer(Application.application);
        menu.tabEnabled = false;
        menu.showRoot = true;
        Menu.popUpMenu(menu, null, items);
        return menu;
    }

    /**
     * Actually pop up the menu. This can be used instead of show().
     */
    public function popUp (
        trigger :DisplayObject, popUpwards :Boolean = false) :void
    {
        var r :Rectangle = trigger.getBounds(trigger.stage);

        if (popUpwards) {
            show(r.x, 0);
            // then, reposition the y once we know our size
            y = r.y - getExplicitOrMeasuredHeight();

        } else {
            // position it below the trigger
            show(r.x, r.y + r.height);
        }
    }

    /**
     * Just like our superclass's show(), except that when invoked
     * with no args, causes the menu to show at the current mouse location
     * instead of the top-left corner of the application.
     */
    override public function show (xShow :Object = null, yShow :Object = null) :void
    {
        if (xShow == null) {
            xShow = DisplayObject(Application.application).mouseX;
        }
        if (yShow == null) {
            yShow = DisplayObject(Application.application).mouseY;
        }
        super.show(xShow, yShow);
    }

    /**
     * Callback for MenuEvent.ITEM_CLICK.
     */
    protected function itemClicked (event :MenuEvent) :void
    {
        var arg :Object = getItemProp(event.item, "arg");
        var cmdOrFn :Object = getItemProp(event.item, "command");
        if (cmdOrFn == null) {
            cmdOrFn = getItemProp(event.item, "callback");
        }
        if (cmdOrFn != null) {
            event.stopImmediatePropagation();
            CommandEvent.dispatch(mx_internal::parentDisplayObject, cmdOrFn, arg)
        }
        // else: no warning. There may be non-command menu items mixed in.
    }

    /**
     * Get the specified property for the specified item, if any.
     * Somewhat similar to bits in the DefaultDataDescriptor.
     */
    protected function getItemProp (item :Object, prop :String) :Object
    {
        try {
            if (item is XML) {
                return String((item as XML).attribute(prop));

            } else if (prop in item) {
                return item[prop];
            }

        } catch (e :Error) {
            // alas; fall through
        }
        return null;
    }
}
}

Extend the flexlib scrolling menu.

Extend the flexlib scrolling menu.

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


/*
 * Copyright (c) 2011, wersling.com All rights reserved.
 */
package net.manaca.loaderqueue
{
import flash.events.EventDispatcher;
import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;

/**
 * 任务队列全部完成后派发
 * @eventType net.manaca.loaderqueue.LoaderQueueEvent.TASK_QUEUE_COMPLETED
 */
[Event(name="taskQueueCompleted",
    type="net.manaca.loaderqueue.LoaderQueueEvent")]

/**
 * 任务队列及下载队列的管理器
 * @example
 * var urlLoader:URLLoaderAdapter =
 *                           new URLLoaderAdapter(4,"http://ggg.ggg.com/a.swf");
 * urlLoader.addEventListener(LoaderQueueEvent.TASK_COMPLETED,
 *                                                           onLoadedCompleted);
 * var loaderQueue:LoaderQueue = new LoaderQueue();
 * loaderQueue.addItem(urlLoader);
 *
 * @see net.manaca.loaderqueue.adapter#URLLoaderAdapter
 *
 * @author Austin
 * @update sean
 */
public class LoaderQueue extends EventDispatcher implements ILoaderQueue
{
    //==========================================================================
    //  Constructor
    //==========================================================================
    /**
     * Constructs a new Application instance.
     * @param threadLimit 下载线程数的上限。默认2
     * @param delay 下载队列排序延迟时间，单位毫秒。默认500毫秒
     * @param jumpQueueIfCached 如果该url文件已经加载过，是否跳过队列直接加载。
     * 该参数通过加载文件的url来判断。默认为true
     */
    public function LoaderQueue(threadLimit:uint = 2, delay:int = 500,
                                jumpQueueIfCached:Boolean = true)
    {
        this.threadLimit = threadLimit;
        this.delay = delay;
        this.jumpQueueIfCached = jumpQueueIfCached;

        timeOutToSort = new Timer(delay, 1);
        timeOutToSort.addEventListener(TimerEvent.TIMER_COMPLETE,
            timeOutToSort_timerCompleteHandler);
    }

    //==========================================================================
    //  Variables
    //==========================================================================
    /**
     * 下载队列排序延迟时间，单位毫秒
     */
    private var delay:int;

    /**
     * 缓存已经加载过的url地址
     */
    private var cacheMap:Object = {};
    //==========================================================================
    //  Properties
    //==========================================================================
    /**
     * 如果该url文件已经加载过，是否跳过队列直接加载。
     * 该参数通过加载文件的url来判断。默认为true
     * @default true
     */
    public var jumpQueueIfCached:Boolean = true;

    /**
     * 最大线程数上限值
     */
    public var threadLimit:uint;

    /**
     * 等级排序时是否使用倒序(如4,3,2,1)
     */
    public var reversePriority:Boolean = false;

    /**
     * 用于保存所有的下载项目
     * p.s:已下载的会被清除
     * @private
     */
    private var loaderDict:Dictionary/* of ILoaderAdapter */= new Dictionary();

    /**
     * 用于决定下载的等级的顺序
     * @private
     */
    private var loaderPriorityLib:Array /* of uint */ = [];

    /**
     * 用于保存目前正在下载的对象
     * @private
     */
    private var threadLib:Array /* of ILoaderAdapter */ = [];

    /**
     * 用于在添加新的Item后延迟触发排序.
     */
    private var timeOutToSort:Timer;

    //==========================================================================
    //  Methods
    //==========================================================================
    /**
     * @inheritDoc
     */
    public function addItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_WAITING;
        //如果ignoreCache为true,则检查是否已经加载过，如果加载过，则不添加到队列，
        //而是直接开始加载
        if(jumpQueueIfCached)
        {
            if(cacheMap[loaderAdapter.url])
            {
                loaderAdapter.start();
                return;
            }
        }

        if (loaderDict[loaderAdapter.priority] == null)
        {
            loaderDict[loaderAdapter.priority] = [];
            loaderPriorityLib.push(loaderAdapter.priority);
        }
        loaderDict[loaderAdapter.priority].push(loaderAdapter);

        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_DISPOSE,
                                       loaderAdapter_disposeHandler);

        // 使用Timer调用是为防止同一时间多个添加造成的性能浪费
        if (!timeOutToSort.running)
        {
            timeOutToSort.start();
        }
    }

    /**
    * 将LoaderQueue实例中的所有内容消毁，并将下载队列清空
    */
    public function dispose():void
    {
        removeAllItem();
        loaderDict = null;
        loaderPriorityLib = null;
        threadLib = null;

        timeOutToSort.stop();
        timeOutToSort.removeEventListener(TimerEvent.TIMER_COMPLETE,
            timeOutToSort_timerCompleteHandler);
        timeOutToSort = null;
    }

    /**
     * 停止并移除队列中所有等级的下载项
     */
    public function removeAllItem():void
    {
        for each (var i:uint in loaderPriorityLib)
        {
            removeItemByPriority(i);
        }
    }

    /**
     * 停止并移除队列中指定的下载项
     * 如想消毁Item实例需手动调用其自身的dispose方法
     */
    public function removeItem(loaderAdapter:ILoaderAdapter):void
    {
        disposeItem(loaderAdapter);
        loaderAdapter.state = LoaderQueueConst.STATE_REMOVED;
    }

    /**
     * 停止并移除队列中所有相应等级的下载项
     * @param priority 需停止并移除的等级
     */
    public function removeItemByPriority(priority:uint):void
    {
        for each (var i:ILoaderAdapter in loaderDict[priority])
        {
            removeItem(i);
        }
    }

    /**
     * 停止并移除队列中除指定等级以外的所有等级的下载项
     * @param priority 需保留的等级
     */
    public function saveItemByPriority(priority:uint):void
    {
        for each (var i:ILoaderAdapter in loaderDict[priority])
        {
            if (i.priority != priority)
            {
                removeItem(i);
            }
        }
    }

    /**
     * 取得当前正在运行的线程数
     * @return uint
     */
    public function get currentStartedNum():uint
    {
        return threadLib.length;
    }

    /**
     * 检查队列中是否还有项目需下载
     * @private
     */
    private function checkQueueHandle():Boolean
    {
        for each (var priority:uint in loaderPriorityLib)
        {
            if (loaderDict[priority].length > 0)
            {
                return true;
            }
        }
        dispatchEvent(
                new LoaderQueueEvent(LoaderQueueEvent.TASK_QUEUE_COMPLETED));
        return false;
    }

    /**
     * 检查下载线程是否已到最大上限
     * @private
     */
    private function checkThreadHandle(loaderAdapter:ILoaderAdapter):void
    {
        if (loaderAdapter == null)
        {
            //执行到此处说明队列中的所有项目均正在执行
            return;
        }
        if (threadLib.length < threadLimit)
        {
            threadLib.push(loaderAdapter);
            startItem(loaderAdapter);
        }
        else
        {
            threadFullHandle(loaderAdapter);
        }
    }

    /**
     * 将项目从线程池与队列中移出,但并不消毁其自身
     * (如想消毁项目实例需手动调用其自身的dispose方法)
     * p.s:一般在item发生completed与error后调用
     *
     * @private
     */
    private function disposeItem(loaderAdapter:ILoaderAdapter):void
    {
        if (loaderAdapter.isStarted)
        {
            loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                              loaderAdapter_completedHandler);
            loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_ERROR,
                                              loaderAdapter_errorHandler);
            try
            {
                loaderAdapter.stop();
            }
            catch (e:Error)
            {
                //屏蔽可能的错误
            }
        }
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_DISPOSE,
                                          loaderAdapter_disposeHandler);
        var num:uint = threadLib.indexOf(loaderAdapter);
        if (num != -1)
        {
            threadLib.splice(num, 1);
        }
        num = loaderDict[loaderAdapter.priority].indexOf(loaderAdapter);

        loaderDict[loaderAdapter.priority].splice(num, 1);
    }

    /**
     * 取得下一个需要下载的项目
     * @private
     */
    private function getNextIdleItem():ILoaderAdapter
    {
        for each (var priority:uint in loaderPriorityLib)
        {
            for each (var loaderAdapter:ILoaderAdapter in loaderDict[priority])
            {
                if (!loaderAdapter.isStarted)
                {
                    return loaderAdapter;
                }
            }
        }
        return null;
    }

    /**
     * 启动LoaderAdapter实例
     * @private
     */
    private function startItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_STARTED;
        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                       loaderAdapter_completedHandler);
        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_ERROR,
                                       loaderAdapter_errorHandler);
        loaderAdapter.start();
    }

    /**
     * 停止LoaderAdapter实例并屏蔽可能引发的错误
     * @private
     */
    private function stopItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_WAITING;
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                          loaderAdapter_completedHandler);
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_ERROR,
                                          loaderAdapter_errorHandler);

        try
        {
            loaderAdapter.stop();
        }
        catch (error:Error)
        {
            //屏蔽可能的错误
        }
    }

    /**
     * 当下载线程全部被占用时,对新添加的实例进行的操作
     * @private
     */
    private function threadFullHandle(loaderAdapter:ILoaderAdapter):void
    {
        for (var i:uint = 0; i < threadLib.length; i++)
        {
            var reversePriorityResult:Boolean =
                    checkReversePriority(ILoaderAdapter(threadLib[i]).priority,
                        loaderAdapter.priority);
            if (reversePriorityResult)
            {
                stopItem(threadLib[i]);
                threadLib[i] = loaderAdapter;
                startItem(loaderAdapter);
            }
        }
    }

    /**
     * 将已启动的adapter重新排序
     */
    private function sortStartedItem():void
    {
        var itemLoader:ILoaderAdapter = getNextIdleItem();
        if (itemLoader == null)
        {
            //已无项目，或是所有项目都已开始下载
            //所以已无重新排序必要
            return;
        }
        var oldPriority:int = itemLoader.priority;
        var idleLoaderAdapter:ILoaderAdapter;
        var runningLoaderAdapter:ILoaderAdapter;
        for (var i:uint = 0; i < threadLib.length; i++)
        {
            runningLoaderAdapter = threadLib[i];
            var reversePriorityResult:Boolean =
                checkReversePriority(
                    runningLoaderAdapter.priority, oldPriority);
            if (reversePriorityResult)
            {
                idleLoaderAdapter = getNextIdleItem();
                reversePriorityResult =
                    checkReversePriority(runningLoaderAdapter.priority,
                                                    idleLoaderAdapter.priority);
                if (reversePriorityResult)
                {
                    stopItem(runningLoaderAdapter);
                    threadLib[i] = idleLoaderAdapter;
                    startItem(idleLoaderAdapter);
                    oldPriority = idleLoaderAdapter.priority;
                }
                else
                {
                    oldPriority = runningLoaderAdapter.priority;
                }
            }
            else
            {
                oldPriority = runningLoaderAdapter.priority;
            }
        }
    }

    /**
     * 如线程池未全部使用，则将等待下载的项目装进线程池
     */
    private function fillThreadPool():void
    {
        var nextIdleAdapter:ILoaderAdapter;
        while (currentStartedNum < threadLimit)
        {
            nextIdleAdapter = getNextIdleItem();
            if (nextIdleAdapter != null)
            {
                threadLib.push(nextIdleAdapter);
                startItem(nextIdleAdapter);
                nextIdleAdapter = null;
            }
            else
            {
                //线程未满，但已没有等待下载的项目时调用此处
                break;
            }
        }
    }

    //==========================================================================
    //  Event Handlers
    //==========================================================================
    private function loaderAdapter_completedHandler(event:LoaderQueueEvent):void
    {
        var loaderAdapter:ILoaderAdapter =
                                          event.currentTarget as ILoaderAdapter;
        if(jumpQueueIfCached)
        {
            cacheMap[loaderAdapter.url] = true;
        }
        disposeItem(loaderAdapter);

        if (checkQueueHandle())
        {
            checkThreadHandle(getNextIdleItem());
        }
    }

    private function loaderAdapter_errorHandler(event:LoaderQueueEvent):void
    {
        var loaderAdapter:ILoaderAdapter =
                                        event.currentTarget as ILoaderAdapter;
        disposeItem(loaderAdapter);
        if (checkQueueHandle())
        {
            checkThreadHandle(getNextIdleItem());
        }
    }

    /**
    * adataper实例自动调用其自身的dispose方法后触发此处
    * @private
    */
    private function loaderAdapter_disposeHandler(event:LoaderQueueEvent):void
    {
        removeItem(event.target as ILoaderAdapter);
    }

    private function timeOutToSort_timerCompleteHandler(event:TimerEvent):void
    {
        timeOutToSort.reset();

        if (!reversePriority)
        {
            loaderPriorityLib.sort(Array.NUMERIC);
        }
        else
        {
            loaderPriorityLib.sort(Array.NUMERIC);
            loaderPriorityLib.reverse();
        }

        if (threadLib.length > 0)
        {
            sortStartedItem();
        }
        fillThreadPool();
    }

    /**
     * 根据是否倒序,来得出两个等级之间优先级的结果
     * @param priority1
     * @param priority2
     * @return
     * @private
     */
    private function checkReversePriority(priority1:uint, priority2:uint):Boolean
    {
        if (!this.reversePriority)
        {
            return priority1 > priority2;
        }
        return priority2 > priority1;
    }
}
}

/*
 * Copyright (c) 2011, wersling.com All rights reserved.
 */
package net.manaca.loaderqueue
{
import flash.events.EventDispatcher;
import flash.events.TimerEvent;
import flash.utils.Dictionary;
import flash.utils.Timer;

/**
 * 任务队列全部完成后派发
 * @eventType net.manaca.loaderqueue.LoaderQueueEvent.TASK_QUEUE_COMPLETED
 */
[Event(name="taskQueueCompleted",
    type="net.manaca.loaderqueue.LoaderQueueEvent")]

/**
 * 任务队列及下载队列的管理器
 * @example
 * var urlLoader:URLLoaderAdapter =
 *                           new URLLoaderAdapter(4,"http://ggg.ggg.com/a.swf");
 * urlLoader.addEventListener(LoaderQueueEvent.TASK_COMPLETED,
 *                                                           onLoadedCompleted);
 * var loaderQueue:LoaderQueue = new LoaderQueue();
 * loaderQueue.addItem(urlLoader);
 *
 * @see net.manaca.loaderqueue.adapter#URLLoaderAdapter
 *
 * @author Austin
 * @update sean
 */
public class LoaderQueue extends EventDispatcher implements ILoaderQueue
{
    //==========================================================================
    //  Constructor
    //==========================================================================
    /**
     * Constructs a new Application instance.
     * @param threadLimit 下载线程数的上限。默认2
     * @param delay 下载队列排序延迟时间，单位毫秒。默认500毫秒
     * @param jumpQueueIfCached 如果该url文件已经加载过，是否跳过队列直接加载。
     * 该参数通过加载文件的url来判断。默认为true
     */
    public function LoaderQueue(threadLimit:uint = 2, delay:int = 500,
                                jumpQueueIfCached:Boolean = true)
    {
        this.threadLimit = threadLimit;
        this.delay = delay;
        this.jumpQueueIfCached = jumpQueueIfCached;

        timeOutToSort = new Timer(delay, 1);
        timeOutToSort.addEventListener(TimerEvent.TIMER_COMPLETE,
            timeOutToSort_timerCompleteHandler);
    }

    //==========================================================================
    //  Variables
    //==========================================================================
    /**
     * 下载队列排序延迟时间，单位毫秒
     */
    private var delay:int;

    /**
     * 缓存已经加载过的url地址
     */
    private var cacheMap:Object = {};

    /**
     * 用于保存所有的下载项目
     * p.s:已下载的会被清除
     * @private
     */
    private var loaderDict:Dictionary/* of ILoaderAdapter */= new Dictionary();

    /**
     * 用于决定下载的等级的顺序
     * @private
     */
    private var loaderPriorityLib:Array /* of uint */ = [];

    /**
     * 用于保存目前正在下载的对象
     * @private
     */
    private var threadLib:Array /* of ILoaderAdapter */ = [];

    /**
     * 用于在添加新的Item后延迟触发排序.
     */
    private var timeOutToSort:Timer;
    //==========================================================================
    //  Properties
    //==========================================================================
    /**
     * 如果该url文件已经加载过，是否跳过队列直接加载。
     * 该参数通过加载文件的url来判断。默认为true
     * @default true
     */
    public var jumpQueueIfCached:Boolean = true;

    /**
     * 最大线程数上限值
     */
    public var threadLimit:uint;

    /**
     * 等级排序时是否使用倒序(如4,3,2,1)
     */
    public var reversePriority:Boolean = false;
    //==========================================================================
    //  Methods
    //==========================================================================
    /**
     * @inheritDoc
     */
    public function addItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_WAITING;
        //如果jumpQueueIfCached为true,则检查是否已经加载过，如果加载过，则不添加到队列，
        //而是直接开始加载
        if(jumpQueueIfCached)
        {
            if(cacheMap[loaderAdapter.url])
            {
                loaderAdapter.start();
                return;
            }
        }

        if (loaderDict[loaderAdapter.priority] == null)
        {
            loaderDict[loaderAdapter.priority] = [];
            loaderPriorityLib.push(loaderAdapter.priority);
        }
        loaderDict[loaderAdapter.priority].push(loaderAdapter);

        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_DISPOSE,
                                       loaderAdapter_disposeHandler);

        // 使用Timer调用是为防止同一时间多个添加造成的性能浪费
        if (!timeOutToSort.running)
        {
            timeOutToSort.start();
        }
    }

    /**
    * 将LoaderQueue实例中的所有内容消毁，并将下载队列清空
    */
    public function dispose():void
    {
        removeAllItem();
        loaderDict = null;
        loaderPriorityLib = null;
        threadLib = null;

        timeOutToSort.stop();
        timeOutToSort.removeEventListener(TimerEvent.TIMER_COMPLETE,
            timeOutToSort_timerCompleteHandler);
        timeOutToSort = null;
    }

    /**
     * 停止并移除队列中所有等级的下载项
     */
    public function removeAllItem():void
    {
        for each (var i:uint in loaderPriorityLib)
        {
            removeItemByPriority(i);
        }
    }

    /**
     * 停止并移除队列中指定的下载项
     * 如想消毁Item实例需手动调用其自身的dispose方法
     */
    public function removeItem(loaderAdapter:ILoaderAdapter):void
    {
        disposeItem(loaderAdapter);
        loaderAdapter.state = LoaderQueueConst.STATE_REMOVED;
    }

    /**
     * 停止并移除队列中所有相应等级的下载项
     * @param priority 需停止并移除的等级
     */
    public function removeItemByPriority(priority:uint):void
    {
        for each (var i:ILoaderAdapter in loaderDict[priority])
        {
            removeItem(i);
        }
    }

    /**
     * 停止并移除队列中除指定等级以外的所有等级的下载项
     * @param priority 需保留的等级
     */
    public function saveItemByPriority(priority:uint):void
    {
        for each (var i:ILoaderAdapter in loaderDict[priority])
        {
            if (i.priority != priority)
            {
                removeItem(i);
            }
        }
    }

    /**
     * 取得当前正在运行的线程数
     * @return uint
     */
    public function get currentStartedNum():uint
    {
        return threadLib.length;
    }

    /**
     * 检查队列中是否还有项目需下载
     * @private
     */
    private function checkQueueHandle():Boolean
    {
        for each (var priority:uint in loaderPriorityLib)
        {
            if (loaderDict[priority].length > 0)
            {
                return true;
            }
        }
        dispatchEvent(
                new LoaderQueueEvent(LoaderQueueEvent.TASK_QUEUE_COMPLETED));
        return false;
    }

    /**
     * 检查下载线程是否已到最大上限
     * @private
     */
    private function checkThreadHandle(loaderAdapter:ILoaderAdapter):void
    {
        if (loaderAdapter == null)
        {
            //执行到此处说明队列中的所有项目均正在执行
            return;
        }
        if (threadLib.length < threadLimit)
        {
            threadLib.push(loaderAdapter);
            startItem(loaderAdapter);
        }
        else
        {
            threadFullHandle(loaderAdapter);
        }
    }

    /**
     * 将项目从线程池与队列中移出,但并不消毁其自身
     * (如想消毁项目实例需手动调用其自身的dispose方法)
     * p.s:一般在item发生completed与error后调用
     *
     * @private
     */
    private function disposeItem(loaderAdapter:ILoaderAdapter):void
    {
        if (loaderAdapter.isStarted)
        {
            loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                              loaderAdapter_completedHandler);
            loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_ERROR,
                                              loaderAdapter_errorHandler);
            try
            {
                loaderAdapter.stop();
            }
            catch (e:Error)
            {
                //屏蔽可能的错误
            }
        }
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_DISPOSE,
                                          loaderAdapter_disposeHandler);
        var num:uint = threadLib.indexOf(loaderAdapter);
        if (num != -1)
        {
            threadLib.splice(num, 1);
        }
        num = loaderDict[loaderAdapter.priority].indexOf(loaderAdapter);

        loaderDict[loaderAdapter.priority].splice(num, 1);
    }

    /**
     * 取得下一个需要下载的项目
     * @private
     */
    private function getNextIdleItem():ILoaderAdapter
    {
        for each (var priority:uint in loaderPriorityLib)
        {
            for each (var loaderAdapter:ILoaderAdapter in loaderDict[priority])
            {
                if (!loaderAdapter.isStarted)
                {
                    return loaderAdapter;
                }
            }
        }
        return null;
    }

    /**
     * 启动LoaderAdapter实例
     * @private
     */
    private function startItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_STARTED;
        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                       loaderAdapter_completedHandler);
        loaderAdapter.addEventListener(LoaderQueueEvent.TASK_ERROR,
                                       loaderAdapter_errorHandler);
        loaderAdapter.start();
    }

    /**
     * 停止LoaderAdapter实例并屏蔽可能引发的错误
     * @private
     */
    private function stopItem(loaderAdapter:ILoaderAdapter):void
    {
        loaderAdapter.state = LoaderQueueConst.STATE_WAITING;
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_COMPLETED,
                                          loaderAdapter_completedHandler);
        loaderAdapter.removeEventListener(LoaderQueueEvent.TASK_ERROR,
                                          loaderAdapter_errorHandler);

        try
        {
            loaderAdapter.stop();
        }
        catch (error:Error)
        {
            //屏蔽可能的错误
        }
    }

    /**
     * 当下载线程全部被占用时,对新添加的实例进行的操作
     * @private
     */
    private function threadFullHandle(loaderAdapter:ILoaderAdapter):void
    {
        for (var i:uint = 0; i < threadLib.length; i++)
        {
            var reversePriorityResult:Boolean =
                    checkReversePriority(ILoaderAdapter(threadLib[i]).priority,
                        loaderAdapter.priority);
            if (reversePriorityResult)
            {
                stopItem(threadLib[i]);
                threadLib[i] = loaderAdapter;
                startItem(loaderAdapter);
            }
        }
    }

    /**
     * 将已启动的adapter重新排序
     */
    private function sortStartedItem():void
    {
        var itemLoader:ILoaderAdapter = getNextIdleItem();
        if (itemLoader == null)
        {
            //已无项目，或是所有项目都已开始下载
            //所以已无重新排序必要
            return;
        }
        var oldPriority:int = itemLoader.priority;
        var idleLoaderAdapter:ILoaderAdapter;
        var runningLoaderAdapter:ILoaderAdapter;
        for (var i:uint = 0; i < threadLib.length; i++)
        {
            runningLoaderAdapter = threadLib[i];
            var reversePriorityResult:Boolean =
                checkReversePriority(
                    runningLoaderAdapter.priority, oldPriority);
            if (reversePriorityResult)
            {
                idleLoaderAdapter = getNextIdleItem();
                reversePriorityResult =
                    checkReversePriority(runningLoaderAdapter.priority,
                                                    idleLoaderAdapter.priority);
                if (reversePriorityResult)
                {
                    stopItem(runningLoaderAdapter);
                    threadLib[i] = idleLoaderAdapter;
                    startItem(idleLoaderAdapter);
                    oldPriority = idleLoaderAdapter.priority;
                }
                else
                {
                    oldPriority = runningLoaderAdapter.priority;
                }
            }
            else
            {
                oldPriority = runningLoaderAdapter.priority;
            }
        }
    }

    /**
     * 如线程池未全部使用，则将等待下载的项目装进线程池
     */
    private function fillThreadPool():void
    {
        var nextIdleAdapter:ILoaderAdapter;
        while (currentStartedNum < threadLimit)
        {
            nextIdleAdapter = getNextIdleItem();
            if (nextIdleAdapter != null)
            {
                threadLib.push(nextIdleAdapter);
                startItem(nextIdleAdapter);
                nextIdleAdapter = null;
            }
            else
            {
                //线程未满，但已没有等待下载的项目时调用此处
                break;
            }
        }
    }

    //==========================================================================
    //  Event Handlers
    //==========================================================================
    private function loaderAdapter_completedHandler(event:LoaderQueueEvent):void
    {
        var loaderAdapter:ILoaderAdapter =
                                          event.currentTarget as ILoaderAdapter;
        if(jumpQueueIfCached)
        {
            cacheMap[loaderAdapter.url] = true;
        }
        disposeItem(loaderAdapter);

        if (checkQueueHandle())
        {
            checkThreadHandle(getNextIdleItem());
        }
    }

    private function loaderAdapter_errorHandler(event:LoaderQueueEvent):void
    {
        var loaderAdapter:ILoaderAdapter =
                                        event.currentTarget as ILoaderAdapter;
        disposeItem(loaderAdapter);
        if (checkQueueHandle())
        {
            checkThreadHandle(getNextIdleItem());
        }
    }

    /**
    * adataper实例自动调用其自身的dispose方法后触发此处
    * @private
    */
    private function loaderAdapter_disposeHandler(event:LoaderQueueEvent):void
    {
        removeItem(event.target as ILoaderAdapter);
    }

    private function timeOutToSort_timerCompleteHandler(event:TimerEvent):void
    {
        timeOutToSort.reset();

        if (!reversePriority)
        {
            loaderPriorityLib.sort(Array.NUMERIC);
        }
        else
        {
            loaderPriorityLib.sort(Array.NUMERIC);
            loaderPriorityLib.reverse();
        }

        if (threadLib.length > 0)
        {
            sortStartedItem();
        }
        fillThreadPool();
    }

    /**
     * 根据是否倒序,来得出两个等级之间优先级的结果
     * @param priority1
     * @param priority2
     * @return
     * @private
     */
    private function checkReversePriority(priority1:uint, priority2:uint):Boolean
    {
        if (!this.reversePriority)
        {
            return priority1 > priority2;
        }
        return priority2 > priority1;
    }
}
}

Format LoaderQueue code

Format LoaderQueue code