관리 메뉴

도드넷

JAVASCRIPT#71 - 최적화1. 인터벌과 타임아웃 줄이기 본문

창고/JS KING 포니 [중단]

JAVASCRIPT#71 - 최적화1. 인터벌과 타임아웃 줄이기

도드! 2016. 7. 18. 12:16
반응형



aw so cute :3 look at her glasses and tie on her tail. aww

she is killin me with her cutieness <3 kill me ponies!!


JAVASCRIPT#71 - 최적화1. 인터벌과 타임아웃 줄이기


gf.refreshGame = function (){
    // update animations
    var finishedAnimations = [];
   
    for (var i=0; i < gf.animations.length; i++)
    {   
        var animate = gf.animations[i];
       
        animate.counter++;

        if (animate.counter == animate.animation.rate) {
            animate.counter = 0;
            animate.animation.currentFrame++;
            if(!animate.loop && animate.animation.currentFrame > animate.animation.numberOfFrame){
                finishedAnimations.push(i);
            } else {
                animate.animation.currentFrame %= animate.animation.numberOfFrame;
                gf.setFrame(animate.div, animate.animation);
            }
        }
    }

    for(var i=0; i < finishedAnimations.length; i++){
        gf.animations.splice(finishedAnimations[i], 1);
    }
   
    // execute the callbacks
    for (var i=0; i < gf.callbacks.length; i++) {
        var call  = gf.callbacks[i];
       
        call.counter++;
        if (call.counter == call.rate) {
            call.counter = 0;
            call.callback();
        }
    }
}


인터벌 그러니까 자동반복문을 하나만 쓰면서 여러 반복문을 동시에 쓰는,  하나의 인터벌로 모든 반복문을 사용하는 코딩 메소드를 공부하고있다.


위의 함수는 아래 프리로드 함수의 변형인 startGame에 의해 불러지게 된다.


gf.startGame = function(endCallback, progressCallback)
{
    var images = [];
    var total = gf.imagesToPreload.length;
    for (var i = 0; i < total; i++)
    {
        var image = new Images();
        image.src = gf.imagesToPreload[i];
        images.push(image);
    }

    var preloadingPoller = setInterval(function()
        {
            var counter = 0;
            var total = gf.imagesToPreload.length;
            for(var i = 0; i < total; i++)
            {
                if(images[i].complete)
                {
                    counter++;
                }
            }
            if(counter == total)
            {
                clearInterval(preloadingPoller);
                endCallback();
                setInterval(gf.refreshGame, gf.baseRate);
            }
            else
            {
                if(progressCallback)
                {
                    progressCallback(parseInt(counter / total * 100));
                }
            }

        }, 100);
}


사실 preloadingPoller 라는 인터벌이 먼저 실행되서 이미지 로딩을 체크하고 이후 모두 완료될경우 gf.refreshGame을 인터벌 형식으로 gf.baseRate 간격으로 반복하게 된다.


다시 돌아가서 이번공부의 주인공인 refreshGame을 살펴보면,


var finishedAnimations = [];


끝난 애니메이션의 수를 넣을 배열 정의하고있다.

이곳에는 끝난 애니메이션의 인덱스가 저장된다.


for (var i=0; i < gf.animations.length; i++)
    {   
        var animate = gf.animations[i];
       
        animate.counter++;

        if (animate.counter == animate.animation.rate) {
            animate.counter = 0;
            animate.animation.currentFrame++;
            if(!animate.loop && animate.animation.currentFrame > animate.animation.numberOfFrame){
                finishedAnimations.push(i);
            } else {
                animate.animation.currentFrame %= animate.animation.numberOfFrame;
                gf.setFrame(animate.div, animate.animation);
            }
        }
    }


첫번째 반복문이다 분석해보자.


for (var i=0; i < gf.animations.length; i++)


1. i를 0부터 1씩 증가시키며 animations배열의 길이 만큼 이하를 반복한다.


* animations? : 전에는 animationshandle ? 이라는 이름으로 인터벌들을 각자 만들어서 배열에 저장하는 방식을 사용했지만 이제부터는 인터벌을 하나만 써야하므로 이를 대체하는 장치이자 배열이다. 이 배열안에는 setAnimation 함수에 의해서 만들어진 애니메이션들이 추가 된다. (아래)


gf.setAnimation = function(divId, animation, loop){
    var animate = {
        animation: animation,
        div: divId,
        loop: loop,
        counter: 0
    }
   
    if(animation.url){
        $("#"+divId).css("backgroundImage","url('"+animation.url+"')");
    }
   
    // search if this div already has an animation
    var divFound = false;
   
    for (var i = 0; i < gf.animations.length; i++) {
        if(gf.animations[i].div == divId){
            divFound = true;
            gf.animations[i] = animate;
        }
    }
   
    // otherwise we add it to the array
    if(!divFound) {
        gf.animations.push(animate);
    }
}


변형된 setAnimation함수는 이제 애니메이션 객체를 생성하면서 animations 배열안에 자신의 객체인 animate를 집어넣는 역할을 하게 된다.


var animate = gf.animations[i];


2. setAnimation 함수에서봤던 animate 객체가 또나왔다. 하지만 전혀다른 지역객체이다. 위의 setAnimation 함수에서는 만들어진 animate를 animations에 넣었으나 여기서는 반대로 animations에 있는 배열의 요소를 animate에 전달하고 있다.


animate.counter++;


3. animate 함수의 고유값인 "카운터"값을 1 증가시키고 있다.


if (animate.counter == animate.animation.rate)


4. 첫키스같은 첫번째 조건문이 등장한다. 그만큼 중요한데, 보면 2번에서 방금만든 animate 객체의 counter값과 animation의 rate이 같으면 실행하게 된다.


삼중으로 이어진 animate.animation.rate을 좀 살펴보자.


animate에는 여러 값들이 있는데 animation은 그 중하나로 의미는 어떤 애니메이션인지를 가리킨다.

즉 무슨 run stand attack 같은거겠지.


그리고 animation 이라는 객체는 애니메이션 함수에 의해서 만들어진다.


gf.animation = function(options)
{
    var defaultValues = {
        url : false,
        width : 64,
        numberOfFrames : 1,
        currentFrame : 0,
        rate : 1
    }
    $.extend(this, defaultValues, options);
    if(options.rate){
        // normalize the animation rate
        this.rate = Math.round(this.rate / gf.baseRate);
    }
    if(this.url){
        gf.addImage(this.url);
    }
}


animation 객체를 만드는 함수이다.


animate.animation.rate 객체가 객체를 포함하고 그 객체의 값인 rate를 불러내고 있다고 보면 된다.


위의 함수에서도 잠깐 살펴봐야 추가사항이 있는데 바로...


this.rate = Math.round(this.rate / gf.baseRate);

이다. Math.round 메소드는 소수점을 반올림 처리해서 가장 가까운 정수를 반환하는 메소드이다.


Math.round(20.49) = 20

Math.round(100.5) = 101

Math.round(-12.2) = -12
Math.round(-12.6) = -13


위에서 베이스 레이트로 나눠줘서 rate를 정의하는 이유는 "하나의 인터벌"의 반복주기에 맞추기 위해서다.

만약 베이스 레이트가 30ms 이고 animation 객체의 rate가 30이면 그대로 주 인터벌(refreshGame)의 반복주기에

맞춰서 1주기에 1회실행하게 된다.


만약 30ms 보다 더 짧은 15ms이면 0을 반환하게 되는데 그러면 아예 실행되지 않는다.


animate.counter = 0;


4-1. 만약 animation의 레이트값과 animate 카운터값이 일치하면, 가장먼저 카운터값을 초기화시키는데 이는 해당 animate 객체로부터 볼일이 끝났기 때문이다. 다시 써먹을때 꼬이지 않게 초기화 시켜주는거라고 보면 된다.


animate.animation.currentFrame++;


4-2. animate객체의 animation객체의 currentFrame 값을 증가시킨다.

애니메이션에서 다음 프레임을 보여주기 위함이다.


if(!animate.loop && animate.animation.currentFrame > animate.animation.numberOfFrame){
                finishedAnimations.push(i);
            } else {
                animate.animation.currentFrame %= animate.animation.numberOfFrame;
                gf.setFrame(animate.div, animate.animation);
            }


4-3. 조건문안의 조건문으로 setAnimation라는 함수가 refreshGame으로 들어왔다는걸 알수있다. 이 함수는 루프가 아니고 재생할 프레임이 최대 프레임을 초과하가지 않았을 경우 애니메이션을 재생하고 해당 프레임을 최대 프레임으로 제한하고 프레임을 설정, 만약 루프가 아니거나 최대 프레임에 도달시 끝난 애니메이션 배열인 finishedAnimations에 해당 인덱스를 추가, refreshGame 막바지에 삭제한다.


아래는 참고용 setFrame 함수


gf.setFrame = function(divId, animation) {
    $("#" + divId).css("bakgroundPosition", "" + animation.currentFrame * animation.width + "px 0px");
}


for(var i=0; i < finishedAnimations.length; i++){
        gf.animations.splice(finishedAnimations[i], 1);
    }


5. 이제 for문을 나왔으나 다시 for문에 맞닥들이게 된다. 이것은 4-3에서 마지막에 도달한, 즉 애니메이션을 끝낸

애니메이션을  animations 배열로부터 도려내는 역할을 한다.


for (var i=0; i < gf.callbacks.length; i++) {
        var call  = gf.callbacks[i];
       
        call.counter++;
        if (call.counter == call.rate) {
            call.counter = 0;
            call.callback();
        }
    }


6. 또다른 for문, 마지막이니 잘 살펴보자. callbacks라는 배열의 길이에 의해 작동되고 call 에 callbacks의 요소들이 저장되고 call역시 animate 내부객체 처럼 counter라는 값을 가지고 있다. 이를 1증가시키고 만약 counter와 rate가 일치하면 counter를 초기화하고 callback을 부른다.  


가장 첫번째에서 만난 for문과 논리구조가 동일하다. 이번 for의 주인공인 callbacks라는 객체를 좀 살펴보자.


callbacks애들은 다음 두 함수에 의해 만들어지고 있다.


gf.callbacks = [];

gf.addCallback = function(callback, rate){
    gf.callbacks.push({
        callback: callback,
        rate: Math.round(rate / gf.baseRate),
        counter: 0
    });
}


반복문을 생성, 추가하고 있다. 


7. 마치며

- 인터벌을 하나만 굴려서 시스템 효율을 증가시키고 버그를 줄이기 위한 최적화 과정이였다.

핵심 개념은 하나의 인터벌을 무한반복 시켜두고 counter 값을 증가시키고 rate값이 일치할경우 재생후 counter값을 초기화시키고 반복. 





반응형
Comments