Path: blob/main/src/vs/editor/common/services/languageFeatureDebounce.ts
3295 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { doHash } from '../../../base/common/hash.js';6import { LRUCache } from '../../../base/common/map.js';7import { clamp, MovingAverage, SlidingWindowAverage } from '../../../base/common/numbers.js';8import { LanguageFeatureRegistry } from '../languageFeatureRegistry.js';9import { ITextModel } from '../model.js';10import { IEnvironmentService } from '../../../platform/environment/common/environment.js';11import { InstantiationType, registerSingleton } from '../../../platform/instantiation/common/extensions.js';12import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';13import { ILogService } from '../../../platform/log/common/log.js';14import { matchesScheme } from '../../../base/common/network.js';151617export const ILanguageFeatureDebounceService = createDecorator<ILanguageFeatureDebounceService>('ILanguageFeatureDebounceService');1819export interface ILanguageFeatureDebounceService {2021readonly _serviceBrand: undefined;2223for(feature: LanguageFeatureRegistry<object>, debugName: string, config?: { min?: number; max?: number; salt?: string }): IFeatureDebounceInformation;24}2526export interface IFeatureDebounceInformation {27get(model: ITextModel): number;28update(model: ITextModel, value: number): number;29default(): number;30}3132namespace IdentityHash {33const _hashes = new WeakMap<object, number>();34let pool = 0;35export function of(obj: object): number {36let value = _hashes.get(obj);37if (value === undefined) {38value = ++pool;39_hashes.set(obj, value);40}41return value;42}43}4445class NullDebounceInformation implements IFeatureDebounceInformation {4647constructor(private readonly _default: number) { }4849get(_model: ITextModel): number {50return this._default;51}52update(_model: ITextModel, _value: number): number {53return this._default;54}55default(): number {56return this._default;57}58}5960class FeatureDebounceInformation implements IFeatureDebounceInformation {6162private readonly _cache = new LRUCache<string, SlidingWindowAverage>(50, 0.7);6364constructor(65private readonly _logService: ILogService,66private readonly _name: string,67private readonly _registry: LanguageFeatureRegistry<object>,68private readonly _default: number,69private readonly _min: number,70private readonly _max: number,71) { }7273private _key(model: ITextModel): string {74return model.id + this._registry.all(model).reduce((hashVal, obj) => doHash(IdentityHash.of(obj), hashVal), 0);75}7677get(model: ITextModel): number {78const key = this._key(model);79const avg = this._cache.get(key);80return avg81? clamp(avg.value, this._min, this._max)82: this.default();83}8485update(model: ITextModel, value: number): number {86const key = this._key(model);87let avg = this._cache.get(key);88if (!avg) {89avg = new SlidingWindowAverage(6);90this._cache.set(key, avg);91}92const newValue = clamp(avg.update(value), this._min, this._max);93if (!matchesScheme(model.uri, 'output')) {94this._logService.trace(`[DEBOUNCE: ${this._name}] for ${model.uri.toString()} is ${newValue}ms`);95}96return newValue;97}9899private _overall(): number {100const result = new MovingAverage();101for (const [, avg] of this._cache) {102result.update(avg.value);103}104return result.value;105}106107default() {108const value = (this._overall() | 0) || this._default;109return clamp(value, this._min, this._max);110}111}112113114export class LanguageFeatureDebounceService implements ILanguageFeatureDebounceService {115116declare _serviceBrand: undefined;117118private readonly _data = new Map<string, IFeatureDebounceInformation>();119private readonly _isDev: boolean;120121constructor(122@ILogService private readonly _logService: ILogService,123@IEnvironmentService envService: IEnvironmentService,124) {125126this._isDev = envService.isExtensionDevelopment || !envService.isBuilt;127}128129for(feature: LanguageFeatureRegistry<object>, name: string, config?: { min?: number; max?: number; key?: string }): IFeatureDebounceInformation {130const min = config?.min ?? 50;131const max = config?.max ?? min ** 2;132const extra = config?.key ?? undefined;133const key = `${IdentityHash.of(feature)},${min}${extra ? ',' + extra : ''}`;134let info = this._data.get(key);135if (!info) {136if (this._isDev) {137this._logService.debug(`[DEBOUNCE: ${name}] is disabled in developed mode`);138info = new NullDebounceInformation(min * 1.5);139} else {140info = new FeatureDebounceInformation(141this._logService,142name,143feature,144(this._overallAverage() | 0) || (min * 1.5), // default is overall default or derived from min-value145min,146max147);148}149this._data.set(key, info);150}151return info;152}153154private _overallAverage(): number {155// Average of all language features. Not a great value but an approximation156const result = new MovingAverage();157for (const info of this._data.values()) {158result.update(info.default());159}160return result.value;161}162}163164registerSingleton(ILanguageFeatureDebounceService, LanguageFeatureDebounceService, InstantiationType.Delayed);165166167