суббота, 18 августа 2012 г.

Two-ways list & event subscription

Добрый день.

На этот раз будем рассматривать двухсторонние списки и подписки на событие.
Все это используется здесь:
( Нажмите на флешку для получения фокуса )

Первое, про что я расскажу - это двухсторонние списки.

Что это такое?
Двухсторонний список - это список, у элементов которого есть ссылки на следующий и предыдущий элементы.

Что? Как это работает?
У каждого элемента есть две ссылки: на предыдущий и следующий элементы.
Допустим, в списке есть один элемент.
  • SomeElement1
    • next : null
    • previous : null
Внезапно, в список добавился еще один элемент.
  • SomeElement1
    • next :  SomeElement2
    • previous : null
  • SomeElement2
    • next : null
    • prevoius :  SomeElement1
И еще один.
  • SomeElement1
    • next :  SomeElement2
    • previous : null
  • SomeElement2
    • next :  SomeElement3
    • prevoius :  SomeElement1
  • SomeElement3
    • next : null
    • prevoius :  SomeElement2

Чтобы удалить элемент нам не нужно знать что за элемент мы удаляем, а лишь ссылки на следующий и предыдущий элементы.

Удалим, к примеру, второй элемент.
Берем ссылку на следующий элемент ( третий ) и ставим у него prevoius в значение previous удаляемого элемента. Аналогично поступаем с предыдущим элементом - у предыдущего ставим next в значение next удаляемого элемента.
И получается:
  • SomeElement1
    • next :   SomeElement3
    • previous : null
  • SomeElement3
    • next : null
    • prevoius :   SomeElement1
Понятно. Как я могу это использовать?
Двухсторонние списки применимы для хранения чего-либо, когда неважно в каком порядке идут элементы, главное, что все они находятся в одном месте.

Как сделать этот список?
Я уже реализовал его для собственных нужд.
Там есть, как минимум, один недостаток - из списка можно удалить элемент другого списка. И все сломается. Не делайте так. Никогда.
Я делал его для себя, поэтому меня это не беспокоит. :)

Теперь про подписки на событие. При вызове события, уведомления рассылаются всем подписчикам.

Эээ.. В чем прикол? Ведь так и устроена система событий в ActionScript, разве нет?
Да. Вероятно, так она и устроена. Прикол в том, что нам нужно подписаться на событие всего один раз.

И что это нам дает?
Так как рассылка уведомлений происходит из одного места, это, по-сути, дает нам возможность управлять слушателями события всех подписчиков.

Возьмем пример сверху.
Если бы у каждого кружка был свой слушатель события ENTER_FRAME, и нам вдруг понадобилось его убрать, то пришлось бы перелопатить все кружки и отключать слушатель у каждого.

А так, у нас есть один слушатель ENTER_FRAME, из которого вызываются методы update для каждого подписчика ( кружка ). При необходимости, мы можем удалить один слушатель, и все кружки перестанут обновляться.

Для хранения подписчиков удобно использовать двухсторонний список.

Оки, хочу сделать пример с падающей хренотенью из мышки!
Хорошо. Здесь используются фильтры, если не читали про них - читаем и паттерн Singleton, тоже читаем. Кстати, в статье про фильтры я сказал, "на этот раз напишем все полностью из кода". Это не так. После публикации я обнаружил, что там нужно создать Movieclip с именем Circle. Мне впадлу было переделывать, так что оставил как есть.

Теперь реально сделаем все из кода ( фон не считается ). :Р

Main.as:

package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;
    
    public class Main extends MovieClip
    {
        public static var
        instance : Main;
        
        public const
        STAGE_HEIGHT : Number = 400.0;
        
        private const
        MIN_SPARK_SPEED_X : Number = -10.0,
        MAX_SPARK_SPEED_X : Number = 10.0,
        MIN_SPARK_SPEED_Y : Number = -10.0,
        MAX_SPARK_SPEED_Y : Number = 0.0,
        GRAVITY : Number = 1.0,
        SPARK_RADIUS : Number = 5.0;
        
        private var
        subscribes : List,
        paused : Boolean;
        
        public function Main () : void
        {
            // Use Singleton pattern
            // http://konanmentor.blogspot.com/2012/08/design-pattern-singleton.html
            // Do you remenber?
            if ( !Main.instance )
            {
                Main.instance = this;
                
                this.init();
            }
        }
        
        private function init () : void
        {
            this.subscribes = new List();
            // Set paused true to run this.resume method
            paused = true;
            // Start listen ENTER_FRAME event to trigger this.update method
            this.resume();
            
            this.stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownHandler );
        }
        
        private function keyDownHandler ( e : KeyboardEvent ) : void
        {
            if ( e.keyCode == Keyboard.SPACE )
            {
                if ( this.paused )
                {
                    this.resume();
                }
                else
                {
                    this.pause();
                }
            }
        }
        
        public function subscribe( callback : Function ) : ListItem
        {
            return subscribes.push( callback );
        }
        
        public function unsubscribe( subscription : ListItem ) : void
        {
            subscribes.pull( subscription );
        }
        
        private function update ( e : Event ) : void
        {
            // Loop through all subscribers
            for ( var listItem : ListItem = this.subscribes.getFirst(); listItem; listItem = listItem.getNext() )
            {
                var data : Function = listItem.GetData() as Function;
                data.call();
            }
            
            // Create new spark
            this.createSpark();
        }
        
        public function pause () : void
        {
            if ( !this.paused )
            {
                this.paused = true;
                this.removeEventListener( Event.ENTER_FRAME, this.update );
            }
        }
        
        public function resume () : void
        {
            if ( this.paused )
            {
                this.paused = false;
                this.addEventListener( Event.ENTER_FRAME, this.update );
            }
        }
        
        private function createSpark () : void
        {
            var options : Object =
            {
                x : stage.mouseX,
                y : stage.mouseY,
                speedX : Math.random() * ( MAX_SPARK_SPEED_X - MIN_SPARK_SPEED_X ) + MIN_SPARK_SPEED_X,
                speedY : Math.random() * ( MAX_SPARK_SPEED_Y - MIN_SPARK_SPEED_Y ) + MIN_SPARK_SPEED_Y,
                radius : SPARK_RADIUS,
                filterColor : Math.random() * 0xFFFFFF,
                gravity : GRAVITY
            },
            spark : Spark = new Spark( options );
        }
    }
}
Spark.as:

package
{
    import flash.display.Shape;
    import flash.display.MovieClip;
    import flash.filters.GlowFilter;
    
    public class Spark extends MovieClip
    {
        private const
        COLOR : uint = 0xFFFFFF;
        
        private var
        speedX : Number,
        speedY : Number,
        gravity : Number,
        subscription : ListItem;
        
        public function Spark ( options : Object ) : void
        {
            this.init( options );
        }
        
        private function init ( options : Object ) : void
        {
            this.x = options.x;
            this.y = options.y;
            this.gravity = options.gravity;
            this.speedX = options.speedX;
            this.speedY = options.speedY;
            // Subscribe on ENTER_FRAME update
            // Remember a subscriprion to delete it in future
            this.subscription = Main.instance.subscribe( this.update );
            
            this.createSparkImage( options );
            // Add glow filter
            this.filters = [ new GlowFilter( options.filterColor, 1.0, 15, 15, 2, 1, false, false ) ];
            // Add the spark to the stage
            Main.instance.stage.addChild( this );
        }
        
        private function createSparkImage ( options : Object ) : void
        {
            var shape : Shape = new Shape();
            shape.graphics.beginFill( COLOR );
            shape.graphics.drawCircle( 0, 0, options.radius );
            shape.graphics.endFill();
            
            this.addChild( shape );
        }
        
        private function update () : void
        {
            this.speedY += this.gravity;
            this.move();
            this.checkCoordinats();
        }
        
        private function checkCoordinats () : void
        {
            // When the spark falls off the screen
            // Destroy it
            if ( this.y - this.height * 0.5 > Main.instance.STAGE_HEIGHT )
            {
                this.destroy();
            }
        }
        
        private function move () : void
        {
            this.x += this.speedX;
            this.y += this.speedY;
        }
        
        private function destroy () : void
        {
            Main.instance.unsubscribe( this.subscription );
            // Remove the spark from the stage
            this.parent.removeChild( this );
        }
    }
}


Удачи!

Комментариев нет:

Отправить комментарий