-
Notifications
You must be signed in to change notification settings - Fork 27.9k
/
languageFeatureDebounce.ts
139 lines (114 loc) · 4.47 KB
/
languageFeatureDebounce.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { doHash } from 'vs/base/common/hash';
import { LRUCache } from 'vs/base/common/map';
import { clamp, MovingAverage, SlidingWindowAverage } from 'vs/base/common/numbers';
import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { matchesScheme } from 'vs/platform/opener/common/opener';
export const ILanguageFeatureDebounceService = createDecorator<ILanguageFeatureDebounceService>('ILanguageFeatureDebounceService');
export interface ILanguageFeatureDebounceService {
readonly _serviceBrand: undefined;
for(feature: LanguageFeatureRegistry<object>, debugName: string, config?: { min?: number; max?: number; salt?: string }): IFeatureDebounceInformation;
}
export interface IFeatureDebounceInformation {
get(model: ITextModel): number;
update(model: ITextModel, value: number): number;
default(): number;
}
namespace IdentityHash {
const _hashes = new WeakMap<object, number>();
let pool = 0;
export function of(obj: object): number {
let value = _hashes.get(obj);
if (value === undefined) {
value = ++pool;
_hashes.set(obj, value);
}
return value;
}
}
class FeatureDebounceInformation implements IFeatureDebounceInformation {
private readonly _cache = new LRUCache<string, SlidingWindowAverage>(50, 0.7);
constructor(
private readonly _logService: ILogService,
private readonly _name: string,
private readonly _registry: LanguageFeatureRegistry<object>,
private readonly _default: number,
private readonly _min: number,
private readonly _max: number,
) { }
private _key(model: ITextModel): string {
return model.id + this._registry.all(model).reduce((hashVal, obj) => doHash(IdentityHash.of(obj), hashVal), 0);
}
get(model: ITextModel): number {
const key = this._key(model);
const avg = this._cache.get(key);
return avg
? clamp(avg.value, this._min, this._max)
: this.default();
}
update(model: ITextModel, value: number): number {
const key = this._key(model);
let avg = this._cache.get(key);
if (!avg) {
avg = new SlidingWindowAverage(6);
this._cache.set(key, avg);
}
const newValue = clamp(avg.update(value), this._min, this._max);
if (!matchesScheme(model.uri, 'output')) {
this._logService.trace(`[DEBOUNCE: ${this._name}] for ${model.uri.toString()} is ${newValue}ms`);
}
return newValue;
}
private _overall(): number {
const result = new MovingAverage();
for (const [, avg] of this._cache) {
result.update(avg.value);
}
return result.value;
}
default() {
const value = (this._overall() | 0) || this._default;
return clamp(value, this._min, this._max);
}
}
export class LanguageFeatureDebounceService implements ILanguageFeatureDebounceService {
declare _serviceBrand: undefined;
private readonly _data = new Map<string, FeatureDebounceInformation>();
constructor(@ILogService private readonly _logService: ILogService) {
}
for(feature: LanguageFeatureRegistry<object>, name: string, config?: { min?: number; max?: number; key?: string }): IFeatureDebounceInformation {
const min = config?.min ?? 50;
const max = config?.max ?? min ** 2;
const extra = config?.key ?? undefined;
const key = `${IdentityHash.of(feature)},${min}${extra ? ',' + extra : ''}`;
let info = this._data.get(key);
if (!info) {
info = new FeatureDebounceInformation(
this._logService,
name,
feature,
(this._overallAverage() | 0) || (min * 1.5), // default is overall default or derived from min-value
min,
max
);
this._data.set(key, info);
}
return info;
}
private _overallAverage(): number {
// Average of all language features. Not a great value but an approximation
const result = new MovingAverage();
for (const info of this._data.values()) {
result.update(info.default());
}
return result.value;
}
}
registerSingleton(ILanguageFeatureDebounceService, LanguageFeatureDebounceService, InstantiationType.Delayed);