Skip to content

Commit 27c883b

Browse files
committed
Improves observable docs
1 parent 5605a4e commit 27c883b

File tree

3 files changed

+152
-131
lines changed

3 files changed

+152
-131
lines changed

src/vs/base/common/observableImpl/autorun.ts

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -67,42 +67,56 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
6767
private state = AutorunState.stale;
6868
private updateCount = 0;
6969
private disposed = false;
70-
71-
/**
72-
* The actual dependencies.
73-
*/
74-
private _dependencies = new Set<IObservable<any>>();
75-
public get dependencies() {
76-
return this._dependencies;
77-
}
78-
79-
/**
80-
* Dependencies that have to be removed when {@link runFn} ran through.
81-
*/
82-
private staleDependencies = new Set<IObservable<any>>();
70+
private dependencies = new Set<IObservable<any>>();
71+
private dependenciesToBeRemoved = new Set<IObservable<any>>();
8372

8473
constructor(
8574
public readonly debugName: string,
8675
private readonly runFn: (reader: IReader) => void,
8776
private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
8877
) {
8978
getLogger()?.handleAutorunCreated(this);
90-
this.runIfNeeded();
79+
this._runIfNeeded();
9180
}
9281

93-
public readObservable<T>(observable: IObservable<T>): T {
94-
// In case the run action disposes the autorun
95-
if (this.disposed) {
96-
return observable.get();
82+
public dispose(): void {
83+
this.disposed = true;
84+
for (const o of this.dependencies) {
85+
o.removeObserver(this);
9786
}
87+
this.dependencies.clear();
88+
}
9889

99-
observable.addObserver(this);
100-
const value = observable.get();
101-
this._dependencies.add(observable);
102-
this.staleDependencies.delete(observable);
103-
return value;
90+
private _runIfNeeded() {
91+
if (this.state === AutorunState.upToDate) {
92+
return;
93+
}
94+
95+
const emptySet = this.dependenciesToBeRemoved;
96+
this.dependenciesToBeRemoved = this.dependencies;
97+
this.dependencies = emptySet;
98+
99+
this.state = AutorunState.upToDate;
100+
101+
getLogger()?.handleAutorunTriggered(this);
102+
103+
try {
104+
this.runFn(this);
105+
} finally {
106+
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
107+
// Thus, we only unsubscribe from observables that are definitely not read anymore.
108+
for (const o of this.dependenciesToBeRemoved) {
109+
o.removeObserver(this);
110+
}
111+
this.dependenciesToBeRemoved.clear();
112+
}
104113
}
105114

115+
public toString(): string {
116+
return `Autorun<${this.debugName}>`;
117+
}
118+
119+
// IObserver implementation
106120
public beginUpdate(): void {
107121
if (this.state === AutorunState.upToDate) {
108122
this.state = AutorunState.dependenciesMightHaveChanged;
@@ -115,7 +129,7 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
115129
do {
116130
if (this.state === AutorunState.dependenciesMightHaveChanged) {
117131
this.state = AutorunState.upToDate;
118-
for (const d of this._dependencies) {
132+
for (const d of this.dependencies) {
119133
d.reportChanges();
120134
if (this.state as AutorunState === AutorunState.stale) {
121135
// The other dependencies will refresh on demand
@@ -124,7 +138,7 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
124138
}
125139
}
126140

127-
this.runIfNeeded();
141+
this._runIfNeeded();
128142
} while (this.state !== AutorunState.upToDate);
129143
}
130144
this.updateCount--;
@@ -149,42 +163,18 @@ export class AutorunObserver implements IObserver, IReader, IDisposable {
149163
}
150164
}
151165

152-
private runIfNeeded() {
153-
if (this.state === AutorunState.upToDate) {
154-
return;
155-
}
156-
157-
// Assert: this.staleDependencies is an empty set.
158-
const emptySet = this.staleDependencies;
159-
this.staleDependencies = this._dependencies;
160-
this._dependencies = emptySet;
161-
162-
this.state = AutorunState.upToDate;
163-
164-
getLogger()?.handleAutorunTriggered(this);
165-
166-
try {
167-
this.runFn(this);
168-
} finally {
169-
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
170-
// Thus, we only unsubscribe from observables that are definitely not read anymore.
171-
for (const o of this.staleDependencies) {
172-
o.removeObserver(this);
173-
}
174-
this.staleDependencies.clear();
175-
}
176-
}
177-
178-
public dispose(): void {
179-
this.disposed = true;
180-
for (const o of this._dependencies) {
181-
o.removeObserver(this);
166+
// IReader implementation
167+
public readObservable<T>(observable: IObservable<T>): T {
168+
// In case the run action disposes the autorun
169+
if (this.disposed) {
170+
return observable.get();
182171
}
183-
this._dependencies.clear();
184-
}
185172

186-
public toString(): string {
187-
return `Autorun<${this.debugName}>`;
173+
observable.addObserver(this);
174+
const value = observable.get();
175+
this.dependencies.add(observable);
176+
this.dependenciesToBeRemoved.delete(observable);
177+
return value;
188178
}
189179
}
190180

src/vs/base/common/observableImpl/base.ts

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,71 +8,106 @@ import type { derived } from 'vs/base/common/observableImpl/derived';
88
import { getLogger } from 'vs/base/common/observableImpl/logging';
99

1010
export interface IObservable<T, TChange = unknown> {
11-
readonly TChange: TChange;
12-
1311
/**
14-
* Reads the current value.
12+
* Returns the current value.
1513
*
16-
* Must not be called from {@link IObserver.handleChange}.
14+
* Calls {@link IObserver.handleChange} if the observable notices that the value changed.
15+
* Must not be called from {@link IObserver.handleChange}!
1716
*/
1817
get(): T;
1918

19+
/**
20+
* Forces the observable to check for and report changes.
21+
*
22+
* Has the same effect as calling {@link IObservable.get}, but does not force the observable
23+
* to actually construct the value, e.g. if change deltas are used.
24+
* Calls {@link IObserver.handleChange} if the observable notices that the value changed.
25+
* Must not be called from {@link IObserver.handleChange}!
26+
*/
2027
reportChanges(): void;
2128

29+
/**
30+
* Adds the observer to the set of subscribed observers.
31+
* This method is idempotent.
32+
*/
2233
addObserver(observer: IObserver): void;
34+
35+
/**
36+
* Removes the observer from the set of subscribed observers.
37+
* This method is idempotent.
38+
*/
2339
removeObserver(observer: IObserver): void;
2440

2541
/**
26-
* Subscribes the reader to this observable and returns the current value of this observable.
42+
* Reads the current value and subscribes to this observable.
43+
*
44+
* Just calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get}
45+
* (see {@link ConvenientObservable.read}).
2746
*/
2847
read(reader: IReader | undefined): T;
2948

49+
/**
50+
* Creates a derived observable that depends on this observable.
51+
* Use the reader to read other observables
52+
* (see {@link ConvenientObservable.map}).
53+
*/
3054
map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew>;
3155

56+
/**
57+
* A human-readable name for debugging purposes.
58+
*/
3259
readonly debugName: string;
3360

34-
/*get isPossiblyStale(): boolean;
35-
36-
get isUpdating(): boolean;*/
61+
/**
62+
* This property captures the type of the change object. Do not use it at runtime!
63+
*/
64+
readonly TChange: TChange;
3765
}
3866

3967
export interface IReader {
4068
/**
4169
* Reads the value of an observable and subscribes to it.
42-
*
43-
* Is called by {@link IObservable.read}.
4470
*/
4571
readObservable<T>(observable: IObservable<T, any>): T;
4672
}
4773

74+
/**
75+
* Represents an observer that can be subscribed to an observable.
76+
*
77+
* If an observer is subscribed to an observable and that observable didn't signal
78+
* a change through one of the observer methods, the observer can assume that the
79+
* observable didn't change.
80+
* If an observable reported a possible change, {@link IObservable.reportChanges} forces
81+
* the observable to report an actual change if there was one.
82+
*/
4883
export interface IObserver {
4984
/**
50-
* Indicates that calling {@link IObservable.get} might return a different value and the observable is in updating mode.
51-
* Must not be called when the given observable has already been reported to be in updating mode.
85+
* Signals that the given observable might have changed and a transaction potentially modifying that observable started.
86+
* Before the given observable can call this method again, is must call {@link IObserver.endUpdate}.
87+
*
88+
* The method {@link IObservable.reportChanges} can be used to force the observable to report the changes.
5289
*/
5390
beginUpdate<T>(observable: IObservable<T>): void;
5491

5592
/**
56-
* Is called by a subscribed observable when it leaves updating mode and it doesn't expect changes anymore,
57-
* i.e. when a transaction for that observable is over.
58-
*
59-
* Call {@link IObservable.reportChanges} to learn about possible changes (if they weren't reported yet).
93+
* Signals that the transaction that potentially modified the given observable ended.
6094
*/
6195
endUpdate<T>(observable: IObservable<T>): void;
6296

97+
/**
98+
* Signals that the given observable might have changed.
99+
* The method {@link IObservable.reportChanges} can be used to force the observable to report the changes.
100+
*
101+
* Implementations must not call into other observables, as they might not have received this event yet!
102+
* The change should be processed lazily or in {@link IObserver.endUpdate}.
103+
*/
63104
handlePossibleChange<T>(observable: IObservable<T>): void;
64105

65106
/**
66-
* Is called by a subscribed observable immediately after it notices a change.
67-
*
68-
* When {@link IObservable.get} is called two times and no change was reported before the second call returns,
69-
* there has been no change in between the two calls for that observable.
70-
*
71-
* If the update counter is zero for a subscribed observable and calling {@link IObservable.get} didn't trigger a change,
72-
* subsequent calls to {@link IObservable.get} don't trigger a change either until the update counter is increased again.
107+
* Signals that the given observable changed.
73108
*
74-
* Implementations must not call into other observables!
75-
* The change should be processed when all observed observables settled.
109+
* Implementations must not call into other observables, as they might not have received this event yet!
110+
* The change should be processed lazily or in {@link IObserver.endUpdate}.
76111
*/
77112
handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
78113
}
@@ -83,13 +118,10 @@ export interface ISettable<T, TChange = void> {
83118

84119
export interface ITransaction {
85120
/**
86-
* Calls `Observer.beginUpdate` immediately
87-
* and `Observer.endUpdate` when the transaction is complete.
121+
* Calls {@link Observer.beginUpdate} immediately
122+
* and {@link Observer.endUpdate} when the transaction ends.
88123
*/
89-
updateObserver(
90-
observer: IObserver,
91-
observable: IObservable<any, any>
92-
): void;
124+
updateObserver(observer: IObserver, observable: IObservable<any, any>): void;
93125
}
94126

95127
let _derived: typeof derived;

0 commit comments

Comments
 (0)