Use up the entire alloted time indicated by IdleDeadline, while still yielding as often as possible (#138887)

This commit is contained in:
Alex Dima 2021-12-14 12:11:17 +01:00
parent f4bcb5e75a
commit e4b2ca8ac9
No known key found for this signature in database
GPG key ID: 39563C1504FDD0C9
2 changed files with 42 additions and 17 deletions

View file

@ -1043,7 +1043,7 @@ declare function cancelIdleCallback(handle: number): void;
if (disposed) {
return;
}
const end = Date.now() + 3; // yield often
const end = Date.now() + 15; // one frame at 64fps
runner(Object.freeze({
didTimeout: true,
timeRemaining() {

View file

@ -16,6 +16,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore';
import { runWhenIdle, IdleDeadline } from 'vs/base/common/async';
import { setTimeout0 } from 'vs/base/common/platform';
const enum Constants {
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
@ -265,37 +266,61 @@ export class TextModelTokenization extends Disposable {
runWhenIdle((deadline) => {
this._isScheduled = false;
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow(deadline);
this._backgroundTokenizeWithDeadline(deadline);
});
}
private _revalidateTokensNow(deadline: IdleDeadline): void {
const textModelLastLineNumber = this._textModel.getLineCount();
/**
* Tokenize until the deadline occurs, but try to yield every 1-2ms.
*/
private _backgroundTokenizeWithDeadline(deadline: IdleDeadline): void {
// Read the time remaining from the `deadline` immediately because it is unclear
// if the `deadline` object will be valid after execution leaves this function.
const endTime = Date.now() + deadline.timeRemaining();
const MAX_ALLOWED_TIME = 1;
const execute = () => {
if (this._isDisposed || !this._textModel.isAttachedToEditor() || !this._hasLinesToTokenize()) {
// disposed in the meantime or detached or finished
return;
}
this._backgroundTokenizeForAtLeast1ms();
if (Date.now() < endTime) {
// There is still time before reaching the deadline, so yield to the browser and then
// continue execution
setTimeout0(execute);
} else {
// The deadline has been reached, so schedule a new idle callback if necessary
this._beginBackgroundTokenization();
}
};
execute();
}
/**
* Tokenize for at least 1ms.
*/
private _backgroundTokenizeForAtLeast1ms(): void {
const lineCount = this._textModel.getLineCount();
const builder = new MultilineTokensBuilder();
const sw = StopWatch.create(false);
let tokenizedLineNumber = -1;
do {
if (sw.elapsed() > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
if (sw.elapsed() > 1) {
// the comparison is intentionally > 1 and not >= 1 to ensure that
// a full millisecond has elapsed, given how microseconds are rounded
// to milliseconds
break;
}
tokenizedLineNumber = this._tokenizeOneInvalidLine(builder);
const tokenizedLineNumber = this._tokenizeOneInvalidLine(builder);
if (tokenizedLineNumber >= textModelLastLineNumber) {
if (tokenizedLineNumber >= lineCount) {
break;
}
} while (this._hasLinesToTokenize() && deadline.timeRemaining() > 0);
} while (this._hasLinesToTokenize());
this._beginBackgroundTokenization();
this._textModel.setTokens(builder.tokens, !this._hasLinesToTokenize());
}