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

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

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

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


person Makram Saleh    schedule 09.07.2009    source источник


Ответы (4)


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

приветствие

back2dos

person back2dos    schedule 09.07.2009
comment
Что там с "регионом"? - person Luke; 10.07.2009
comment
Ой. И почему вы делаете переменные-члены закрытыми, а не защищенными? - person Luke; 10.07.2009
comment
Спасибо за бесценную информацию о таймерах. Я должен попробовать ваш код; Тикер звучит многообещающе! - person Makram Saleh; 10.07.2009
comment
@luke о регионах: я использую flashdevelop для разработки actionscript ... регионы позволяют сворачивать разные части класса ... плюс это придает коду дополнительную структуру ... просто мое соглашение о кодировании, так сказать ... - person back2dos; 10.07.2009
comment
@back2dos. Спасибо за это. Тем не мение. Я не согласен почти ни с чем, что вы говорите в своем обновлении. Похоже, вы думаете, что все враждебно и вам нужно защитить себя и других программистов от ваших реализаций классов. Моя философия прямо противоположна. Каждый член класса должен быть доступен для создания подклассов, если нет особой причины сделать его закрытым. Ваш подход кажется мне немного бюрократическим. Ах хорошо. Рад, что нам не приходится работать над проектами вместе. :) - person Luke; 10.07.2009
comment
речь идет об инкапсуляции и написании надежного кода, который либо всегда будет работать должным образом, либо выдавать ошибки времени выполнения, если это так. делает с ним глупости. для меня хороший API мощный, маленький и ориентированный на проблемы. не заботьтесь о том, как, пока это то, что надежно. это то, что я ожидаю, и именно поэтому я делаю это сам. Кстати, смысл подкласса не в том, чтобы возиться с некоторыми свойствами суперкласса, а в конкретной реализации абстрактного поведения, и это очень хороший инструмент для установления IOC. вы можете задать вопрос обо всем этом, если вы действительно хотите серьезно обсудить этот вопрос. - person back2dos; 10.07.2009
comment
Объяснение о таймерах и потоках совершенно неточное. Весь ActionScript во Flash Player выполняется в одном потоке, независимо от таймеров и т. д. Важно помнить об этом и не абстрагироваться от потока, если вы хотите понять, что происходит на самом деле. - person Troy Gilbert; 21.07.2009
comment
если бы вы читали мой пост, вы бы поняли, я сказал, что ABC выполняется одним потоком. флеш плеера как такового нет. Pixel Bender — лучший пример, где вы даже можете запускать код параллельно. - person back2dos; 22.07.2009
comment
@luke: члены, объявленные защищенными, гораздо более открыты для злоупотреблений, чем члены, объявленные закрытыми. В частности, объявление элементов данных защищенными обычно является ошибкой проектирования. Размещение значительных объемов данных в общем классе для использования всеми производными классами оставляет эти данные открытыми для повреждения. Хуже того, защищенные данные, как и общедоступные данные, не могут быть легко реструктурированы, потому что нет хорошего способа найти каждое применение. Таким образом, защищенные данные становятся проблемой обслуживания программного обеспечения. - Бьерн Страуструп. См. также cpptips.com/prot_data. - person ctn; 04.09.2015

Я бы рекомендовал использовать 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, когда вы уже замедлились, только замедлит вас еще больше. Пользователи лучше справляются с замедленным игровым процессом, чем с пропуском игрового процесса.

person Troy Gilbert    schedule 21.07.2009
comment
Спасибо за ценную информацию! Дело в том, что я использую комбинацию обоих. ENTER_FRAME для моего персонажа, перемещаемого мышью, и Timer для движущихся автомобилей... Однако хорошо то, что «задержка», о которой я упоминал в предыдущем вопросе, наблюдалась только в инструменте разработки Flash. Когда я открыл только swf (запущенный в автономном флеш-плеере), скорость была идеальной 30 кадров в секунду. - person Makram Saleh; 22.07.2009

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

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

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

Джош

person Josh    schedule 09.07.2009

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

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

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

person Fabrice Bacquart    schedule 12.05.2013