简体   繁体   English

AS3计时器与ENTER_FRAME性能

[英]AS3 Timers vs. ENTER_FRAME performance

I'm building a game that got some moving things all the time, so I'm using a lot of Timer instances to control repetition and trigger motion. 我正在构建一个总是有一些移动的游戏,所以我使用了很多Timer实例来控制重复和触发运动。

Now the thing is that I started to notice some performance "lags". 现在的问题是,我开始注意到一些性能“滞后”。 Is this due to the timers? 这是由于计时器吗? and do you suggest using ENTER_FRAME event instead? 你建议使用ENTER_FRAME事件吗?

Related: Do you suggest any other library/method for such games that could enhance performance? 相关:您是否建议任何其他可以提高性能的图书馆/方法? Simple Tween libraries are not enough per se. 简单的Tween库本身是不够的。

maybe it would make more sense, to have only one timer running for that matter ... as far as i know, a running Timer needs a whole thread ... to put it in pseudo-code, the Timer thread's main code is something like that ... 也许它会更有意义, 只有一个计时器运行 ...据我所知,一个正在运行的Timer需要一个完整的线程......把它放在伪代码中,Timer线程的主要代码是什么东西像那样 ...

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

output being a dequeue the main thread (which executes the ABC) checks every now an then ... having many Timers, you will have many threads, which is an unnecessary overhead ... also, for every event, the message sent from the timer to the main thread needs to be popped of the deque, which is expensive, since it has to be thread safe ... and then the corresponding timer has to be found, a timer event has to be created (allocation is also quite costy) and then dispatched, which also is a matter of multiple calls ... 输出是一个出列主线程(执行ABC)现在每次检查...有很多计时器,你将有很多线程,这是一个不必要的开销...同样,对于每个事件,从主线程的计时器需要弹出deque,这是昂贵的,因为它必须是线程安全的...然后必须找到相应的计时器,必须创建一个计时器事件(分配也相当昂贵) )然后发送,这也是多个电话的问题......

so try to have ONE timer, or use setInterval ... also, consider, that the Event model in flash is quite nice, but expensive ... it is used for decoupling, to ensure a nice architecture ... for the same reason, it is not good for performance critical situations ... once again, dispatching an event is expensive ... 所以尝试拥有一个计时器,或者使用setInterval ...另外,考虑一下,flash中的事件模型非常好,但价格昂贵......它用于解耦,以确保一个漂亮的架构......出于同样的原因,这对性能危急情况不利......再一次,派遣一个活动很昂贵......

i've made a little class, that is a little more manual (it's just to make my point, although it oculd be used in theory): 我做了一个小班,这是一个更多的手册(这只是为了说明我的观点,虽然它在理论上被使用):

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);
        }
    }
}

it performs quite well ... here a benchmarking class (you should be able to simply use it as document class in a fla, if you use CS3/CS4): 它执行得很好......这里是一个基准测试类(如果使用CS3 / CS4,你应该可以简单地将它用作fla中的文档类):

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("");
        }
    }
}

on my machine, with 60 FPS targetet, i get an average FPS of 6.4 (after 3 minutes) and 10-14 MB memory usage (fluctuation comes from the fact that TimerEvent objects need to be garbage collected) for 10000 functions being called with timers ... using the other class, i get 55.2 FPS with 95.0 MB memory usage (very constant, fluctuations are unter 1%) with 200000 functions being called directly ... this means, at factor 20 you get a framerate that is 9 times higher, and you use only 8 times the memory ... this should get you an idea of how much footprint a timer creates ... 在我的机器上,使用60 FPS targetstet,我得到平均FPS为6.4(3分钟后)和10-14 MB内存使用(波动来自于TimerEvent对象需要被垃圾收集的事实),用于定时器调用10000个函数...使用其他类,我得到55.2 FPS,95.0 MB内存使用率(非常恒定,波动率为1%),直接调用200000个函数...这意味着,在20倍时,你得到的帧率是9倍更高,你只使用8倍的内存......这应该可以让你了解一个计时器创建的足迹...

this should get you a rough idea, in which direction to go ... 这应该给你一个粗略的想法,在哪个方向去......

[edit] i've been asked, why i use private vars ... matter of philosophy ... my rule: never ever let anyone from outside change the state of your object directly ... imagine Ticker::_tickLength was protected ... someone subclasses it, and writes to that variable ... with what effect? [编辑]我被问到,为什么我使用私有变量...哲学问题...我的规则:永远不要让任何人从外面直接改变你的对象的状态...想象Ticker::_tickLength受到protected 。 ..有人将其子类化,并写入该变量......具有什么效果? the value of Ticker::tickLength will be different from the interval length ... i don't really see an advantage ... Ticker::tickLength的值将与间隔长度不同......我真的没有看到优势......

also, private fields are only valid in a class ... which means anyone can redefine them within subclasses without any collisions ... 此外,私有字段仅在类中有效...这意味着任何人都可以在子类中重新定义它们而不会发生任何冲突......

if i think, that subclasses should have a protected way to take effect on the state defined in the superclass, i make a protected setter ... but still, i can react ... i can change/validate/clamp the value, throw argument and range errors at will, dispatch events, and so on ... if you write a class, you yourself are responsible for maintaining the integrity of its state and the effects on its behaviour ... 如果我认为,那个子类应该有一个protected方式对超类中定义的状态生效,我做一个protected setter ......但是,我仍然可以做出反应...我可以更改/验证/钳制值,抛出随意的参数和范围错误,调度事件等......如果你写一个类,你自己负责维护其状态的完整性及其对行为的影响......

do not expose the inner workings of your class ... you may need to change them, breaking dependant code ... and also: subclassing is hugely overrrated ... :) 不要暴露你的类的内部工作...你可能需要更改它们,破坏相关的代码......还有:子类化被严重过度... :)

so that's why ... [/edit] 这就是为什么... [/ edit]

greetz 格尔茨

back2dos back2dos

I'd recommend using ENTER_FRAME as the master "tick" for your game engine. 我建议使用ENTER_FRAME作为游戏引擎的主“勾号”。 ENTER_FRAME lines up exactly with the Flash Player's framerate, which is the true maximum framerate your code will run at. ENTER_FRAME与Flash Player的帧速率完全对齐,帧速率是代码运行的真正最大帧速率。 Timers, etc., are just approximations and cannot execute any faster than ENTER_FRAME. 定时器等只是近似值,并且不能以比ENTER_FRAME更快的速度执行。

In fact, while I originally used Timers for all of my stuff, I'm slowly moving away from them because of aliasing issues. 事实上,虽然我最初使用Timers来处理所有的东西,但由于混叠问题,我正在慢慢地离开它们。 If you set your Timer for 30fps, but the Flash Player ends up running at 15fps, then the Timer will end up dispatching it's TIMER event twice between ENTER_FRAME events. 如果将Timer设置为30fps,但Flash Player最终以15fps运行,那么Timer将最终在ENTER_FRAME事件之间两次调度它的TIMER事件。 If these TIMER events lead to expensive code (which they would if it's your game engine's tick), then it has the potential of pushing the Player's actual framerate lower (because now you're ticking twice per ENTER_FRAME). 如果这些TIMER事件导致昂贵的代码(如果它是你的游戏引擎的嘀嗒声),那么它有可能将玩家的实际帧率降低(因为现在你每个ENTER_FRAME滴答两次)。

So, Timer is good if you've got something that you want to run periodically, but for running anything close to your SWF's actual framerate I'd recommend just using the SWF's framerate and adjusting your logic as necessary. 所以,如果你有想要定期运行的东西,那么Timer很好,但是为了运行接近你的SWF实际帧速率的任何东西,我建议只使用SWF的帧速率并根据需要调整逻辑。

One approach is to calculate time deltas on each ENTER_FRAME. 一种方法是计算每个ENTER_FRAME的时间增量。 If you've got time-based logic, this is the best approach. 如果你有基于时间的逻辑,这是最好的方法。 Another approach, if your SWF is assuming a fixed update rate (like Timer-based code), is to call your game's tick method if-and-only-if you've exceeded the time delta on any given ENTER_FRAME. 另一种方法是,如果你的SWF假设一个固定的更新速率(比如基于计时器的代码),如果你已经超过了任何给定的ENTER_FRAME的时间增量,那就是调用游戏的tick方法。

I would not recommend doing two ticks per ENTER_FRAME if you fall behind (or you'll end up with the same situation as the Timers). 如果你落后(或者你最终会得到与计时器相同的情况),我建议每个ENTER_FRAME做两个滴答。 At a certain point, your game has to slow down or it becomes unplayable (because the deltas get too big). 在某个时刻,你的游戏必须放慢速度或变得无法播放(因为增量太大了)。 Doing more than one tick per ENTER_FRAME when you're already slowed down will only slow you down further. 当你已经放慢速度时,每个ENTER_FRAME执行多次勾选只会进一步降低你的速度。 Users can better handle slowed gameplay than they can skipping gameplay. 用户可以比跳过游戏玩法更好地处理减慢的游戏玩法。

if you aren't using a tween library yet i would look at tweenlite or tweenmax. 如果你没有使用补间库,我会看看tweenlite或tweenmax。 it includes a delayed called timer as well as grouping tweens together. 它包括一个延迟的被叫定时器以及将补间分组在一起。 it has great performance and is simple to use. 它性能卓越,使用简单。

take a look here at the performance tests 看一下性能测试

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

Josh 玩笑

The issue probably comes from the fact that timers aren't really reliable in that they aren't as fps independent as we think they are. 问题可能来自这样一个事实,即计时器不是真正可靠的,因为它们不像我们认为的那样独立于fps。 When the framerate drops, for some reason the timers will get called less often as well. 当帧速率下降时,由于某种原因,定时器也会被不频繁地调用。 This is pretty different than the behavior in C, C++ or other OOP languages and thus many are falling for this trap. 这与C,C ++或其他OOP语言中的行为非常不同,因此很多人都陷入了这个陷阱。

To avoid this, try to use the ENTER_FRAME event as a main game loop and inside that loop, evaluate the time to know if you need to do one or several updates to your game logic. 为了避免这种情况,尝试使用ENTER_FRAME事件作为主游戏循环并在该循环内部,评估时间,以了解您是否需要对游戏逻辑进行一次或多次更新。 That will make your code totally fps independent. 这将使您的代码完全独立于fps。 You can use the flash.utils.getTimer call to get the time since launch. 您可以使用flash.utils.getTimer调用来获取自启动以来的时间。

I wrote a post about this on my website: http://fabricebacquart.info/wordpress/?p=9 我在我的网站上写了一篇关于此的帖子: http//fabricebacquart.info/wordpress/?p = 9

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM