Skip to content

Commit b0a75ac

Browse files
Tree-Shake EventManager (#3640)
1 parent fee4e8b commit b0a75ac

File tree

5 files changed

+220
-140
lines changed

5 files changed

+220
-140
lines changed

packages/firestore/src/core/component_provider.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ import {
3838
SyncEngine
3939
} from './sync_engine';
4040
import { RemoteStore } from '../remote/remote_store';
41-
import { EventManager } from './event_manager';
41+
import {
42+
EventManager,
43+
newEventManager,
44+
eventManagerOnOnlineStateChange,
45+
eventManagerOnWatchChange,
46+
eventManagerOnWatchError
47+
} from './event_manager';
4248
import { AsyncQueue } from '../util/async_queue';
4349
import { DatabaseId, DatabaseInfo } from './database_info';
4450
import { Datastore, newDatastore } from '../remote/datastore';
@@ -334,7 +340,14 @@ export class OnlineComponentProvider {
334340
this.syncEngine = this.createSyncEngine(cfg);
335341
this.eventManager = this.createEventManager(cfg);
336342

337-
this.syncEngine.subscribe(this.eventManager);
343+
this.syncEngine.subscribe({
344+
onWatchChange: eventManagerOnWatchChange.bind(null, this.eventManager),
345+
onWatchError: eventManagerOnWatchError.bind(null, this.eventManager),
346+
onOnlineStateChange: eventManagerOnOnlineStateChange.bind(
347+
null,
348+
this.eventManager
349+
)
350+
});
338351

339352
this.sharedClientState.onlineStateHandler = onlineState =>
340353
applyOnlineStateChange(
@@ -353,7 +366,7 @@ export class OnlineComponentProvider {
353366
}
354367

355368
createEventManager(cfg: ComponentConfiguration): EventManager {
356-
return new EventManager();
369+
return newEventManager();
357370
}
358371

359372
createDatastore(cfg: ComponentConfiguration): Datastore {

packages/firestore/src/core/event_manager.ts

+151-106
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { debugAssert } from '../util/assert';
18+
import { debugAssert, debugCast } from '../util/assert';
1919
import { EventHandler } from '../util/misc';
2020
import { ObjectMap } from '../util/obj_map';
2121
import { canonifyQuery, Query, queryEquals, stringifyQuery } from './query';
22-
import { SyncEngineListener } from './sync_engine';
2322
import { OnlineState } from './types';
2423
import { ChangeType, DocumentViewChange, ViewSnapshot } from './view_snapshot';
2524
import { wrapInUserErrorIfRecoverable } from '../util/async_queue';
@@ -50,150 +49,196 @@ export interface Observer<T> {
5049
* assigned to SyncEngine's `listen()` and `unlisten()` API before usage. This
5150
* allows users to tree-shake the Watch logic.
5251
*/
53-
export class EventManager implements SyncEngineListener {
54-
private queries = new ObjectMap<Query, QueryListenersInfo>(
52+
export interface EventManager {
53+
onListen?: (query: Query) => Promise<ViewSnapshot>;
54+
onUnlisten?: (query: Query) => Promise<void>;
55+
}
56+
57+
export function newEventManager(): EventManager {
58+
return new EventManagerImpl();
59+
}
60+
61+
export class EventManagerImpl implements EventManager {
62+
queries = new ObjectMap<Query, QueryListenersInfo>(
5563
q => canonifyQuery(q),
5664
queryEquals
5765
);
5866

59-
private onlineState = OnlineState.Unknown;
67+
onlineState = OnlineState.Unknown;
6068

61-
private snapshotsInSyncListeners: Set<Observer<void>> = new Set();
69+
snapshotsInSyncListeners: Set<Observer<void>> = new Set();
6270

6371
/** Callback invoked when a Query is first listen to. */
6472
onListen?: (query: Query) => Promise<ViewSnapshot>;
6573
/** Callback invoked once all listeners to a Query are removed. */
6674
onUnlisten?: (query: Query) => Promise<void>;
75+
}
6776

68-
async listen(listener: QueryListener): Promise<void> {
69-
debugAssert(!!this.onListen, 'onListen not set');
70-
const query = listener.query;
71-
let firstListen = false;
72-
73-
let queryInfo = this.queries.get(query);
74-
if (!queryInfo) {
75-
firstListen = true;
76-
queryInfo = new QueryListenersInfo();
77-
}
77+
export async function eventManagerListen(
78+
eventManager: EventManager,
79+
listener: QueryListener
80+
): Promise<void> {
81+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
7882

79-
if (firstListen) {
80-
try {
81-
queryInfo.viewSnap = await this.onListen(query);
82-
} catch (e) {
83-
const firestoreError = wrapInUserErrorIfRecoverable(
84-
e,
85-
`Initialization of query '${stringifyQuery(listener.query)}' failed`
86-
);
87-
listener.onError(firestoreError);
88-
return;
89-
}
90-
}
83+
debugAssert(!!eventManagerImpl.onListen, 'onListen not set');
84+
const query = listener.query;
85+
let firstListen = false;
9186

92-
this.queries.set(query, queryInfo);
93-
queryInfo.listeners.push(listener);
94-
95-
// Run global snapshot listeners if a consistent snapshot has been emitted.
96-
const raisedEvent = listener.applyOnlineStateChange(this.onlineState);
97-
debugAssert(
98-
!raisedEvent,
99-
"applyOnlineStateChange() shouldn't raise an event for brand-new listeners."
100-
);
87+
let queryInfo = eventManagerImpl.queries.get(query);
88+
if (!queryInfo) {
89+
firstListen = true;
90+
queryInfo = new QueryListenersInfo();
91+
}
10192

102-
if (queryInfo.viewSnap) {
103-
const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);
104-
if (raisedEvent) {
105-
this.raiseSnapshotsInSyncEvent();
106-
}
93+
if (firstListen) {
94+
try {
95+
queryInfo.viewSnap = await eventManagerImpl.onListen(query);
96+
} catch (e) {
97+
const firestoreError = wrapInUserErrorIfRecoverable(
98+
e,
99+
`Initialization of query '${stringifyQuery(listener.query)}' failed`
100+
);
101+
listener.onError(firestoreError);
102+
return;
107103
}
108104
}
109105

110-
async unlisten(listener: QueryListener): Promise<void> {
111-
debugAssert(!!this.onUnlisten, 'onUnlisten not set');
112-
const query = listener.query;
113-
let lastListen = false;
114-
115-
const queryInfo = this.queries.get(query);
116-
if (queryInfo) {
117-
const i = queryInfo.listeners.indexOf(listener);
118-
if (i >= 0) {
119-
queryInfo.listeners.splice(i, 1);
120-
lastListen = queryInfo.listeners.length === 0;
121-
}
122-
}
106+
eventManagerImpl.queries.set(query, queryInfo);
107+
queryInfo.listeners.push(listener);
123108

124-
if (lastListen) {
125-
this.queries.delete(query);
126-
return this.onUnlisten(query);
127-
}
128-
}
109+
// Run global snapshot listeners if a consistent snapshot has been emitted.
110+
const raisedEvent = listener.applyOnlineStateChange(
111+
eventManagerImpl.onlineState
112+
);
113+
debugAssert(
114+
!raisedEvent,
115+
"applyOnlineStateChange() shouldn't raise an event for brand-new listeners."
116+
);
129117

130-
onWatchChange(viewSnaps: ViewSnapshot[]): void {
131-
let raisedEvent = false;
132-
for (const viewSnap of viewSnaps) {
133-
const query = viewSnap.query;
134-
const queryInfo = this.queries.get(query);
135-
if (queryInfo) {
136-
for (const listener of queryInfo.listeners) {
137-
if (listener.onViewSnapshot(viewSnap)) {
138-
raisedEvent = true;
139-
}
140-
}
141-
queryInfo.viewSnap = viewSnap;
142-
}
143-
}
118+
if (queryInfo.viewSnap) {
119+
const raisedEvent = listener.onViewSnapshot(queryInfo.viewSnap);
144120
if (raisedEvent) {
145-
this.raiseSnapshotsInSyncEvent();
121+
raiseSnapshotsInSyncEvent(eventManagerImpl);
146122
}
147123
}
124+
}
148125

149-
onWatchError(query: Query, error: Error): void {
150-
const queryInfo = this.queries.get(query);
151-
if (queryInfo) {
152-
for (const listener of queryInfo.listeners) {
153-
listener.onError(error);
154-
}
126+
export async function eventManagerUnlisten(
127+
eventManager: EventManager,
128+
listener: QueryListener
129+
): Promise<void> {
130+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
131+
132+
debugAssert(!!eventManagerImpl.onUnlisten, 'onUnlisten not set');
133+
const query = listener.query;
134+
let lastListen = false;
135+
136+
const queryInfo = eventManagerImpl.queries.get(query);
137+
if (queryInfo) {
138+
const i = queryInfo.listeners.indexOf(listener);
139+
if (i >= 0) {
140+
queryInfo.listeners.splice(i, 1);
141+
lastListen = queryInfo.listeners.length === 0;
155142
}
143+
}
156144

157-
// Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()
158-
// after an error.
159-
this.queries.delete(query);
145+
if (lastListen) {
146+
eventManagerImpl.queries.delete(query);
147+
return eventManagerImpl.onUnlisten(query);
160148
}
149+
}
161150

162-
onOnlineStateChange(onlineState: OnlineState): void {
163-
this.onlineState = onlineState;
164-
let raisedEvent = false;
165-
this.queries.forEach((_, queryInfo) => {
151+
export function eventManagerOnWatchChange(
152+
eventManager: EventManager,
153+
viewSnaps: ViewSnapshot[]
154+
): void {
155+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
156+
157+
let raisedEvent = false;
158+
for (const viewSnap of viewSnaps) {
159+
const query = viewSnap.query;
160+
const queryInfo = eventManagerImpl.queries.get(query);
161+
if (queryInfo) {
166162
for (const listener of queryInfo.listeners) {
167-
// Run global snapshot listeners if a consistent snapshot has been emitted.
168-
if (listener.applyOnlineStateChange(onlineState)) {
163+
if (listener.onViewSnapshot(viewSnap)) {
169164
raisedEvent = true;
170165
}
171166
}
172-
});
173-
if (raisedEvent) {
174-
this.raiseSnapshotsInSyncEvent();
167+
queryInfo.viewSnap = viewSnap;
175168
}
176169
}
177-
178-
addSnapshotsInSyncListener(observer: Observer<void>): void {
179-
this.snapshotsInSyncListeners.add(observer);
180-
// Immediately fire an initial event, indicating all existing listeners
181-
// are in-sync.
182-
observer.next();
170+
if (raisedEvent) {
171+
raiseSnapshotsInSyncEvent(eventManagerImpl);
183172
}
173+
}
184174

185-
removeSnapshotsInSyncListener(observer: Observer<void>): void {
186-
this.snapshotsInSyncListeners.delete(observer);
175+
export function eventManagerOnWatchError(
176+
eventManager: EventManager,
177+
query: Query,
178+
error: Error
179+
): void {
180+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
181+
182+
const queryInfo = eventManagerImpl.queries.get(query);
183+
if (queryInfo) {
184+
for (const listener of queryInfo.listeners) {
185+
listener.onError(error);
186+
}
187187
}
188188

189-
// Call all global snapshot listeners that have been set.
190-
private raiseSnapshotsInSyncEvent(): void {
191-
this.snapshotsInSyncListeners.forEach(observer => {
192-
observer.next();
193-
});
189+
// Remove all listeners. NOTE: We don't need to call syncEngine.unlisten()
190+
// after an error.
191+
eventManagerImpl.queries.delete(query);
192+
}
193+
194+
export function eventManagerOnOnlineStateChange(
195+
eventManager: EventManager,
196+
onlineState: OnlineState
197+
): void {
198+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
199+
200+
eventManagerImpl.onlineState = onlineState;
201+
let raisedEvent = false;
202+
eventManagerImpl.queries.forEach((_, queryInfo) => {
203+
for (const listener of queryInfo.listeners) {
204+
// Run global snapshot listeners if a consistent snapshot has been emitted.
205+
if (listener.applyOnlineStateChange(onlineState)) {
206+
raisedEvent = true;
207+
}
208+
}
209+
});
210+
if (raisedEvent) {
211+
raiseSnapshotsInSyncEvent(eventManagerImpl);
194212
}
195213
}
196214

215+
export function addSnapshotsInSyncListener(
216+
eventManager: EventManager,
217+
observer: Observer<void>
218+
): void {
219+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
220+
221+
eventManagerImpl.snapshotsInSyncListeners.add(observer);
222+
// Immediately fire an initial event, indicating all existing listeners
223+
// are in-sync.
224+
observer.next();
225+
}
226+
227+
export function removeSnapshotsInSyncListener(
228+
eventManager: EventManager,
229+
observer: Observer<void>
230+
): void {
231+
const eventManagerImpl = debugCast(eventManager, EventManagerImpl);
232+
eventManagerImpl.snapshotsInSyncListeners.delete(observer);
233+
}
234+
235+
// Call all global snapshot listeners that have been set.
236+
function raiseSnapshotsInSyncEvent(eventManagerImpl: EventManagerImpl): void {
237+
eventManagerImpl.snapshotsInSyncListeners.forEach(observer => {
238+
observer.next();
239+
});
240+
}
241+
197242
export interface ListenOptions {
198243
/** Raise events even when only the metadata changes */
199244
readonly includeMetadataChanges?: boolean;

0 commit comments

Comments
 (0)