AS3 Таймеры против производительности ENTER_FRAME

Я создаю игру, которая все время двигается, поэтому я использую много экземпляров Timer для управления повторением и срабатыванием триггера.

Теперь дело в том, что я начал замечать некоторые "отставания" производительности. Это из-за таймеров? и вы предлагаете вместо этого использовать событие ENTER_FRAME?

Связано: Вы предлагаете какую-либо другую библиотеку/метод для таких игр, которые могли бы повысить производительность? Простые библиотеки Tween сами по себе недостаточно.

Ответ 1

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

while (input.isEmpty()) {
    wait(interval);
    output.add({timerId:thisId, tickId: tickId++});
}

вывод, являющийся dequeue, основной поток (который выполняет ABC) проверяет каждый раз, а затем... имея много таймеров, у вас будет много потоков, что лишние накладные расходы... также для каждого события сообщение отправленный с таймера на основной поток, должен быть выбит из детекса, что дорого, поскольку он должен быть потокобезопасным... и затем необходимо найти соответствующий таймер, необходимо создать событие таймера (выделение также довольно дорого), а затем отправляется, что также связано с несколькими вызовами...

поэтому попробуйте использовать один таймер или используйте setInterval... также, подумайте, что модель Event во флэш-памяти довольно приятная, но дорогая... она используется для развязки, чтобы обеспечить приятную архитектуру... для по той же причине, это не хорошо для критических ситуаций производительности... еще раз, отправка события дорого...

Я сделал небольшой класс, это немного более ручной (просто для того, чтобы понять, хотя он теоретически используется):

package  {
    import flash.utils.*;
    public class Ticker {
        //{ region private vars
            private var _interval:int;
            private var _tick:uint = 0;
            private var _tickLength:Number;
            private var _callBacks:Dictionary;
        //} endregion
        public function Ticker(tickLength:Number = 0) {
            this.tickLength = tickLength;
            this._callBacks = new Dictionary();
        }
        //{ region accessors
            /**
             * the current tick
             */
            public function get tick():uint { return _tick; }
            /**
             * the tick length. set to a non-positive value, to stop ticking
             */
            public function get tickLength():Number { return _tickLength; }
            public function set tickLength(value:Number):void {
                if (this._tickLength > 0) clearInterval(this._interval);
                if ((this._tickLength = value) > 0) this._interval = setInterval(this.doTick, value);
            }       
        //} endregion
        /**
         * add a callback, to be called with every tick
         * @param   callback function (tick:int):*
         */
        public function addCallback(callback:Function):void {
            this._callBacks[callback] = callback;
        }
        /**
         * removes a callback previously added and returns true on success, false otherwise
         * @param   callback
         * @return
         */
        public function removeCallback(callback:Function):Boolean {
            return delete this._callBacks[callback];
        }
        /**
         * executes a tick. actually this happens automatically, but if you want to, you can set tickLength to a non-positive value and then execute ticks manually, if needed
         */
        public function doTick():void {
            var tick:uint = this._tick++;//actually, this is only superspicion ... amazingly, this makes no difference really ... :D
            for each (var callback:* in this._callBacks) callback(tick);
        }
    }
}

он неплохо работает... здесь класс сравнения (вы можете просто использовать его как класс документа во флопе, если используете CS3/CS4):

package {
    //{ region imports
        import flash.display.*;
        import flash.events.*;
        import flash.sampler.getSize;
        import flash.system.System;
        import flash.text.*;
        import flash.utils.*;   
    //} endregion
    public class Main extends MovieClip {
        //{ region configuration
            private const timers:Boolean = false;//true for Timer, false for Ticker
            private const delay:Number = 500;
            private const baseCount:uint = 10000;//base count of functions to be called
            private const factor:Number = 20;//factor for Ticker, which is a little more performant     
        //} endregion
        //{ region vars/consts
            private const count:uint = baseCount * (timers ? 1 : factor);
            private const nullMem:uint = System.totalMemory;//this is the footprint of the VM ... we'll subtract it ... ok, the textfield is not taken into account, but that should be alright ... i guess ...
            private var monitor:TextField;
            private var frameCount:uint = 0;
            private var secCount:uint = 0;      
        //} endregion
        public function Main():void {   
            var t:Ticker = new Ticker(delay);
            var genHandler:Function = function ():Function {
                return function (e:TimerEvent):void { };
            }
            var genCallback:Function = function ():Function {
                return function (tick:uint):void { };
            }
            for (var i:uint = 0; i < count; i++) {
                if (timers) {
                    var timer:Timer = new Timer(delay, 0);
                    timer.addEventListener(TimerEvent.TIMER, genHandler());
                    timer.start();                  
                }
                else {
                    t.addCallback(genCallback());
                }
            }
            this.addChild(this.monitor = new TextField());
            this.monitor.autoSize = TextFieldAutoSize.LEFT;
            this.monitor.defaultTextFormat = new TextFormat("_typewriter");
            this.addEventListener(Event.ENTER_FRAME, function (e:Event):void { frameCount++ });
            setInterval(function ():void { 
                    monitor.text = "Memory usage: " 
                        + groupDidgits(System.totalMemory - nullMem) 
                        + " B\navg. FPS: " + (frameCount /++secCount).toPrecision(3) 
                        + "\nuptime: " + secCount + "\nwith " + count + " functions"; 
                }, 1000);
        }
        private function groupDidgits(n:int,sep:String = " "):String {
            return n.toString().split("").reverse().map(function (c:String, i:int, ...rest):String { return c + ((i % 3 == 0 && i > 0) ? sep : ""); } ).reverse().join("");
        }
    }
}

на моей машине с 60 целевым набором FPS я получаю среднее значение FPS 6.4 (через 3 минуты) и использование памяти 10-14 МБ (флуктуация возникает из-за того, что объекты TimerEvent должны быть собраны в мусор) для 10000 функций, вызывается с таймерами... с использованием другого класса, я получаю 55,2 FPS с использованием памяти 95,0 МБ (очень постоянный, флуктуации - не более 1%), причем 200000 функций вызываются напрямую... это означает, что при коэффициенте 20 вы получаете частоту кадров, которая в 9 раз выше, и вы используете только 8 раз больше памяти... это должно дать вам представление о том, сколько отпечатков создается таймером...

это должно дать вам приблизительную идею, в каком направлении идти...

[edit] Меня спросили, почему я использую частные vars... вопрос философии... мое правило: никогда не позволяйте никому извне изменять состояние вашего объекта напрямую... Представьте, что Ticker::_tickLength был protected... кто-то подклассифицирует его и записывает в эту переменную... с каким эффектом? значение Ticker::tickLength будет отличаться от длины интервала... я действительно не вижу преимущества...

также, частные поля действительны только в классе... что означает, что любой может переопределить их в подклассах без каких-либо столкновений...

если я думаю, что подклассы должны иметь способ protected, чтобы вступить в силу в состоянии, определенном в суперклассе, я создаю установщик protected... но все же я могу реагировать... я могу изменить/проверять/зажимать значение, вводить аргумент и ошибки диапазона по желанию, отправлять события и т.д.... если вы пишете класс, вы сами несете ответственность за поддержание целостности его состояния и влияния на его поведение...

не подвергайте внутреннюю работу вашего класса... вам может потребоваться изменить их, нарушить зависимый код... а также: подклассификация сильно переохлаждена...:)

поэтому почему... [/edit]

Greetz

back2dos

Ответ 2

Я бы рекомендовал использовать ENTER_FRAME в качестве основного "галочки" для вашего игрового движка. Строка ENTER_FRAME точно совпадает с частотой кадров Flash Player, которая является максимальной максимальной частотой кадров, на которой будет работать ваш код. Таймеры и т.д. Являются просто аппроксимациями и не могут выполняться быстрее, чем ENTER_FRAME.

На самом деле, хотя я изначально использовал таймеры для всех своих вещей, я медленно отхожу от них из-за проблем с псевдонимом. Если вы установите таймер на 30 кадров в секунду, но проигрыватель Flash Player работает со скоростью 15 кадров в секунду, таймер в конечном итоге отправит событие TIMER дважды между событиями ENTER_FRAME. Если эти события TIMER приводят к дорогостоящему коду (что бы они сделали, если бы ваш игровой движок был тик), тогда он имеет потенциал для толкания фактической частоты кадров игрока ниже (потому что теперь вы отмечаете два раза за ENTER_FRAME).

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

Один из подходов состоит в том, чтобы рассчитать временные дельта для каждого элемента ENTER_FRAME. Если у вас есть логика, основанная на времени, это лучший подход. Другой подход, если ваш SWF предполагает фиксированную скорость обновления (например, код на основе таймера), заключается в том, чтобы вызывать метод тика вашей игры, если и только если вы превысили дельта времени на любом заданном ENTER_FRAME.

Я бы не рекомендовал делать два нажатия на ENTER_FRAME, если вы отстаете (или у вас будет та же ситуация, что и таймеры). В определенный момент ваша игра должна замедляться или она становится неиграбельной (потому что дельта становится слишком большой). Выполнение более чем одного тика за ENTER_FRAME, когда вы уже замедлились, только замедлит вас. Пользователи могут лучше обрабатывать замедленный геймплей, чем они могут пропустить игровой процесс.

Ответ 3

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

посмотрите здесь на тесты производительности

http://blog.greensock.com/tweening-speed-test/

Джош

Ответ 4

Проблема, вероятно, связана с тем, что таймеры не очень надежны в том смысле, что они не являются независимыми от FPS, как мы считаем. Когда частота кадров падает, по какой-то причине таймеры будут получать менее часто. Это очень отличается от поведения на языках C, С++ или других ООП, и поэтому многие из них попадают в эту ловушку.

Чтобы этого избежать, попробуйте использовать событие ENTER_FRAME в качестве основного игрового цикла и внутри этого цикла, оцените время, необходимое для того, чтобы вам нужно было сделать одно или несколько обновлений для вашей логики игры. Это сделает ваш код полностью независимым от fps. Вы можете использовать вызов flash.utils.getTimer, чтобы получить время с момента запуска.

Я написал сообщение об этом на своем веб-сайте: http://fabricebacquart.info/wordpress/?p=9