diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 092cf2e5..d23caac0 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,6 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; +import { IdleTaskQueue } from 'common/IdleTaskQueue'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -124,20 +125,21 @@ export class WebglCharAtlas implements IDisposable { public warmUp(): void { if (!this._didWarmUp) { - (typeof requestIdleCallback !== 'function' ? requestIdleCallback : setTimeout)(() => { - this._doWarmUp(); - }); + this._doWarmUp(); this._didWarmUp = true; } } private _doWarmUp(): void { - // Pre-fill with ASCII 33-126 + // Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks + const queue = new IdleTaskQueue(); for (let i = 33; i < 126; i++) { - if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); - } + queue.enqueue(() => { + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + } + }); } } diff --git a/src/common/IdleTaskQueue.ts b/src/common/IdleTaskQueue.ts new file mode 100644 index 00000000..240a26ff --- /dev/null +++ b/src/common/IdleTaskQueue.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * A queue of that runs tasks over several idle callbacks, trying to maintain the specified + * frame rate. The tasks will run in the order they are enqueued, but they will run some time later, + * and care should be taken to ensure they're non-urgent and will not introduce race conditions. + */ +export class IdleTaskQueue { + private _tasks: Function[] = []; + private _idleCallback?: number; + private _maxTaskDuration: number; + private _i = 0; + + /** + * @param targetFps The target frame rate. + */ + constructor(targetFps: number = 240) { + this._maxTaskDuration = 1000 / targetFps; + } + + /** + * Adds a task to the queue which will run in a future idle callback. + */ + public enqueue(task: Function): void { + this._tasks.push(task); + this._start(); + } + + private _start(): void { + if (!this._idleCallback) { + this._idleCallback = requestIdleCallback(() => this._process()); + } + } + + private _process(): void { + const start = performance.now(); + this._idleCallback = undefined; + while (this._i < this._tasks.length) { + this._tasks[this._i++](); + if (performance.now() - start > this._maxTaskDuration) { + this._start(); + return; + } + } + // Clear the queue + this._i = 0; + this._tasks.length = 0; + } +}