[Flash] Performance V.S. Reusability

  • Performance V.S. Reusability
  • 我想這是在OOP裡一個很大的tradeoff,我們必須在效能與框架設計之間取得平衡,考慮下面這一個簡單的效果:

    我們手邊已經有一個CircleMotion的Class,如下:

    /* CircleMotion.as */
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.science.Science;

    public class CircleMotion2D extends Motion{
    private var _timer:Timer;
    private var _radian:Number = 0;
    private var _radius:Number;
    private var _speed:Number;
    private var _dir:int;
    private var _xOffset:Number;
    private var _yOffset:Number;
    private var _obj:DisplayObject;

    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function CircleMotion2D(obj:DisplayObject, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 50, speed:Number = 0.1, fps:Number = 30, dir:Number = CircleMotion2D.CW, radOffset:int = 0){
    _obj = obj;
    _radius = radius;
    _speed = Math.min(1, Math.max(speed, 0.001));
    _dir = dir;
    _xOffset = xOffset;
    _yOffset = yOffset;
    _radian = Science.degToRad(radOffset%360);

    _timer = new Timer(1000/fps);
    _timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    _obj.x = _radius*Math.cos(_radian) + _xOffset - _radius;
    _obj.y = _radius*Math.sin(_radian) + _yOffset;

    _radian += _dir * _speed;

    if(_radian > 2*Math.PI){
    _radian = 0;
    }
    }

    override public function start():void{
    _timer.start();
    }

    override public function stop():void{
    _timer.stop();
    }

    override public function get running():Boolean{
    return _timer.running;
    }
    }
    }

    所以以直覺來講,RingMotion2D應該要直接沿用CircleMotion2D的定義,簡單的改變每個Object的initial radian就可以達到環繞的效果,所以RingMotion2D理論上是會這樣撰寫:

    /* RingMotion2D.as*/
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.motion.CircleMotion2D;
    import com.xinyu.science.Science;

    public class RingMotion2D extends Motion{
    private var _obj:Array;
    private var circleM2D:Array;
    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D2.CW){
    _obj = obj;

    circleM2D = new Array(_obj.length);
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i] = new CircleMotion2D(_obj[i], xOffset, yOffset, radius, speed, fps, dir, 360 / _obj.length * i);
    }
    }

    override public function start():void{
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i].start();
    }
    }

    override public function stop():void{
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i].stop();
    }
    }

    override public function get running():Boolean{
    return circleM2D[0].running;
    }
    }
    }

    乍看之下,這樣的寫法很簡潔且沒有問題,但是實際去做測試之後問題就會浮現了,那就是效能,一旦物件增多,fps加快,這個Class所用到的timer也會是成比例成長,縱使是再快的CPU也會有些微的影響 (當然你可能會質疑說,誰沒事會在畫面上產生幾百顆球? 但別忘了這個效果可能只是你的project的功能之一,且並不是所有的瀏覽者的電腦都是那麼快的)。

    為了降低程式的效能成本,我必須重新定義一個RingMotion,如下:

    /* RingMotion2D.as */
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.science.Science;

    public class RingMotion2D extends Motion{
    private var _timer:Timer;
    private var _radian:Array;
    private var _radius:Number;
    private var _speed:Number;
    private var _dir:int;
    private var _xOffset:Number;
    private var _yOffset:Number;
    private var _obj:Array;

    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D.CW){
    _obj = obj;
    _radian = new Array(_obj.length);
    for(var i:uint = 0; i<_obj.length; i++){
    _radian[i] = Science.degToRad(360/_obj.length * i);
    }

    _radius = radius;
    _speed = Math.min(1, Math.max(speed, 0.001));
    _dir = dir;
    _xOffset = xOffset;
    _yOffset = yOffset;

    _timer = new Timer(1000/fps);
    _timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    for(var i:uint = 0; i < _obj.length; i++){
    _obj[i].x = _radius * Math.cos(_radian[i]) + _xOffset - _radius;
    _obj[i].y = _radius * Math.sin(_radian[i]) + _yOffset;

    _radian[i] += _dir * _speed;
    if(_radian[i] > 2*Math.PI){
    _radian[i] = 0;
    }
    }
    }

    override public function start():void{
    _timer.start();
    }

    override public function stop():void{
    _timer.stop();
    }

    override public function get running():Boolean{
    return _timer.running;
    }
    }
    }

    無可否認這樣的寫法的確違反了OOP的精神,但是為了效能不得如此,因為這樣寫不管有多少個DisplayObject,我們都只需要一個Timer。

    看到這裡也許你會發現如果新的RingMotion2D裡只控制一個物件,那不就等於CircleMotion2D,所以理當CircleMotion2D應該要被RingMotion2D給取代掉才對? 未必,畢竟CircleMotion2D在處裡單顆物件上的效能會比RingMotion2D快一點點(也只有那麼微渺的一點點)。

No comments:

Post a Comment

Orange - data analysis tool

Installation pip install orange3 Run orange python -m Orange.canvas