Таймер, работающий синхронно с фокусом

Описание

Создал простой "движок" со статическим FPS-ом.

class StaticEngine extends EventTarget {
    constructor() {
        super();

        let previous = 0;
        /**
         * @param {DOMHighResTimeStamp} current 
         * @returns {void}
         */
        const callback = (current) => {
            const difference = current - previous;
            for (let index = 0; index < difference / this.#gap; index++) {
                if (this.#focus && this.launched) this.dispatchEvent(new Event(`update`));
                previous = current;
            }
            requestAnimationFrame(callback);
        };
        requestAnimationFrame(callback);

        window.addEventListener(`focus`, (event) => this.#focus = true);
        window.addEventListener(`blur`, (event) => this.#focus = false);
    }
    /** @type {boolean} */
    launched = false;
    /** @type {number} */
    #gap = 1000 / 120;
    /** @type {boolean} */
    #focus = document.hasFocus();
}

У него одна большая проблема: если фокус со вкладки теряется, то вызов dispatchEvent не происходит, но счетчик от requestAnimationFrame накапливается и когда фокус возвращается движку придется обрабатывать тысячи циклов сразу.
И даже тот факт, что я создал логику проверки фокуса не обеспечивает надежность на 100%, так как, к примеру в Edge есть баг при открытии Inspect element когда обработчик blur не срабатывает, или же когда переключаемся между вкладками через Alt + Tab.
К тому же я хочу получить возможность использовать движок в Worker-ах, а там document или window недоступны. Я, конечно, могу состояние фокуса отправить через message, но это нарушит целостность класса и костыль скорее чем решение.

Вопрос

Основной вопрос: как мне остановить таймер, когда вкладка не в фокусе?
Если основной не удается решить: чем мне заменить логику фокуса, чтобы хотя бы нормально работала в Worker-ах?


Ответы (1 шт):

Автор решения: eccs0103

Решение всему - setTimeout

Заменив requestAnimationFrame на setTimeout мы уберем зависимость от фокуса, и зависимость от document. Движок будет автоматически оптимизироваться для работы в фоновом режиме. А так же будет без проблем работать в Worker-ах.

class StaticEngine extends EventTarget {
    constructor() {
        super();

        let previous = 0;
        /**
         * @returns {void}
         */
        const callback = () => {
            const current = performance.now();
            const difference = current - previous;
            for (let index = 0; index < difference / this.#gap; index++) {
                if (this.launched) this.dispatchEvent(new Event(`update`));
                previous = current;
            }
            setTimeout(callback);
        };
        setTimeout(callback);
    }
    /** @type {boolean} */
    launched = false;
    /** @type {number} */
    #gap = 1000 / 120;
}
→ Ссылка