Adds debug methods to observables (#242600)

This commit is contained in:
Henning Dieterichs
2025-03-04 20:47:56 +01:00
committed by GitHub
parent 04a1f0815a
commit 73072ae20b
6 changed files with 264 additions and 151 deletions

View File

@@ -168,13 +168,13 @@ export const enum AutorunState {
}
export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader, IDisposable {
public _state = AutorunState.stale;
private updateCount = 0;
private disposed = false;
public _dependencies = new Set<IObservable<any>>();
private dependenciesToBeRemoved = new Set<IObservable<any>>();
private changeSummary: TChangeSummary | undefined;
public _isRunning = false;
private _state = AutorunState.stale;
private _updateCount = 0;
private _disposed = false;
private _dependencies = new Set<IObservable<any>>();
private _dependenciesToBeRemoved = new Set<IObservable<any>>();
private _changeSummary: TChangeSummary | undefined;
private _isRunning = false;
public get debugName(): string {
return this._debugNameData.getDebugName(this) ?? '(anonymous)';
@@ -186,7 +186,7 @@ export class AutorunObserver<TChangeSummary = any> 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<TChangeSummary = any> 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<TChangeSummary = any> 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<TChangeSummary = any> 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<TChangeSummary = any> implements IObserver, IReader
if (this._state === AutorunState.upToDate) {
this._state = AutorunState.dependenciesMightHaveChanged;
}
this.updateCount++;
this._updateCount++;
}
public endUpdate(_observable: IObservable<any>): 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<TChangeSummary = any> 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<any>): void {
@@ -292,7 +292,7 @@ export class AutorunObserver<TChangeSummary = any> 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<TChangeSummary = any> implements IObserver, IReader
}
private _isDependency(observable: IObservableWithChange<any, any>): 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<TChangeSummary = any> 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 {

View File

@@ -290,7 +290,7 @@ export abstract class ConvenientObservable<T, TChange> implements IObservableWit
}
export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
public readonly _observers = new Set<IObserver>();
protected readonly _observers = new Set<IObserver>();
constructor() {
super();
@@ -329,6 +329,10 @@ export abstract class BaseObservable<T, TChange = void> 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<any> }[] | null = [];
private _updatingObservers: { observer: IObserver; observable: IObservable<any> }[] | 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<T, TChange = void>
protected _setValue(newValue: T): void {
this._value = newValue;
}
public debugGetState() {
return {
value: this._value,
};
}
public debugSetValue(value: unknown) {
this._value = value as T;
}
}
/**

View File

@@ -194,14 +194,14 @@ export const enum DerivedState {
}
export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> implements IReader, IObserver {
public _state = DerivedState.initial;
private value: T | undefined = undefined;
public _updateCount = 0;
public _dependencies = new Set<IObservable<any>>();
private dependenciesToBeRemoved = new Set<IObservable<any>>();
private changeSummary: TChangeSummary | undefined = undefined;
private _state = DerivedState.initial;
private _value: T | undefined = undefined;
private _updateCount = 0;
private _dependencies = new Set<IObservable<any>>();
private _dependenciesToBeRemoved = new Set<IObservable<any>>();
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<T, TChangeSummary = any> extends BaseObservable<T, void> im
private readonly _equalityComparator: EqualityComparer<T>,
) {
super();
this.changeSummary = this.createChangeSummary?.();
this._changeSummary = this.createChangeSummary?.();
}
protected override onLastObserverRemoved(): void {
@@ -225,7 +225,7 @@ export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> 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<T, TChangeSummary = any> extends BaseObservable<T, void> 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<T, TChangeSummary = any> extends BaseObservable<T, void> 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<T, TChangeSummary = any> extends BaseObservable<T, void> im
public handlePossibleChange<T>(observable: IObservable<T>): 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<T, TChangeSummary = any> extends BaseObservable<T, void> im
}
public handleChange<T, TChange>(observable: IObservableWithChange<T, TChange>, 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<T, TChangeSummary = any> extends BaseObservable<T, void> 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<T, TChangeSummary = any> extends BaseObservable<T, void> 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<T, TChangeSummary = any> extends BaseObservable<T, void> 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;
}
}

View File

@@ -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<IObsDeclarations> & DeepPartial<IObsPushSta
type DeepPartial<T> = { [TKey in keyof T]?: DeepPartial<T[TKey]> };
export interface IObservableValueInfo {
observers: IObsInstanceRef[];
}
export interface IDerivedObservableDetailedInfo {
dependencies: IObsInstanceRef[];
observers: IObsInstanceRef[];

View File

@@ -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<any>;
return {
observers: [...obs.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
};
},
getDerivedInfo: instanceId => {
const d = this._aliveInstances.get(instanceId) as Derived<any>;
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<any>;
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<any>;
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<IObserver>();
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<any>): IObservableInfo {
private _getObservableInfo(observable: IObservable<any>): 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<any>): { name: string; instanceId: ObsInstanceId } {
return { name: obs.debugName, instanceId: this._getObservableInfo(obs)?.instanceId! };
private _formatObservable(obs: IObservable<any>): { 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,7 +336,8 @@ export class DevToolsLogger implements IObservableLogger {
handleOnListenerCountChanged(observable: IObservable<any>, newCount: number): void {
const info = this._getObservableInfo(observable);
if (info) {
if (!info) { return; }
if (info.listenerCount === 0 && newCount > 0) {
const type: IObsDeclaration['type'] =
observable instanceof Derived ? 'observable/derived' : 'observable/value';
@@ -308,7 +361,6 @@ export class DevToolsLogger implements IObservableLogger {
}
info.listenerCount = newCount;
}
}
handleObservableUpdated(observable: IObservable<any>, changeInfo: IChangeInformation): void {
if (observable instanceof Derived) {
@@ -355,33 +407,33 @@ export class DevToolsLogger implements IObservableLogger {
}
handleAutorunDisposed(autorun: AutorunObserver): void {
const info = this._getAutorunInfo(autorun);
if (info) {
if (!info) { return; }
this._handleChange({
instances: { [info.instanceId]: null }
});
this._instanceInfos.delete(autorun);
this._aliveInstances.delete(info.instanceId);
}
}
handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable<any>, change: unknown): void {
const info = this._getAutorunInfo(autorun);
if (info) {
if (!info) { return; }
info.changedObservables.add(observable);
}
}
handleAutorunStarted(autorun: AutorunObserver): void {
}
handleAutorunFinished(autorun: AutorunObserver): void {
const info = this._getAutorunInfo(autorun);
if (info) {
if (!info) { return; }
info.changedObservables.clear();
info.updateCount++;
this._handleChange({
instances: { [info.instanceId]: { runCount: info.updateCount } }
});
}
}
handleDerivedDependencyChanged(derived: Derived<any>, observable: IObservable<any>, change: unknown): void {
const info = this._getObservableInfo(derived);
@@ -391,7 +443,8 @@ export class DevToolsLogger implements IObservableLogger {
}
_handleDerivedRecomputed(observable: Derived<any>, changeInfo: IChangeInformation): void {
const info = this._getObservableInfo(observable);
if (info) {
if (!info) { return; }
const formattedValue = formatValue(changeInfo.newValue, 30);
info.updateCount++;
info.changedObservables.clear();
@@ -403,10 +456,10 @@ export class DevToolsLogger implements IObservableLogger {
});
}
}
}
handleDerivedCleared(observable: Derived<any>): void {
const info = this._getObservableInfo(observable);
if (info) {
if (!info) { return; }
info.lastValue = undefined;
info.changedObservables.clear();
if (info.listenerCount > 0) {
@@ -419,7 +472,6 @@ export class DevToolsLogger implements IObservableLogger {
});
}
}
}
handleBeginTransaction(transaction: TransactionImpl): void {
this._activeTransactions.add(transaction);
}

View File

@@ -102,9 +102,9 @@ export function observableFromEventOpts<T, TArgs = unknown>(
export class FromEventObservable<TArgs, T> extends BaseObservable<T> {
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<TArgs, T> extends BaseObservable<T> {
}
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<TArgs, T> extends BaseObservable<T> {
}
);
}
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 {