From 73072ae20b61ef4a8cfb1db074503e4bdb807987 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 4 Mar 2025 20:47:56 +0100 Subject: [PATCH] Adds debug methods to observables (#242600) --- .../base/common/observableInternal/autorun.ts | 67 ++++-- src/vs/base/common/observableInternal/base.ts | 22 +- .../base/common/observableInternal/derived.ts | 63 +++-- .../logging/debugger/debuggerApi.d.ts | 7 + .../logging/debugger/devToolsLogger.ts | 216 +++++++++++------- .../base/common/observableInternal/utils.ts | 40 ++-- 6 files changed, 264 insertions(+), 151 deletions(-) diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index b1f8f4e9530..03c37aa6c19 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -168,13 +168,13 @@ export const enum AutorunState { } export class AutorunObserver implements IObserver, IReader, IDisposable { - public _state = AutorunState.stale; - private updateCount = 0; - private disposed = false; - public _dependencies = new Set>(); - private dependenciesToBeRemoved = new Set>(); - private changeSummary: TChangeSummary | undefined; - public _isRunning = false; + private _state = AutorunState.stale; + private _updateCount = 0; + private _disposed = false; + private _dependencies = new Set>(); + private _dependenciesToBeRemoved = new Set>(); + private _changeSummary: TChangeSummary | undefined; + private _isRunning = false; public get debugName(): string { return this._debugNameData.getDebugName(this) ?? '(anonymous)'; @@ -186,7 +186,7 @@ export class AutorunObserver implements IObserver, IReader private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, ) { - this.changeSummary = this.createChangeSummary?.(); + this._changeSummary = this.createChangeSummary?.(); getLogger()?.handleAutorunCreated(this); this._run(); @@ -194,7 +194,7 @@ export class AutorunObserver implements IObserver, IReader } public dispose(): void { - this.disposed = true; + this._disposed = true; for (const o of this._dependencies) { o.removeObserver(this); // Warning: external call! } @@ -205,18 +205,18 @@ export class AutorunObserver implements IObserver, IReader } private _run() { - const emptySet = this.dependenciesToBeRemoved; - this.dependenciesToBeRemoved = this._dependencies; + const emptySet = this._dependenciesToBeRemoved; + this._dependenciesToBeRemoved = this._dependencies; this._dependencies = emptySet; this._state = AutorunState.upToDate; try { - if (!this.disposed) { + if (!this._disposed) { getLogger()?.handleAutorunStarted(this); - const changeSummary = this.changeSummary!; + const changeSummary = this._changeSummary!; try { - this.changeSummary = this.createChangeSummary?.(); // Warning: external call! + this._changeSummary = this.createChangeSummary?.(); // Warning: external call! this._isRunning = true; this._runFn(this, changeSummary); // Warning: external call! } catch (e) { @@ -226,15 +226,15 @@ export class AutorunObserver implements IObserver, IReader } } } finally { - if (!this.disposed) { + if (!this._disposed) { getLogger()?.handleAutorunFinished(this); } // We don't want our observed observables to think that they are (not even temporarily) not being observed. // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.dependenciesToBeRemoved) { + for (const o of this._dependenciesToBeRemoved) { o.removeObserver(this); // Warning: external call! } - this.dependenciesToBeRemoved.clear(); + this._dependenciesToBeRemoved.clear(); } } @@ -247,12 +247,12 @@ export class AutorunObserver implements IObserver, IReader if (this._state === AutorunState.upToDate) { this._state = AutorunState.dependenciesMightHaveChanged; } - this.updateCount++; + this._updateCount++; } public endUpdate(_observable: IObservable): void { try { - if (this.updateCount === 1) { + if (this._updateCount === 1) { do { if (this._state === AutorunState.dependenciesMightHaveChanged) { this._state = AutorunState.upToDate; @@ -271,10 +271,10 @@ export class AutorunObserver implements IObserver, IReader } while (this._state !== AutorunState.upToDate); } } finally { - this.updateCount--; + this._updateCount--; } - assertFn(() => this.updateCount >= 0); + assertFn(() => this._updateCount >= 0); } public handlePossibleChange(observable: IObservable): void { @@ -292,7 +292,7 @@ export class AutorunObserver implements IObserver, IReader changedObservable: observable, change, didChange: (o): this is any => o === observable as any, - }, this.changeSummary!) : true; + }, this._changeSummary!) : true; if (shouldReact) { this._state = AutorunState.stale; } @@ -303,7 +303,7 @@ export class AutorunObserver implements IObserver, IReader } private _isDependency(observable: IObservableWithChange): boolean { - return this._dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable); + return this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable); } // IReader implementation @@ -312,16 +312,33 @@ export class AutorunObserver implements IObserver, IReader if (!this._isRunning) { throw new BugIndicatingError('The reader object cannot be used outside its compute function!'); } // In case the run action disposes the autorun - if (this.disposed) { + if (this._disposed) { return observable.get(); // warning: external call! } observable.addObserver(this); // warning: external call! const value = observable.get(); // warning: external call! this._dependencies.add(observable); - this.dependenciesToBeRemoved.delete(observable); + this._dependenciesToBeRemoved.delete(observable); return value; } + + public debugGetState() { + return { + isRunning: this._isRunning, + updateCount: this._updateCount, + dependencies: this._dependencies, + state: this._state, + }; + } + + public debugRerun(): void { + if (!this._isRunning) { + this._run(); + } else { + this._state = AutorunState.stale; + } + } } export namespace autorun { diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 31ce4988a13..4d7d4ea9e19 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -290,7 +290,7 @@ export abstract class ConvenientObservable implements IObservableWit } export abstract class BaseObservable extends ConvenientObservable { - public readonly _observers = new Set(); + protected readonly _observers = new Set(); constructor() { super(); @@ -329,6 +329,10 @@ export abstract class BaseObservable extends ConvenientObserv } return this; } + + public debugGetObservers() { + return this._observers; + } } /** @@ -385,7 +389,7 @@ export function subtransaction(tx: ITransaction | undefined, fn: (tx: ITransacti } export class TransactionImpl implements ITransaction { - public _updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; + private _updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; constructor(public readonly _fn: Function, private readonly _getDebugName?: () => string) { getLogger()?.handleBeginTransaction(this); @@ -414,6 +418,10 @@ export class TransactionImpl implements ITransaction { this._updatingObservers = null; getLogger()?.handleEndTransaction(this); } + + public debugGetUpdatingObservers() { + return this._updatingObservers; + } } /** @@ -495,6 +503,16 @@ export class ObservableValue protected _setValue(newValue: T): void { this._value = newValue; } + + public debugGetState() { + return { + value: this._value, + }; + } + + public debugSetValue(value: unknown) { + this._value = value as T; + } } /** diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 0735be62c23..59d8dc725a7 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -194,14 +194,14 @@ export const enum DerivedState { } export class Derived extends BaseObservable implements IReader, IObserver { - public _state = DerivedState.initial; - private value: T | undefined = undefined; - public _updateCount = 0; - public _dependencies = new Set>(); - private dependenciesToBeRemoved = new Set>(); - private changeSummary: TChangeSummary | undefined = undefined; + private _state = DerivedState.initial; + private _value: T | undefined = undefined; + private _updateCount = 0; + private _dependencies = new Set>(); + private _dependenciesToBeRemoved = new Set>(); + private _changeSummary: TChangeSummary | undefined = undefined; private _isUpdating = false; - public _isComputing = false; + private _isComputing = false; public override get debugName(): string { return this._debugNameData.getDebugName(this) ?? '(anonymous)'; @@ -216,7 +216,7 @@ export class Derived extends BaseObservable im private readonly _equalityComparator: EqualityComparer, ) { super(); - this.changeSummary = this.createChangeSummary?.(); + this._changeSummary = this.createChangeSummary?.(); } protected override onLastObserverRemoved(): void { @@ -225,7 +225,7 @@ export class Derived extends BaseObservable im * that our cache is invalid. */ this._state = DerivedState.initial; - this.value = undefined; + this._value = undefined; getLogger()?.handleDerivedCleared(this); for (const d of this._dependencies) { d.removeObserver(this); @@ -283,17 +283,17 @@ export class Derived extends BaseObservable im } // In case recomputation changed one of our dependencies, we need to recompute again. } while (this._state !== DerivedState.upToDate); - return this.value!; + return this._value!; } } private _recompute() { - const emptySet = this.dependenciesToBeRemoved; - this.dependenciesToBeRemoved = this._dependencies; + const emptySet = this._dependenciesToBeRemoved; + this._dependenciesToBeRemoved = this._dependencies; this._dependencies = emptySet; const hadValue = this._state !== DerivedState.initial; - const oldValue = this.value; + const oldValue = this._value; this._state = DerivedState.upToDate; let didChange = false; @@ -301,27 +301,27 @@ export class Derived extends BaseObservable im this._isComputing = true; try { - const changeSummary = this.changeSummary!; - this.changeSummary = this.createChangeSummary?.(); + const changeSummary = this._changeSummary!; + this._changeSummary = this.createChangeSummary?.(); try { this._isReaderValid = true; /** might call {@link handleChange} indirectly, which could invalidate us */ - this.value = this._computeFn(this, changeSummary); + this._value = this._computeFn(this, changeSummary); } finally { this._isReaderValid = false; // We don't want our observed observables to think that they are (not even temporarily) not being observed. // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.dependenciesToBeRemoved) { + for (const o of this._dependenciesToBeRemoved) { o.removeObserver(this); } - this.dependenciesToBeRemoved.clear(); + this._dependenciesToBeRemoved.clear(); } - didChange = hadValue && !(this._equalityComparator(oldValue!, this.value)); + didChange = hadValue && !(this._equalityComparator(oldValue!, this._value)); getLogger()?.handleObservableUpdated(this, { oldValue, - newValue: this.value, + newValue: this._value, change: undefined, didChange, hadValue, @@ -396,7 +396,7 @@ export class Derived extends BaseObservable im public handlePossibleChange(observable: IObservable): void { // In all other states, observers already know that we might have changed. - if (this._state === DerivedState.upToDate && this._dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + if (this._state === DerivedState.upToDate && this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable)) { this._state = DerivedState.dependenciesMightHaveChanged; for (const r of this._observers) { r.handlePossibleChange(this); @@ -405,7 +405,7 @@ export class Derived extends BaseObservable im } public handleChange(observable: IObservableWithChange, change: TChange): void { - if (this._dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + if (this._dependencies.has(observable) && !this._dependenciesToBeRemoved.has(observable)) { getLogger()?.handleDerivedDependencyChanged(this, observable, change); let shouldReact = false; @@ -414,7 +414,7 @@ export class Derived extends BaseObservable im changedObservable: observable, change, didChange: (o): this is any => o === observable as any, - }, this.changeSummary!) : true; + }, this._changeSummary!) : true; } catch (e) { onBugIndicatingError(e); } @@ -443,7 +443,7 @@ export class Derived extends BaseObservable im const value = observable.get(); // Which is why we only add the observable to the dependencies now. this._dependencies.add(observable); - this.dependenciesToBeRemoved.delete(observable); + this._dependenciesToBeRemoved.delete(observable); return value; } @@ -469,6 +469,21 @@ export class Derived extends BaseObservable im } super.removeObserver(observer); } + + public debugGetState() { + return { + state: this._state, + updateCount: this._updateCount, + isComputing: this._isComputing, + dependencies: this._dependencies, + value: this._value, + }; + } + + public debugSetValue(newValue: unknown) { + this._value = newValue as any; + } + } diff --git a/src/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts b/src/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts index e0b5fae1820..138732f44b2 100644 --- a/src/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts +++ b/src/vs/base/common/observableInternal/logging/debugger/debuggerApi.d.ts @@ -23,6 +23,9 @@ export type ObsDebuggerApi = { getSummarizedInstances(): IObsPushState; getDerivedInfo(instanceId: ObsInstanceId): IDerivedObservableDetailedInfo; getAutorunInfo(instanceId: ObsInstanceId): IAutorunDetailedInfo; + getObservableValueInfo(instanceId: ObsInstanceId): IObservableValueInfo; + setValue(instanceId: ObsInstanceId, jsonValue: unknown): void; + getValue(instanceId: ObsInstanceId): unknown; getTransactionState(): ITransactionState | undefined; } @@ -94,6 +97,10 @@ export type ObsStateUpdate = Partial & DeepPartial = { [TKey in keyof T]?: DeepPartial }; +export interface IObservableValueInfo { + observers: IObsInstanceRef[]; +} + export interface IDerivedObservableDetailedInfo { dependencies: IObsInstanceRef[]; observers: IObsInstanceRef[]; diff --git a/src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts b/src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts index a85329ca8c9..a09e2f2683a 100644 --- a/src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts +++ b/src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AutorunObserver, AutorunState } from '../../autorun.js'; -import { IObservable, IObserver, TransactionImpl } from '../../base.js'; +import { BaseObservable, IObservable, IObserver, ObservableValue, TransactionImpl } from '../../base.js'; import { Derived, DerivedState } from '../../derived.js'; import { IChangeInformation, IObservableLogger } from '../logging.js'; import { formatValue } from '../consoleObservableLogger.js'; @@ -12,6 +12,8 @@ import { ObsDebuggerApi, IObsDeclaration, ObsInstanceId, ObsStateUpdate, ITransa import { registerDebugChannel } from './debuggerRpc.js'; import { deepAssign, deepAssignDeleteNulls, getFirstStackFrameOutsideOf, ILocation, Throttler } from './utils.js'; import { isDefined } from '../../../types.js'; +import { FromEventObservable } from '../../utils.js'; +import { BugIndicatingError, onUnexpectedError } from '../../../errors.js'; interface IInstanceInfo { declarationId: number; @@ -75,21 +77,61 @@ export class DevToolsLogger implements IObservableLogger { getSummarizedInstances: () => { return null!; }, + getObservableValueInfo: instanceId => { + const obs = this._aliveInstances.get(instanceId) as BaseObservable; + return { + observers: [...obs.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined), + }; + }, getDerivedInfo: instanceId => { const d = this._aliveInstances.get(instanceId) as Derived; return { - dependencies: [...d._dependencies].map(d => this._formatObservable(d)), - observers: [...d._observers].map(d => this._formatObserver(d)).filter(isDefined), + dependencies: [...d.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined), + observers: [...d.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined), }; }, getAutorunInfo: instanceId => { const obs = this._aliveInstances.get(instanceId) as AutorunObserver; return { - dependencies: [...obs._dependencies].map(d => this._formatObservable(d)), + dependencies: [...obs.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined), }; }, getTransactionState: () => { return this.getTransactionState(); + }, + setValue: (instanceId, jsonValue) => { + const obs = this._aliveInstances.get(instanceId) as BaseObservable; + + if (obs instanceof Derived) { + obs.debugSetValue(jsonValue); + } else if (obs instanceof ObservableValue) { + obs.debugSetValue(jsonValue); + } else if (obs instanceof FromEventObservable) { + obs.debugSetValue(jsonValue); + } else { + throw new BugIndicatingError('Observable is not supported'); + } + + const observers = [...obs.debugGetObservers()]; + for (const d of observers) { + d.beginUpdate(obs); + } + for (const d of observers) { + d.handleChange(obs, undefined); + } + for (const d of observers) { + d.endUpdate(obs); + } + }, + getValue: instanceId => { + const obs = this._aliveInstances.get(instanceId) as BaseObservable; + if (obs instanceof Derived) { + return formatValue(obs.debugGetState().value, 200); + } else if (obs instanceof ObservableValue) { + return formatValue(obs.debugGetState().value, 200); + } + + return undefined; } } }; @@ -101,7 +143,7 @@ export class DevToolsLogger implements IObservableLogger { if (txs.length === 0) { return undefined; } - const observerQueue = txs.flatMap(t => t._updatingObservers ?? []).map(o => o.observer); + const observerQueue = txs.flatMap(t => t.debugGetUpdatingObservers() ?? []).map(o => o.observer); const processedObservers = new Set(); while (observerQueue.length > 0) { const observer = observerQueue.shift()!; @@ -124,36 +166,42 @@ export class DevToolsLogger implements IObservableLogger { return { names: txs.map(t => t.getDebugName() ?? 'tx'), affected }; } - private _getObservableInfo(observable: IObservable): IObservableInfo { + private _getObservableInfo(observable: IObservable): IObservableInfo | undefined { const info = this._instanceInfos.get(observable); if (!info) { - throw new Error('No info found'); + onUnexpectedError(new BugIndicatingError('No info found')); + return undefined; } return info as IObservableInfo; } - private _getAutorunInfo(autorun: AutorunObserver): IAutorunInfo { + private _getAutorunInfo(autorun: AutorunObserver): IAutorunInfo | undefined { const info = this._instanceInfos.get(autorun); if (!info) { - throw new Error('No info found'); + onUnexpectedError(new BugIndicatingError('No info found')); + return undefined; } return info as IAutorunInfo; } private _getInfo(observer: IObserver, queue: (observer: IObserver) => void): ObserverInstanceState | undefined { if (observer instanceof Derived) { - const observersToUpdate = [...observer._observers]; + const observersToUpdate = [...observer.debugGetObservers()]; for (const o of observersToUpdate) { queue(o); } - const info = this._getObservableInfo(observer)!; - const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: observer._updateCount }; - const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)!.instanceId); - if (observer._isComputing) { + const info = this._getObservableInfo(observer); + if (!info) { return; } + + const observerState = observer.debugGetState(); + + const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: observerState.updateCount }; + const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)?.instanceId).filter(isDefined); + if (observerState.isComputing) { return { ...base, type: 'observable/derived', state: 'updating', changedDependencies, initialComputation: false }; } - switch (observer._state) { + switch (observerState.state) { case DerivedState.initial: return { ...base, type: 'observable/derived', state: 'noValue' }; case DerivedState.upToDate: @@ -164,13 +212,15 @@ export class DevToolsLogger implements IObservableLogger { return { ...base, type: 'observable/derived', state: 'possiblyStale' }; } } else if (observer instanceof AutorunObserver) { - const info = this._getAutorunInfo(observer)!; + const info = this._getAutorunInfo(observer); + if (!info) { return undefined; } + const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: info.updateCount }; const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)!.instanceId); - if (observer._isRunning) { + if (observer.debugGetState().isRunning) { return { ...base, type: 'autorun', state: 'updating', changedDependencies }; } - switch (observer._state) { + switch (observer.debugGetState().state) { case AutorunState.upToDate: return { ...base, type: 'autorun', state: 'upToDate' }; case AutorunState.stale: @@ -183,8 +233,10 @@ export class DevToolsLogger implements IObservableLogger { return undefined; } - private _formatObservable(obs: IObservable): { name: string; instanceId: ObsInstanceId } { - return { name: obs.debugName, instanceId: this._getObservableInfo(obs)?.instanceId! }; + private _formatObservable(obs: IObservable): { name: string; instanceId: ObsInstanceId } | undefined { + const info = this._getObservableInfo(obs); + if (!info) { return undefined; } + return { name: obs.debugName, instanceId: info.instanceId }; } private _formatObserver(obs: IObserver): { name: string; instanceId: ObsInstanceId } | undefined { @@ -236,10 +288,10 @@ export class DevToolsLogger implements IObservableLogger { const stack = new Error().stack!; Error.stackTraceLimit = l; - let result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]|[/\\]util(s)?\./); + let result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]|\.observe|[/\\]util(s)?\./); if (!shallow && !result) { - result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]/)!; + result = getFirstStackFrameOutsideOf(stack, /[/\\]observableInternal[/\\]|\.observe/)!; } if (result) { loc = result; @@ -284,30 +336,30 @@ export class DevToolsLogger implements IObservableLogger { handleOnListenerCountChanged(observable: IObservable, newCount: number): void { const info = this._getObservableInfo(observable); - if (info) { - if (info.listenerCount === 0 && newCount > 0) { - const type: IObsDeclaration['type'] = - observable instanceof Derived ? 'observable/derived' : 'observable/value'; - this._aliveInstances.set(info.instanceId, observable); - this._handleChange({ - instances: { - [info.instanceId]: { - instanceId: info.instanceId, - declarationId: info.declarationId, - formattedValue: info.lastValue, - type, - name: observable.debugName, - } + if (!info) { return; } + + if (info.listenerCount === 0 && newCount > 0) { + const type: IObsDeclaration['type'] = + observable instanceof Derived ? 'observable/derived' : 'observable/value'; + this._aliveInstances.set(info.instanceId, observable); + this._handleChange({ + instances: { + [info.instanceId]: { + instanceId: info.instanceId, + declarationId: info.declarationId, + formattedValue: info.lastValue, + type, + name: observable.debugName, } - }); - } else if (info.listenerCount > 0 && newCount === 0) { - this._handleChange({ - instances: { [info.instanceId]: null } - }); - this._aliveInstances.delete(info.instanceId); - } - info.listenerCount = newCount; + } + }); + } else if (info.listenerCount > 0 && newCount === 0) { + this._handleChange({ + instances: { [info.instanceId]: null } + }); + this._aliveInstances.delete(info.instanceId); } + info.listenerCount = newCount; } handleObservableUpdated(observable: IObservable, changeInfo: IChangeInformation): void { @@ -355,32 +407,32 @@ export class DevToolsLogger implements IObservableLogger { } handleAutorunDisposed(autorun: AutorunObserver): void { const info = this._getAutorunInfo(autorun); - if (info) { - this._handleChange({ - instances: { [info.instanceId]: null } - }); - this._instanceInfos.delete(autorun); - this._aliveInstances.delete(info.instanceId); - } + if (!info) { return; } + + this._handleChange({ + instances: { [info.instanceId]: null } + }); + this._instanceInfos.delete(autorun); + this._aliveInstances.delete(info.instanceId); } handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable, change: unknown): void { const info = this._getAutorunInfo(autorun); - if (info) { - info.changedObservables.add(observable); - } + if (!info) { return; } + + info.changedObservables.add(observable); } handleAutorunStarted(autorun: AutorunObserver): void { } handleAutorunFinished(autorun: AutorunObserver): void { const info = this._getAutorunInfo(autorun); - if (info) { - info.changedObservables.clear(); - info.updateCount++; - this._handleChange({ - instances: { [info.instanceId]: { runCount: info.updateCount } } - }); - } + if (!info) { return; } + + info.changedObservables.clear(); + info.updateCount++; + this._handleChange({ + instances: { [info.instanceId]: { runCount: info.updateCount } } + }); } handleDerivedDependencyChanged(derived: Derived, observable: IObservable, change: unknown): void { @@ -391,33 +443,33 @@ export class DevToolsLogger implements IObservableLogger { } _handleDerivedRecomputed(observable: Derived, changeInfo: IChangeInformation): void { const info = this._getObservableInfo(observable); - if (info) { - const formattedValue = formatValue(changeInfo.newValue, 30); - info.updateCount++; - info.changedObservables.clear(); + if (!info) { return; } - info.lastValue = formattedValue; - if (info.listenerCount > 0) { - this._handleChange({ - instances: { [info.instanceId]: { formattedValue: formattedValue, recomputationCount: info.updateCount } } - }); - } + const formattedValue = formatValue(changeInfo.newValue, 30); + info.updateCount++; + info.changedObservables.clear(); + + info.lastValue = formattedValue; + if (info.listenerCount > 0) { + this._handleChange({ + instances: { [info.instanceId]: { formattedValue: formattedValue, recomputationCount: info.updateCount } } + }); } } handleDerivedCleared(observable: Derived): void { const info = this._getObservableInfo(observable); - if (info) { - info.lastValue = undefined; - info.changedObservables.clear(); - if (info.listenerCount > 0) { - this._handleChange({ - instances: { - [info.instanceId]: { - formattedValue: undefined, - } + if (!info) { return; } + + info.lastValue = undefined; + info.changedObservables.clear(); + if (info.listenerCount > 0) { + this._handleChange({ + instances: { + [info.instanceId]: { + formattedValue: undefined, } - }); - } + } + }); } } handleBeginTransaction(transaction: TransactionImpl): void { diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 3b5bf7d35bf..530474b334e 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -102,9 +102,9 @@ export function observableFromEventOpts( export class FromEventObservable extends BaseObservable { public static globalTransaction: ITransaction | undefined; - private value: T | undefined; - private hasValue = false; - private subscription: IDisposable | undefined; + private _value: T | undefined; + private _hasValue = false; + private _subscription: IDisposable | undefined; constructor( private readonly _debugNameData: DebugNameData, @@ -126,25 +126,25 @@ export class FromEventObservable extends BaseObservable { } protected override onFirstObserverAdded(): void { - this.subscription = this.event(this.handleEvent); + this._subscription = this.event(this.handleEvent); } private readonly handleEvent = (args: TArgs | undefined) => { const newValue = this._getValue(args); - const oldValue = this.value; + const oldValue = this._value; - const didChange = !this.hasValue || !(this._equalityComparator(oldValue!, newValue)); + const didChange = !this._hasValue || !(this._equalityComparator(oldValue!, newValue)); let didRunTransaction = false; if (didChange) { - this.value = newValue; + this._value = newValue; - if (this.hasValue) { + if (this._hasValue) { didRunTransaction = true; subtransaction( this._getTransaction(), (tx) => { - getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this._hasValue }); for (const o of this._observers) { tx.updateObserver(o, this); @@ -157,33 +157,37 @@ export class FromEventObservable extends BaseObservable { } ); } - this.hasValue = true; + this._hasValue = true; } if (!didRunTransaction) { - getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this._hasValue }); } }; protected override onLastObserverRemoved(): void { - this.subscription!.dispose(); - this.subscription = undefined; - this.hasValue = false; - this.value = undefined; + this._subscription!.dispose(); + this._subscription = undefined; + this._hasValue = false; + this._value = undefined; } public get(): T { - if (this.subscription) { - if (!this.hasValue) { + if (this._subscription) { + if (!this._hasValue) { this.handleEvent(undefined); } - return this.value!; + return this._value!; } else { // no cache, as there are no subscribers to keep it updated const value = this._getValue(undefined); return value; } } + + public debugSetValue(value: unknown) { + this._value = value as any; + } } export namespace observableFromEvent {