-
Notifications
You must be signed in to change notification settings - Fork 928
Make View processing logic optional #3561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
aa4fc7f
8350a84
dd872af
e2be013
eafa8e9
03b1ce4
68f565b
4eccec3
f3efd44
88a5bc0
9f97606
4b5105c
9b018db
c7d62f4
08e425b
320d79a
a869f8f
d7b5d2a
74ab415
452e460
1b2d3eb
49bde61
2674016
7f52b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ import { debugAssert } from '../util/assert'; | |
import { EventHandler } from '../util/misc'; | ||
import { ObjectMap } from '../util/obj_map'; | ||
import { canonifyQuery, Query, queryEquals, stringifyQuery } from './query'; | ||
import { SyncEngine, SyncEngineListener } from './sync_engine'; | ||
import { SyncEngineListener } from './sync_engine'; | ||
import { OnlineState } from './types'; | ||
import { ChangeType, DocumentViewChange, ViewSnapshot } from './view_snapshot'; | ||
import { wrapInUserErrorIfRecoverable } from '../util/async_queue'; | ||
|
@@ -45,6 +45,10 @@ export interface Observer<T> { | |
* EventManager is responsible for mapping queries to query event emitters. | ||
* It handles "fan-out". -- Identical queries will re-use the same watch on the | ||
* backend. | ||
* | ||
* PORTING NOTE: On Web, EventManager requires a call to `subscribe()` to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is no longer accurate. Instead, the onListen and onUnlisten handlers need to be assigned. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
* register SyncEngine's `listen()` and `unlisten()` functionality. This allows | ||
* users to tree-shake the Watch logic. | ||
*/ | ||
export class EventManager implements SyncEngineListener { | ||
private queries = new ObjectMap<Query, QueryListenersInfo>( | ||
|
@@ -56,11 +60,21 @@ export class EventManager implements SyncEngineListener { | |
|
||
private snapshotsInSyncListeners: Set<Observer<void>> = new Set(); | ||
|
||
constructor(private syncEngine: SyncEngine) { | ||
this.syncEngine.subscribe(this); | ||
/** Callback invoked when a Query is first listen to. */ | ||
private onListen?: (query: Query) => Promise<ViewSnapshot>; | ||
/** Callback invoked once all listeners to a Query are removed. */ | ||
private onUnlisten?: (query: Query) => Promise<void>; | ||
|
||
subscribe( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name "subscribe" is confusing in this context because this doesn't actually subscribe, it only registers callbacks. Also have listen operations so it's potentially confusing to have to choose between listen and subscribe. Meanwhile, in other contexts where we're wiring up these kinds of things on demand there is no separate function: we're just assigning to the members to set them. Rather than renaming this to something like "registerHandlers" or something like that, why not remove this function and assign directly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That works for me. I changed it to assign directly. The code size change will be part of my next perf :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: I had to revert this since this breaks the "terminate()" tests. The call to get the component provider throws an exception if the client has already been terminated, and converting these calls to async functions turns that into a rejected Promise. |
||
onListen: (query: Query) => Promise<ViewSnapshot>, | ||
onUnlisten: (query: Query) => Promise<void> | ||
): void { | ||
this.onListen = onListen; | ||
this.onUnlisten = onUnlisten; | ||
} | ||
|
||
async listen(listener: QueryListener): Promise<void> { | ||
debugAssert(!!this.onListen, 'onListen not set'); | ||
const query = listener.query; | ||
let firstListen = false; | ||
|
||
|
@@ -72,7 +86,7 @@ export class EventManager implements SyncEngineListener { | |
|
||
if (firstListen) { | ||
try { | ||
queryInfo.viewSnap = await this.syncEngine.listen(query); | ||
queryInfo.viewSnap = await this.onListen(query); | ||
} catch (e) { | ||
const firestoreError = wrapInUserErrorIfRecoverable( | ||
e, | ||
|
@@ -102,6 +116,7 @@ export class EventManager implements SyncEngineListener { | |
} | ||
|
||
async unlisten(listener: QueryListener): Promise<void> { | ||
debugAssert(!!this.onUnlisten, 'onUnlisten not set'); | ||
const query = listener.query; | ||
let lastListen = false; | ||
|
||
|
@@ -116,7 +131,7 @@ export class EventManager implements SyncEngineListener { | |
|
||
if (lastListen) { | ||
this.queries.delete(query); | ||
return this.syncEngine.unlisten(query); | ||
return this.onUnlisten(query); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,13 @@ import { | |
Observer, | ||
QueryListener | ||
} from './event_manager'; | ||
import { SyncEngine } from './sync_engine'; | ||
import { | ||
registerPendingWritesCallback, | ||
SyncEngine, | ||
syncEngineListen, | ||
syncEngineUnlisten, | ||
syncEngineWrite | ||
} from './sync_engine'; | ||
import { View } from './view'; | ||
import { SharedClientState } from '../local/shared_client_state'; | ||
import { AutoId } from '../util/misc'; | ||
|
@@ -277,6 +283,11 @@ export class FirestoreClient { | |
this.syncEngine = onlineComponentProvider.syncEngine; | ||
this.eventMgr = onlineComponentProvider.eventManager; | ||
|
||
this.eventMgr.subscribe( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't doing this unconditionally here cause this to not be tree-shakeable? I would have thought we'd do this in the listen method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because of how FirestoreClient brings together all components, I actually removed FirestoreClient from the firestore-exp build to support the the cache-only build. It will eventually go away altogether, but for now it is still needed for the legacy build. In the modular build, these functions are only assigned in |
||
syncEngineListen.bind(null, this.syncEngine), | ||
syncEngineUnlisten.bind(null, this.syncEngine) | ||
); | ||
|
||
// When a user calls clearPersistence() in one client, all other clients | ||
// need to be terminated to allow the delete to succeed. | ||
this.persistence.setDatabaseDeletedListener(async () => { | ||
|
@@ -405,9 +416,9 @@ export class FirestoreClient { | |
this.verifyNotTerminated(); | ||
|
||
const deferred = new Deferred<void>(); | ||
this.asyncQueue.enqueueAndForget(() => { | ||
return this.syncEngine.registerPendingWritesCallback(deferred); | ||
}); | ||
this.asyncQueue.enqueueAndForget(() => | ||
registerPendingWritesCallback(this.syncEngine, deferred) | ||
); | ||
return deferred.promise; | ||
} | ||
|
||
|
@@ -480,7 +491,7 @@ export class FirestoreClient { | |
this.verifyNotTerminated(); | ||
const deferred = new Deferred<void>(); | ||
this.asyncQueue.enqueueAndForget(() => | ||
this.syncEngine.write(mutations, deferred) | ||
syncEngineWrite(this.syncEngine, mutations, deferred) | ||
); | ||
return deferred.promise; | ||
} | ||
|
@@ -549,7 +560,9 @@ export function enqueueWrite( | |
mutations: Mutation[] | ||
): Promise<void> { | ||
const deferred = new Deferred<void>(); | ||
asyncQueue.enqueueAndForget(() => syncEngine.write(mutations, deferred)); | ||
asyncQueue.enqueueAndForget(() => | ||
syncEngineWrite(syncEngine, mutations, deferred) | ||
); | ||
return deferred.promise; | ||
} | ||
|
||
|
@@ -570,9 +583,9 @@ export function enqueueWaitForPendingWrites( | |
syncEngine: SyncEngine | ||
): Promise<void> { | ||
const deferred = new Deferred<void>(); | ||
asyncQueue.enqueueAndForget(() => { | ||
return syncEngine.registerPendingWritesCallback(deferred); | ||
}); | ||
asyncQueue.enqueueAndForget(() => | ||
registerPendingWritesCallback(syncEngine, deferred) | ||
); | ||
return deferred.promise; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to address this here, necessarily, but why not write these with async/await?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember why I did it this way, but "await" here is much better, especially now that the function has some nested logic. Change is pretty easy and only in 5 places so I did it just now.