티스토리 뷰

 

오늘도 평소와 다름없이 내 깃허브에 소중하게 관리되고 있는 나의 애기들, 패키지들을 유지보수하기 위해 코드를 살펴보던 중, animatable-js의 Ticker 클래스가 눈에 띄었다. 이 클래스를 자세히 살펴보던 순간, 자바스크립트의 requestAnimationFrame 함수를 빈번하게 호출하면 브라우저의 렌더링 동기화 과정에서 상당한 성능 비용이 발생하지 않을까 하는 작은 의문이 들었다.


물론 자바스크립트 내에서 콜백 함수를 등록하고 호출하는 행위 자체는 그리 큰 비용이 들지 않는다. 하지만 requestAnimationFrame은 브라우저가 제공하는 API이며, 이를 통한 렌더링 동기화 과정에서 발생할 수 있는 여러 가지 성능 이슈의 가능성을 고려해 보았다.


이러한 고민 끝에 실제로 테스트를 해보는 것이 가장 좋겠다는 결론에 도달했다. 그 결과로, Ticker 클래스는 콜백 함수만을 관리하도록 변경하고, 실제 틱(tick)을 관리하는 책임은 새로 만든 TickerBinding이라는 싱글톤 클래스로 분리하여 구현하기로 결정했다. 이렇게 함으로써 requestAnimationFrame의 호출을 최소화하고, 전체적인 애니메이션 성능을 개선할 수 있을 것이라고 생각했다.

 

/**
 * This class is essential to implementing animation.
 * 
 * Used to define a elapsed duration between a previous frame and
 * the current frame when a frame updated.
 * 
 * Used by `Animation` class.
 */
export class Ticker {
    /**
     * When an instance is created by this constructor, a related task
     * is performed immediately.
     * 
     * See also:
     * @important A dispose() must be called after the ticker is used.
     */
    constructor(public callback: TickerCallback) {
        TickerBinding.instance.addListener(callback);
    }

    /** Cancels the registered animation frame listener. */
    dispose() {
        TickerBinding.instance.removeListener(this.callback);
    }
}
import { TickerCallback } from "./type";

/**
 * Used to resolve overheads about the performance caused by frequent
 * `requestAnimationFrame` calls.
 */
export class TickerBinding {
    /** Whether the frame is not detected by ticker anymore. */
    private isDisposed: boolean = false;

    /** Unique id of the requested animation frame listener. */
    private id: number;

    /** A elapsed duration of the previous frame. */
    private previousElapsed: number;

    private static _instance: TickerBinding;
    private constructor() {
        this.id = requestAnimationFrame(this.handle = this.handle.bind(this));
    }

    static get instance() {
        return this._instance ?? (this._instance = new TickerBinding());
    }

    /** Defines the callback function that must be called when the new tick updated. */
    private callbacks: TickerCallback[] = [];

    set onTick(callback: TickerCallback) {
        this.addListener(callback);
    }

    set unTick(callback: TickerCallback) {
        this.removeListener(callback);
    }

    addListener(callback: TickerCallback) {
        console.assert(!this.callbacks.includes(callback), "Already exists a given callback function.");
        this.callbacks.push(callback);
    }

    removeListener(callback: TickerCallback) {
        console.assert(this.callbacks.includes(callback), "Already not exists a given callback function.");
        this.callbacks = this.callbacks.filter(c => c !== callback);
    }

    /** Notifies a new delta value updated for a registered ticker listeners. */
    notifyTick(delta: number) {
        this.callbacks.forEach(func => func(delta));
    }

    /** Called whenever a frame is updated. */
    handle(elapsed: number) {
        if (this.isDisposed) return;

        // Starting with a delta value of 0 is a commonly task.
        const delta = elapsed - (this.previousElapsed ?? elapsed);
                                 this.previousElapsed = elapsed;

        this.notifyTick(delta);
        this.id = requestAnimationFrame(this.handle);
    }

    /** Cancels the registered animation frame listener. */
    dispose() {
        this.isDisposed = true;
        cancelAnimationFrame(this.id);
    }
}

 

다음과 같이 먼저 역할을 분리하고 TickerBinding에는 싱글톤을 적용시켜 최적화하고 이를 테스트를 해본 결과.

 

 

보여지는 것은 어림잡아 대충 300개 정도이지만 런타임 내부에서는 100,000개가 넘는 애니메이션 인스턴스가 존재하고 값을 업데이트하고 있다. (넘 많으면 렌더링에 의해 프레임 드랍이 발생하고 정확하지 않은 결과를 불러온다, 애초부터 대충 렉이 걸리는지 안걸리는 지만 육안으로 확인하면 되기 때문에)

 

결과적으로는 성능 향상 폭이 거의 600% 이상이 넘는 미친 결과를 불러와버린 것이였다. (히익!)

 

 

animatable-js

This package allows easy and light implementation of linear or curved animation in javascript. (Especially suitable in a development environment on web components or canvas.). Latest version: 1.2.22, last published: 19 hours ago. Start using animatable-js

www.npmjs.com

'일기' 카테고리의 다른 글

[GitHub] 비상 초비상!!!  (0) 2024.08.15