Таймеры AS3 и производительность ENTER_FRAME - PullRequest
11 голосов
/ 09 июля 2009

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

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

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

Ответы [ 4 ]

8 голосов
/ 09 июля 2009

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

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

вывод, являющийся удалением из очереди, основной поток (который выполняет 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);
        }
    }
}

он работает довольно хорошо ... здесь класс бенчмаркинга (вы можете просто использовать его как класс документа в FLA, если вы используете 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 раз памяти ... это должно дать вам представление о том, сколько места занимает таймер ...

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

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

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

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

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

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

Greetz

back2dos

6 голосов
/ 21 июля 2009

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

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

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

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

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

1 голос
/ 09 июля 2009

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

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

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

Josh

0 голосов
/ 12 мая 2013

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...