Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/taskQueue.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { getActiveWindow } from '../../../base/browser/dom.js';
7
import { Disposable, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js';
8
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
9
import { ILogService } from '../../../platform/log/common/log.js';
10
11
/**
12
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
13
* @license MIT
14
*/
15
16
export interface ITaskQueue extends IDisposable {
17
/**
18
* Adds a task to the queue which will run in a future idle callback.
19
* To avoid perceivable stalls on the mainthread, tasks with heavy workload
20
* should split their work into smaller pieces and return `true` to get
21
* called again until the work is done (on falsy return value).
22
*/
23
enqueue(task: () => boolean | void): void;
24
25
/**
26
* Flushes the queue, running all remaining tasks synchronously.
27
*/
28
flush(): void;
29
30
/**
31
* Clears any remaining tasks from the queue, these will not be run.
32
*/
33
clear(): void;
34
}
35
36
interface ITaskDeadline {
37
timeRemaining(): number;
38
}
39
type CallbackWithDeadline = (deadline: ITaskDeadline) => void;
40
41
abstract class TaskQueue extends Disposable implements ITaskQueue {
42
private _tasks: (() => boolean | void)[] = [];
43
private _idleCallback?: number;
44
private _i = 0;
45
46
constructor(
47
@ILogService private readonly _logService: ILogService
48
) {
49
super();
50
this._register(toDisposable(() => this.clear()));
51
}
52
53
protected abstract _requestCallback(callback: CallbackWithDeadline): number;
54
protected abstract _cancelCallback(identifier: number): void;
55
56
public enqueue(task: () => boolean | void): void {
57
this._tasks.push(task);
58
this._start();
59
}
60
61
public flush(): void {
62
while (this._i < this._tasks.length) {
63
if (!this._tasks[this._i]()) {
64
this._i++;
65
}
66
}
67
this.clear();
68
}
69
70
public clear(): void {
71
if (this._idleCallback) {
72
this._cancelCallback(this._idleCallback);
73
this._idleCallback = undefined;
74
}
75
this._i = 0;
76
this._tasks.length = 0;
77
}
78
79
private _start(): void {
80
if (!this._idleCallback) {
81
this._idleCallback = this._requestCallback(this._process.bind(this));
82
}
83
}
84
85
private _process(deadline: ITaskDeadline): void {
86
this._idleCallback = undefined;
87
let taskDuration = 0;
88
let longestTask = 0;
89
let lastDeadlineRemaining = deadline.timeRemaining();
90
let deadlineRemaining = 0;
91
while (this._i < this._tasks.length) {
92
taskDuration = Date.now();
93
if (!this._tasks[this._i]()) {
94
this._i++;
95
}
96
// other than performance.now, Date.now might not be stable (changes on wall clock changes),
97
// this is not an issue here as a clock change during a short running task is very unlikely
98
// in case it still happened and leads to negative duration, simply assume 1 msec
99
taskDuration = Math.max(1, Date.now() - taskDuration);
100
longestTask = Math.max(taskDuration, longestTask);
101
// Guess the following task will take a similar time to the longest task in this batch, allow
102
// additional room to try avoid exceeding the deadline
103
deadlineRemaining = deadline.timeRemaining();
104
if (longestTask * 1.5 > deadlineRemaining) {
105
// Warn when the time exceeding the deadline is over 20ms, if this happens in practice the
106
// task should be split into sub-tasks to ensure the UI remains responsive.
107
if (lastDeadlineRemaining - taskDuration < -20) {
108
this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(lastDeadlineRemaining - taskDuration))}ms`);
109
}
110
this._start();
111
return;
112
}
113
lastDeadlineRemaining = deadlineRemaining;
114
}
115
this.clear();
116
}
117
}
118
119
/**
120
* A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames
121
* per second. The tasks will run in the order they are enqueued, but they will run some time later,
122
* and care should be taken to ensure they're non-urgent and will not introduce race conditions.
123
*/
124
export class PriorityTaskQueue extends TaskQueue {
125
protected _requestCallback(callback: CallbackWithDeadline): number {
126
return getActiveWindow().setTimeout(() => callback(this._createDeadline(16)));
127
}
128
129
protected _cancelCallback(identifier: number): void {
130
getActiveWindow().clearTimeout(identifier);
131
}
132
133
private _createDeadline(duration: number): ITaskDeadline {
134
const end = Date.now() + duration;
135
return {
136
timeRemaining: () => Math.max(0, end - Date.now())
137
};
138
}
139
}
140
141
class IdleTaskQueueInternal extends TaskQueue {
142
protected _requestCallback(callback: IdleRequestCallback): number {
143
return getActiveWindow().requestIdleCallback(callback);
144
}
145
146
protected _cancelCallback(identifier: number): void {
147
getActiveWindow().cancelIdleCallback(identifier);
148
}
149
}
150
151
/**
152
* A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's
153
* deadline given by the environment. The tasks will run in the order they are enqueued, but they
154
* will run some time later, and care should be taken to ensure they're non-urgent and will not
155
* introduce race conditions.
156
*
157
* This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks.
158
*/
159
export const IdleTaskQueue = ('requestIdleCallback' in getActiveWindow()) ? IdleTaskQueueInternal : PriorityTaskQueue;
160
161
/**
162
* An object that tracks a single debounced task that will run on the next idle frame. When called
163
* multiple times, only the last set task will run.
164
*/
165
export class DebouncedIdleTask {
166
private _queue: ITaskQueue;
167
168
constructor(
169
@IInstantiationService instantiationService: IInstantiationService
170
) {
171
this._queue = instantiationService.createInstance(IdleTaskQueue);
172
}
173
174
public set(task: () => boolean | void): void {
175
this._queue.clear();
176
this._queue.enqueue(task);
177
}
178
179
public flush(): void {
180
this._queue.flush();
181
}
182
}
183
184