Path: blob/main/src/vs/editor/browser/gpu/taskQueue.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { getActiveWindow } from '../../../base/browser/dom.js';6import { Disposable, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js';7import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';8import { ILogService } from '../../../platform/log/common/log.js';910/**11* Copyright (c) 2022 The xterm.js authors. All rights reserved.12* @license MIT13*/1415export interface ITaskQueue extends IDisposable {16/**17* Adds a task to the queue which will run in a future idle callback.18* To avoid perceivable stalls on the mainthread, tasks with heavy workload19* should split their work into smaller pieces and return `true` to get20* called again until the work is done (on falsy return value).21*/22enqueue(task: () => boolean | void): void;2324/**25* Flushes the queue, running all remaining tasks synchronously.26*/27flush(): void;2829/**30* Clears any remaining tasks from the queue, these will not be run.31*/32clear(): void;33}3435interface ITaskDeadline {36timeRemaining(): number;37}38type CallbackWithDeadline = (deadline: ITaskDeadline) => void;3940abstract class TaskQueue extends Disposable implements ITaskQueue {41private _tasks: (() => boolean | void)[] = [];42private _idleCallback?: number;43private _i = 0;4445constructor(46@ILogService private readonly _logService: ILogService47) {48super();49this._register(toDisposable(() => this.clear()));50}5152protected abstract _requestCallback(callback: CallbackWithDeadline): number;53protected abstract _cancelCallback(identifier: number): void;5455public enqueue(task: () => boolean | void): void {56this._tasks.push(task);57this._start();58}5960public flush(): void {61while (this._i < this._tasks.length) {62if (!this._tasks[this._i]()) {63this._i++;64}65}66this.clear();67}6869public clear(): void {70if (this._idleCallback) {71this._cancelCallback(this._idleCallback);72this._idleCallback = undefined;73}74this._i = 0;75this._tasks.length = 0;76}7778private _start(): void {79if (!this._idleCallback) {80this._idleCallback = this._requestCallback(this._process.bind(this));81}82}8384private _process(deadline: ITaskDeadline): void {85this._idleCallback = undefined;86let taskDuration = 0;87let longestTask = 0;88let lastDeadlineRemaining = deadline.timeRemaining();89let deadlineRemaining = 0;90while (this._i < this._tasks.length) {91taskDuration = Date.now();92if (!this._tasks[this._i]()) {93this._i++;94}95// other than performance.now, Date.now might not be stable (changes on wall clock changes),96// this is not an issue here as a clock change during a short running task is very unlikely97// in case it still happened and leads to negative duration, simply assume 1 msec98taskDuration = Math.max(1, Date.now() - taskDuration);99longestTask = Math.max(taskDuration, longestTask);100// Guess the following task will take a similar time to the longest task in this batch, allow101// additional room to try avoid exceeding the deadline102deadlineRemaining = deadline.timeRemaining();103if (longestTask * 1.5 > deadlineRemaining) {104// Warn when the time exceeding the deadline is over 20ms, if this happens in practice the105// task should be split into sub-tasks to ensure the UI remains responsive.106if (lastDeadlineRemaining - taskDuration < -20) {107this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(lastDeadlineRemaining - taskDuration))}ms`);108}109this._start();110return;111}112lastDeadlineRemaining = deadlineRemaining;113}114this.clear();115}116}117118/**119* A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames120* per second. The tasks will run in the order they are enqueued, but they will run some time later,121* and care should be taken to ensure they're non-urgent and will not introduce race conditions.122*/123export class PriorityTaskQueue extends TaskQueue {124protected _requestCallback(callback: CallbackWithDeadline): number {125return getActiveWindow().setTimeout(() => callback(this._createDeadline(16)));126}127128protected _cancelCallback(identifier: number): void {129getActiveWindow().clearTimeout(identifier);130}131132private _createDeadline(duration: number): ITaskDeadline {133const end = Date.now() + duration;134return {135timeRemaining: () => Math.max(0, end - Date.now())136};137}138}139140class IdleTaskQueueInternal extends TaskQueue {141protected _requestCallback(callback: IdleRequestCallback): number {142return getActiveWindow().requestIdleCallback(callback);143}144145protected _cancelCallback(identifier: number): void {146getActiveWindow().cancelIdleCallback(identifier);147}148}149150/**151* A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's152* deadline given by the environment. The tasks will run in the order they are enqueued, but they153* will run some time later, and care should be taken to ensure they're non-urgent and will not154* introduce race conditions.155*156* This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks.157*/158export const IdleTaskQueue = ('requestIdleCallback' in getActiveWindow()) ? IdleTaskQueueInternal : PriorityTaskQueue;159160/**161* An object that tracks a single debounced task that will run on the next idle frame. When called162* multiple times, only the last set task will run.163*/164export class DebouncedIdleTask {165private _queue: ITaskQueue;166167constructor(168@IInstantiationService instantiationService: IInstantiationService169) {170this._queue = instantiationService.createInstance(IdleTaskQueue);171}172173public set(task: () => boolean | void): void {174this._queue.clear();175this._queue.enqueue(task);176}177178public flush(): void {179this._queue.flush();180}181}182183184