From c42f290eb8cadea56eff5b0f6dbfb995b4048c9e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:10:26 -0700 Subject: [PATCH 1/2] Do char atlas warmup via new IdleTaskQueue --- .../src/atlas/WebglCharAtlas.ts | 18 ++++---- src/common/IdleTaskQueue.ts | 41 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/common/IdleTaskQueue.ts diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 092cf2e5be..d23caac067 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 0000000000..6d3b341b1f --- /dev/null +++ b/src/common/IdleTaskQueue.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export class IdleTaskQueue { + private _tasks: Function[] = []; + private _idleCallback?: number; + private _maxTaskDuration: number; + private _i = 0; + + constructor(targetFps: number = 240) { + this._maxTaskDuration = 1000 / targetFps; + } + + 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; + } +} From 270ac1c7d9b6b3d23e99652f68e24d6c867aa1cc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:17:39 -0700 Subject: [PATCH 2/2] Docs --- src/common/IdleTaskQueue.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/common/IdleTaskQueue.ts b/src/common/IdleTaskQueue.ts index 6d3b341b1f..240a26ff60 100644 --- a/src/common/IdleTaskQueue.ts +++ b/src/common/IdleTaskQueue.ts @@ -3,16 +3,27 @@ * @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();