Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/services/languageFeatureDebounce.ts
3295 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 { doHash } from '../../../base/common/hash.js';
7
import { LRUCache } from '../../../base/common/map.js';
8
import { clamp, MovingAverage, SlidingWindowAverage } from '../../../base/common/numbers.js';
9
import { LanguageFeatureRegistry } from '../languageFeatureRegistry.js';
10
import { ITextModel } from '../model.js';
11
import { IEnvironmentService } from '../../../platform/environment/common/environment.js';
12
import { InstantiationType, registerSingleton } from '../../../platform/instantiation/common/extensions.js';
13
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
14
import { ILogService } from '../../../platform/log/common/log.js';
15
import { matchesScheme } from '../../../base/common/network.js';
16
17
18
export const ILanguageFeatureDebounceService = createDecorator<ILanguageFeatureDebounceService>('ILanguageFeatureDebounceService');
19
20
export interface ILanguageFeatureDebounceService {
21
22
readonly _serviceBrand: undefined;
23
24
for(feature: LanguageFeatureRegistry<object>, debugName: string, config?: { min?: number; max?: number; salt?: string }): IFeatureDebounceInformation;
25
}
26
27
export interface IFeatureDebounceInformation {
28
get(model: ITextModel): number;
29
update(model: ITextModel, value: number): number;
30
default(): number;
31
}
32
33
namespace IdentityHash {
34
const _hashes = new WeakMap<object, number>();
35
let pool = 0;
36
export function of(obj: object): number {
37
let value = _hashes.get(obj);
38
if (value === undefined) {
39
value = ++pool;
40
_hashes.set(obj, value);
41
}
42
return value;
43
}
44
}
45
46
class NullDebounceInformation implements IFeatureDebounceInformation {
47
48
constructor(private readonly _default: number) { }
49
50
get(_model: ITextModel): number {
51
return this._default;
52
}
53
update(_model: ITextModel, _value: number): number {
54
return this._default;
55
}
56
default(): number {
57
return this._default;
58
}
59
}
60
61
class FeatureDebounceInformation implements IFeatureDebounceInformation {
62
63
private readonly _cache = new LRUCache<string, SlidingWindowAverage>(50, 0.7);
64
65
constructor(
66
private readonly _logService: ILogService,
67
private readonly _name: string,
68
private readonly _registry: LanguageFeatureRegistry<object>,
69
private readonly _default: number,
70
private readonly _min: number,
71
private readonly _max: number,
72
) { }
73
74
private _key(model: ITextModel): string {
75
return model.id + this._registry.all(model).reduce((hashVal, obj) => doHash(IdentityHash.of(obj), hashVal), 0);
76
}
77
78
get(model: ITextModel): number {
79
const key = this._key(model);
80
const avg = this._cache.get(key);
81
return avg
82
? clamp(avg.value, this._min, this._max)
83
: this.default();
84
}
85
86
update(model: ITextModel, value: number): number {
87
const key = this._key(model);
88
let avg = this._cache.get(key);
89
if (!avg) {
90
avg = new SlidingWindowAverage(6);
91
this._cache.set(key, avg);
92
}
93
const newValue = clamp(avg.update(value), this._min, this._max);
94
if (!matchesScheme(model.uri, 'output')) {
95
this._logService.trace(`[DEBOUNCE: ${this._name}] for ${model.uri.toString()} is ${newValue}ms`);
96
}
97
return newValue;
98
}
99
100
private _overall(): number {
101
const result = new MovingAverage();
102
for (const [, avg] of this._cache) {
103
result.update(avg.value);
104
}
105
return result.value;
106
}
107
108
default() {
109
const value = (this._overall() | 0) || this._default;
110
return clamp(value, this._min, this._max);
111
}
112
}
113
114
115
export class LanguageFeatureDebounceService implements ILanguageFeatureDebounceService {
116
117
declare _serviceBrand: undefined;
118
119
private readonly _data = new Map<string, IFeatureDebounceInformation>();
120
private readonly _isDev: boolean;
121
122
constructor(
123
@ILogService private readonly _logService: ILogService,
124
@IEnvironmentService envService: IEnvironmentService,
125
) {
126
127
this._isDev = envService.isExtensionDevelopment || !envService.isBuilt;
128
}
129
130
for(feature: LanguageFeatureRegistry<object>, name: string, config?: { min?: number; max?: number; key?: string }): IFeatureDebounceInformation {
131
const min = config?.min ?? 50;
132
const max = config?.max ?? min ** 2;
133
const extra = config?.key ?? undefined;
134
const key = `${IdentityHash.of(feature)},${min}${extra ? ',' + extra : ''}`;
135
let info = this._data.get(key);
136
if (!info) {
137
if (this._isDev) {
138
this._logService.debug(`[DEBOUNCE: ${name}] is disabled in developed mode`);
139
info = new NullDebounceInformation(min * 1.5);
140
} else {
141
info = new FeatureDebounceInformation(
142
this._logService,
143
name,
144
feature,
145
(this._overallAverage() | 0) || (min * 1.5), // default is overall default or derived from min-value
146
min,
147
max
148
);
149
}
150
this._data.set(key, info);
151
}
152
return info;
153
}
154
155
private _overallAverage(): number {
156
// Average of all language features. Not a great value but an approximation
157
const result = new MovingAverage();
158
for (const info of this._data.values()) {
159
result.update(info.default());
160
}
161
return result.value;
162
}
163
}
164
165
registerSingleton(ILanguageFeatureDebounceService, LanguageFeatureDebounceService, InstantiationType.Delayed);
166
167