AS3 таймери срещу производителност на ENTER_FRAME

Създавам игра, в която постоянно се движат неща, така че използвам много случаи на таймер, за да контролирам повторението и да задействам движение.

Сега работата е там, че започнах да забелязвам някои "закъснения" в производителността. Това на таймерите ли се дължи? и предлагате ли да използвате събитие 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) проверява от време на време ... като имате много таймери, ще имате много нишки, което е ненужно натоварване ... също така, за всяко събитие, съобщението, изпратено от таймерът към основната нишка трябва да бъде изваден от deque, което е скъпо, тъй като трябва да е безопасен за нишки ... и след това трябва да се намери съответният таймер, трябва да се създаде събитие на таймера (разпределението също е доста скъпо ) и след това изпратени, което също е въпрос на множество обаждания ...

така че опитайте да имате ЕДИН таймер или използвайте 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 MB използване на паметта (флуктуацията идва от факта, че обектите на TimerEvent трябва да бъдат събрани за боклук) за 10 000 функции, извиквани с таймери ... използвайки другия клас, получавам 55,2 FPS с 95,0 MB използване на паметта (много постоянно, колебанията са под 1%) с 200 000 функции, които се извикват директно ... това означава, че при фактор 20 получавате кадрова честота, която е 9 пъти по-високо и използвате само 8 пъти повече памет ... това трябва да ви даде представа колко отпечатък създава таймер ...

това трябва да ви даде груба представа в каква посока да вървите ...

[редактиране] ме попитаха защо използвам private vars ... въпрос на философия ... моето правило: никога не позволявайте на някой отвън да променя директно състоянието на вашия обект ... представете си 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 се изпълнява от една нишка. flash player като такъв не е такъв. pixel bender е най-добрият пример, където можете дори да изпълнявате код паралелно. - person back2dos; 22.07.2009
comment
@luke: Членовете, обявени за защитени, са много по-податливи на злоупотреба, отколкото членовете, обявени за частни. По-специално, обявяването на защитени членове на данните обикновено е грешка при проектирането. Поставянето на значителни количества данни в общ клас за използване от всички производни класове оставя тези данни отворени за повреда. Още по-лошо, защитените данни, като публичните данни, не могат лесно да бъдат преструктурирани, защото няма добър начин за намиране на всякаква употреба. Така защитените данни се превръщат в проблем за софтуерната поддръжка. - Бярне Страуструп. Вижте също cpptips.com/prot_data. - person ctn; 04.09.2015

ах И аз така си мислех. Ще създам тип съдържание и след това просто ще поставя кода и логиката директно на страницата. Може да го извлечете в отделен файл. Благодаря Палантир!
person Troy Gilbert    schedule 21.07.2009
comment
Благодаря за ценната информация! Работата е там, че използвам комбинация от двете. ENTER_FRAME за моя герой, преместен с мишката, и таймер за движещи се коли... Хубавото обаче е, че „закъснението“, което споменах в моя горен въпрос, се наблюдава само в инструмента за създаване на Flash. Когато отворих swf сам (работя в самостоятелен флаш плейър), скоростта беше перфектна 30 fps. - person Makram Saleh; 22.07.2009

ако все още не използвате библиотека tween, бих погледнал tweenlite или tweenmax. включва отложен таймер, както и групиране на tweens заедно. има страхотна производителност и е лесен за използване.

вижте тук тестовете за ефективност

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

Джош

person Josh    schedule 09.07.2009

Проблемът вероятно идва от факта, че таймерите не са наистина надеждни, тъй като не са толкова независими от fps, колкото си мислим. Когато честотата на кадрите спадне, по някаква причина таймерите също ще се извикват по-рядко. Това е доста различно от поведението в C, C++ или други ООП езици и затова много хора попадат в този капан.

За да избегнете това, опитайте се да използвате събитието ENTER_FRAME като основен цикъл на играта и вътре в този цикъл преценете времето, за да разберете дали трябва да направите една или няколко актуализации на логиката на вашата игра. Това ще направи вашия код напълно независим от fps. Можете да използвате извикването flash.utils.getTimer, за да получите времето от стартирането.

Написах публикация за това на моя уебсайт: http://fabricebacquart.info/wordpress/?p=9

person Fabrice Bacquart    schedule 12.05.2013