diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dfcede38..4764ede4 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +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/Idle'; +import { IdleTaskQueue } from 'common/TaskQueue'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 3849bdd3..97258609 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { DebouncedIdleTask } from 'common/Idle'; +import { DebouncedIdleTask } from 'common/TaskQueue'; interface ISelectionState { start: [number, number] | undefined; diff --git a/src/common/Idle.ts b/src/common/TaskQueue.ts similarity index 53% rename from src/common/Idle.ts rename to src/common/TaskQueue.ts index e05ad068..3b1acfa6 100644 --- a/src/common/Idle.ts +++ b/src/common/TaskQueue.ts @@ -3,27 +3,43 @@ * @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 { +import { isNode } from 'common/Platform'; + +interface ITaskQueue { + /** + * Adds a task to the queue which will run in a future idle callback. + */ + enqueue(task: () => void): void; + + /** + * Flushes the queue, running all remaining tasks synchronously. + */ + flush(): void; + + /** + * Clears any remaining tasks from the queue, these will not be run. + */ + clear(): void; +} + +interface ITaskDeadline { + timeRemaining(): number; +} +type CallbackWithDeadline = (deadline: ITaskDeadline) => void; + +abstract class TaskQueue implements ITaskQueue { private _tasks: (() => void)[] = []; private _idleCallback?: number; private _i = 0; - /** - * Adds a task to the queue which will run in a future idle callback. - */ + protected abstract _requestCallback(callback: CallbackWithDeadline): number; + protected abstract _cancelCallback(identifier: number): void; + public enqueue(task: () => void): void { this._tasks.push(task); this._start(); } - /** - * Flushes the queue, running all remaining tasks synchronously. - */ public flush(): void { while (this._i < this._tasks.length) { this._tasks[this._i++](); @@ -31,9 +47,6 @@ export class IdleTaskQueue { this.clear(); } - /** - * Clears any remaining tasks from the queue, these will not be run. - */ public clear(): void { if (this._idleCallback) { cancelIdleCallback(this._idleCallback); @@ -69,12 +82,55 @@ export class IdleTaskQueue { } } +/** + * A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames + * per second. 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 PriorityTaskQueue extends TaskQueue { + protected _requestCallback(callback: CallbackWithDeadline): number { + return setTimeout(() => callback(this._createDeadline(16))); + } + + protected _cancelCallback(identifier: number): void { + clearTimeout(identifier); + } + + private _createDeadline(duration: number): ITaskDeadline { + const end = performance.now() + duration; + return { + timeRemaining: () => Math.max(0, end - performance.now()) + }; + } +} + +class IdleTaskQueueInternal extends TaskQueue { + protected _requestCallback(callback: IdleRequestCallback): number { + return requestIdleCallback(callback); + } + + protected _cancelCallback(identifier: number): void { + cancelIdleCallback(identifier); + } +} + +/** + * A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's + * deadline given by the environment. 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. + * + * This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? IdleTaskQueueInternal : PriorityTaskQueue; + /** * An object that tracks a single debounced task that will run on the next idle frame. When called * multiple times, only the last set task will run. */ export class DebouncedIdleTask { - private _queue: IdleTaskQueue; + private _queue: ITaskQueue; constructor() { this._queue = new IdleTaskQueue();