Adopt runWhenIdle (#133173)

This commit is contained in:
Alex Dima 2021-11-22 14:51:21 +01:00
parent 7be48d527a
commit 2fa5d4f76e
No known key found for this signature in database
GPG key ID: 39563C1504FDD0C9
2 changed files with 63 additions and 15 deletions

View file

@ -968,6 +968,45 @@ export interface IdleDeadline {
readonly didTimeout: boolean;
timeRemaining(): number;
}
/**
* See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
*
* Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay
* that browsers set when the nesting level is > 5.
*/
const scheduleAsyncWork = (() => {
if (typeof globalThis.postMessage === 'function' && !globalThis.importScripts) {
interface IQueueElement {
id: number;
callback: () => void;
}
let pending: IQueueElement[] = [];
globalThis.addEventListener('message', (e: MessageEvent) => {
if (e.data && e.data.vscodeScheduleAsyncWork) {
for (let i = 0, len = pending.length; i < len; i++) {
const candidate = pending[i];
if (candidate.id === e.data.vscodeScheduleAsyncWork) {
pending.splice(i, 1);
candidate.callback();
return;
}
}
}
});
let lastId = 0;
return (callback: () => void) => {
const myId = ++lastId;
pending.push({
id: myId,
callback: callback
});
globalThis.postMessage({ vscodeScheduleAsyncWork: myId }, '*');
};
}
return (callback: () => void) => setTimeout(callback);
})();
/**
* Execute the callback the next time the browser is idle
*/
@ -979,8 +1018,11 @@ declare function cancelIdleCallback(handle: number): void;
(function () {
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
runWhenIdle = (runner) => {
const handle = setTimeout(() => {
const end = Date.now() + 15; // one frame at 64fps
scheduleAsyncWork(() => {
if (disposed) {
return;
}
const end = Date.now() + 3; // yield often
runner(Object.freeze({
didTimeout: true,
timeRemaining() {
@ -995,7 +1037,6 @@ declare function cancelIdleCallback(handle: number): void;
return;
}
disposed = true;
clearTimeout(handle);
}
};
};

View file

@ -15,7 +15,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { Disposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore';
import * as platform from 'vs/base/common/platform';
import { runWhenIdle } from 'vs/base/common/async';
const enum Constants {
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
@ -255,19 +255,26 @@ export class TextModelTokenization extends Disposable {
this._beginBackgroundTokenization();
}
private _isScheduled = false;
private _beginBackgroundTokenization(): void {
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize()) {
platform.setImmediate(() => {
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow();
});
if (this._isScheduled || !this._textModel.isAttachedToEditor() || !this._hasLinesToTokenize()) {
return;
}
this._isScheduled = true;
runWhenIdle((deadline) => {
this._isScheduled = false;
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow(deadline);
});
}
private _revalidateTokensNow(): void {
private _revalidateTokensNow(deadline: IdleDeadline): void {
const textModelLastLineNumber = this._textModel.getLineCount();
const MAX_ALLOWED_TIME = 1;
@ -275,7 +282,7 @@ export class TextModelTokenization extends Disposable {
const sw = StopWatch.create(false);
let tokenizedLineNumber = -1;
while (this._hasLinesToTokenize()) {
do {
if (sw.elapsed() > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
break;
@ -286,7 +293,7 @@ export class TextModelTokenization extends Disposable {
if (tokenizedLineNumber >= textModelLastLineNumber) {
break;
}
}
} while (this._hasLinesToTokenize() && deadline.timeRemaining() > 0);
this._beginBackgroundTokenization();
this._textModel.setTokens(builder.tokens, !this._hasLinesToTokenize());